- Add campaigns list page with campaign cards - Add missions list page with filtering and mission cards - Add factions list page with faction cards - Add mission detail components: briefing, comms, factions, logistics, objectives, roles - Add MissionFilters for status and type filtering
110 lines
3.4 KiB
TypeScript
110 lines
3.4 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
Item,
|
|
ItemContent,
|
|
ItemDescription,
|
|
ItemHeader,
|
|
ItemMedia,
|
|
ItemTitle,
|
|
} from "@/components/ui/item";
|
|
import { convertLexicalToPlaintext } from "@payloadcms/richtext-lexical/plaintext";
|
|
import { ShieldIcon, UsersIcon } from "lucide-react";
|
|
|
|
interface FactionCardProps {
|
|
faction: {
|
|
id: number;
|
|
name: string;
|
|
type: "majorFaction" | "minorFaction" | "corporation" | "special";
|
|
description?: {
|
|
root: {
|
|
type: string;
|
|
children: {
|
|
type: any;
|
|
version: number;
|
|
[k: string]: unknown;
|
|
}[];
|
|
direction: ("ltr" | "rtl") | null;
|
|
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
|
indent: number;
|
|
version: number;
|
|
};
|
|
[k: string]: unknown;
|
|
} | null;
|
|
playableFaction?: boolean | null;
|
|
startingFaction?: boolean | null;
|
|
associatedCampaigns?: (number | { id: number; name: string })[] | null;
|
|
};
|
|
}
|
|
|
|
const typeColors: Record<string, string> = {
|
|
majorFaction: "bg-blue-500/20 text-blue-400",
|
|
minorFaction: "bg-muted text-muted-foreground",
|
|
corporation: "bg-amber-500/20 text-amber-400",
|
|
special: "bg-purple-500/20 text-purple-400",
|
|
};
|
|
|
|
const typeLabels: Record<string, string> = {
|
|
majorFaction: "Major Faction",
|
|
minorFaction: "Minor Faction",
|
|
corporation: "Corporation",
|
|
special: "Special",
|
|
};
|
|
|
|
export function FactionCard({ faction }: FactionCardProps) {
|
|
const description = faction.description
|
|
? convertLexicalToPlaintext({ data: faction.description })
|
|
: null;
|
|
const truncatedDescription =
|
|
description && description.length > 150
|
|
? description.slice(0, 150) + "…"
|
|
: description;
|
|
|
|
const campaignCount = faction.associatedCampaigns?.length ?? 0;
|
|
|
|
return (
|
|
<Item variant="outline">
|
|
<ItemHeader>
|
|
<div className="flex items-center gap-2">
|
|
<ItemMedia variant="icon">
|
|
<ShieldIcon />
|
|
</ItemMedia>
|
|
<ItemTitle>{faction.name}</ItemTitle>
|
|
</div>
|
|
<span
|
|
className={`text-[10px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded ${typeColors[faction.type] ?? "bg-muted text-muted-foreground"}`}
|
|
>
|
|
{typeLabels[faction.type] ?? faction.type}
|
|
</span>
|
|
</ItemHeader>
|
|
<ItemContent>
|
|
<div className="flex flex-col gap-3 w-full">
|
|
{truncatedDescription && (
|
|
<p className="text-sm text-muted-foreground">{truncatedDescription}</p>
|
|
)}
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
{faction.playableFaction && (
|
|
<span className="text-[10px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded bg-green-500/20 text-green-400">
|
|
Playable
|
|
</span>
|
|
)}
|
|
{faction.startingFaction && (
|
|
<span className="text-[10px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-400">
|
|
Starting
|
|
</span>
|
|
)}
|
|
{campaignCount > 0 && (
|
|
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
<UsersIcon className="size-3" />
|
|
{campaignCount} campaign{campaignCount !== 1 ? "s" : ""}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</ItemContent>
|
|
<ItemDescription>
|
|
{truncatedDescription ?? "No description provided."}
|
|
</ItemDescription>
|
|
</Item>
|
|
);
|
|
}
|