1
0
Fork 0

style(dashboard): polish quick stats and recent events feed

- Emphasize stat values and keep labels on one line
- Rework the recent events feed into a two-line layout with human-readable type labels and narrative highlighting
This commit is contained in:
Jason Fraley 2026-08-11 23:25:44 -04:00
parent 66816e2182
commit 7f27b5e823
2 changed files with 56 additions and 34 deletions

View file

@ -28,14 +28,16 @@ export function QuickStats({
totalMembers,
}: QuickStatsProps) {
return (
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
<div className="grid grid-cols-2 md:grid-cols-3 2xl:grid-cols-5 gap-3">
<Item variant="muted" size="sm">
<ItemMedia variant="icon">
<CrosshairIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Active Campaigns</ItemTitle>
<ItemDescription>{activeCampaigns}</ItemDescription>
<ItemTitle className="whitespace-nowrap">Active Campaigns</ItemTitle>
<ItemDescription className="text-2xl font-semibold text-foreground leading-none">
{activeCampaigns}
</ItemDescription>
</ItemContent>
</Item>
<Item variant="muted" size="sm">
@ -43,8 +45,10 @@ export function QuickStats({
<CrosshairIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Total Missions</ItemTitle>
<ItemDescription>{totalMissions}</ItemDescription>
<ItemTitle className="whitespace-nowrap">Total Missions</ItemTitle>
<ItemDescription className="text-2xl font-semibold text-foreground leading-none">
{totalMissions}
</ItemDescription>
</ItemContent>
</Item>
<Item variant="muted" size="sm">
@ -52,8 +56,10 @@ export function QuickStats({
<Building2Icon />
</ItemMedia>
<ItemContent>
<ItemTitle>Structures</ItemTitle>
<ItemDescription>{totalStructures}</ItemDescription>
<ItemTitle className="whitespace-nowrap">Structures</ItemTitle>
<ItemDescription className="text-2xl font-semibold text-foreground leading-none">
{totalStructures}
</ItemDescription>
</ItemContent>
</Item>
<Item variant="muted" size="sm">
@ -61,8 +67,10 @@ export function QuickStats({
<ShieldIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Factions</ItemTitle>
<ItemDescription>{totalFactions}</ItemDescription>
<ItemTitle className="whitespace-nowrap">Factions</ItemTitle>
<ItemDescription className="text-2xl font-semibold text-foreground leading-none">
{totalFactions}
</ItemDescription>
</ItemContent>
</Item>
<Item variant="muted" size="sm">
@ -70,8 +78,10 @@ export function QuickStats({
<UsersIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Operators</ItemTitle>
<ItemDescription>{totalMembers}</ItemDescription>
<ItemTitle className="whitespace-nowrap">Operators</ItemTitle>
<ItemDescription className="text-2xl font-semibold text-foreground leading-none">
{totalMembers}
</ItemDescription>
</ItemContent>
</Item>
</div>

View file

@ -6,7 +6,8 @@ import {
ItemHeader,
ItemTitle,
} from "@/components/ui/item";
import { ScrollTextIcon } from "lucide-react";
import { BookOpenTextIcon, ScrollTextIcon } from "lucide-react";
import { formatType } from "@/utils/event-log/formatType";
interface EventEntry {
id: number;
@ -64,36 +65,47 @@ export function RecentEvents({ events, currentUserId }: RecentEventsProps) {
</div>
</ItemHeader>
<ItemContent>
<div className="flex flex-col gap-2">
{events.map((event) => {
<div className="flex flex-col">
{events.map((event, index) => {
const actor =
typeof event.actor === "object" && event.actor !== null
? (event.actor as User)
: null;
const actorId =
actor?.id ??
(typeof event.actor === "number" ? event.actor : null);
const isYou = actorId != null && actorId === currentUserId;
const actorName =
actor?.id === currentUserId
? "You"
: actor?.displayName ?? actor?.username ?? null;
actor?.displayName ?? actor?.username ?? null;
const actorLabel = isYou ? "You" : actorName;
const isNarrative = event.system === false;
return (
<div
key={event.id}
className="flex items-start gap-3 text-sm py-1.5 border-b border-border/50 last:border-0"
>
<span className="text-xs text-muted-foreground font-mono whitespace-nowrap pt-0.5">
{timeAgo(event.timestamp)}
</span>
<div className="flex flex-col gap-0.5 min-w-0">
<p className="text-sm leading-snug">
{actorName && (
<span className="font-medium">{actorName} </span>
)}
{event.message}
</p>
<span className="text-[10px] uppercase tracking-wider text-muted-foreground font-mono">
{event.type}
</span>
<div key={event.id}>
<div
className={`flex items-start gap-3 py-2 text-sm ${isNarrative ? "pl-3 border-l-2 border-amber-500/40" : ""}`}
>
{isNarrative && (
<BookOpenTextIcon className="size-4 text-amber-400 shrink-0 mt-0.5" />
)}
<div className="flex-1 min-w-0 flex flex-col gap-0.5">
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<span className="tabular-nums shrink-0">
{timeAgo(event.timestamp)}
</span>
{actorLabel && (
<span className={`font-medium ${isYou ? "text-foreground" : ""}`}>
{actorLabel}
</span>
)}
<span className="capitalize">{formatType(event.type)}</span>
</div>
<span className={`min-w-0 ${isNarrative ? "text-amber-100/90" : ""}`}>
{event.message}
</span>
</div>
</div>
{index < events.length - 1 && <div className="border-t border-border" />}
</div>
);
})}