1
0
Fork 0
polaris-task-force/src/components/frontend/dashboard/RecentEvents.tsx

103 lines
3.7 KiB
TypeScript

import type { GameStructure, User } from "@/payload-types";
import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
import { BookOpenTextIcon, ScrollTextIcon } from "lucide-react";
import { formatType } from "@/utils/event-log/formatType";
interface EventEntry {
id: number;
timestamp: string;
type: string;
message: string;
system: boolean;
actor?: number | User | null;
structure?: number | GameStructure | null;
}
interface RecentEventsProps {
events: EventEntry[];
currentUserId?: number;
}
function timeAgo(dateStr: string): string {
const now = new Date();
const date = new Date(dateStr);
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return "just now";
if (diffMins < 60) return `${diffMins}m ago`;
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return `${diffHours}h ago`;
const diffDays = Math.floor(diffHours / 24);
return `${diffDays}d ago`;
}
export function RecentEvents({ events, currentUserId }: RecentEventsProps) {
if (events.length === 0) {
return (
<Item variant="outline">
<ItemHeader>
<div className="flex items-center gap-2">
<ScrollTextIcon className="size-4 text-muted-foreground" />
<ItemTitle>Recent Events</ItemTitle>
</div>
</ItemHeader>
<ItemContent>
<p className="text-sm text-muted-foreground italic">No recent events recorded.</p>
</ItemContent>
</Item>
);
}
return (
<Item variant="outline">
<ItemHeader>
<div className="flex items-center gap-2">
<ScrollTextIcon className="size-4 text-muted-foreground" />
<ItemTitle>Recent Events</ItemTitle>
</div>
</ItemHeader>
<ItemContent>
<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?.displayName ?? actor?.username ?? null;
const actorLabel = isYou ? "You" : actorName;
const isNarrative = event.system === false;
return (
<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>
);
})}
</div>
</ItemContent>
</Item>
);
}