1
0
Fork 0
polaris-task-force/src/components/frontend/intelligence/FactionCard.tsx
Z8MB1E 28a148c06f feat: add intelligence section with campaigns, missions, and factions
- 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
2026-07-31 20:02:07 -04:00

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>
);
}