112 lines
3.1 KiB
TypeScript
112 lines
3.1 KiB
TypeScript
import { CampaignDocuments } from "@/components/campaign/CampaignDocuments";
|
|
import { DocumentPreview } from "@/components/documents/DocumentPreview";
|
|
import { Tab, TabbedLayout } from "@/components/layout/TabbedLayout";
|
|
import { Loader } from "@/components/Loader";
|
|
import { DocumentLoader } from "@/context/document/DocumentLoader";
|
|
import { useDocument } from "@/context/document/hooks";
|
|
import { pb } from "@/lib/pocketbase";
|
|
import type { Campaign, DocumentId } from "@/lib/types";
|
|
import { createFileRoute, Link } from "@tanstack/react-router";
|
|
import { useEffect, useState } from "react";
|
|
import { z } from "zod";
|
|
|
|
const CampaignTabs = {
|
|
sessions: { label: "Sessions", docType: "session" },
|
|
secrets: { label: "Secrets", docType: "secret" },
|
|
npcs: { label: "NPCs", docType: "npc" },
|
|
locations: { label: "Locations", docType: "location" },
|
|
threads: { label: "Threads", docType: "thread" },
|
|
fronts: { label: "Fronts", docType: "front" },
|
|
} as const;
|
|
|
|
const campaignSearchSchema = z.object({
|
|
tab: z
|
|
.enum(Object.keys(CampaignTabs) as (keyof typeof CampaignTabs)[])
|
|
.default("sessions"),
|
|
docId: z.optional(z.string().transform((s) => s as DocumentId)),
|
|
});
|
|
|
|
export const Route = createFileRoute(
|
|
"/_app/_authenticated/campaigns/$campaignId",
|
|
)({
|
|
component: RouteComponent,
|
|
pendingComponent: Loader,
|
|
validateSearch: (s) => campaignSearchSchema.parse(s),
|
|
});
|
|
|
|
function RouteComponent() {
|
|
const params = Route.useParams();
|
|
const { tab, docId } = Route.useSearch();
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
const [campaign, setCampaign] = useState<Campaign | null>(null);
|
|
|
|
useEffect(() => {
|
|
async function fetchData() {
|
|
setLoading(true);
|
|
const campaign = await pb
|
|
.collection("campaigns")
|
|
.getOne(params.campaignId);
|
|
setCampaign(campaign as Campaign);
|
|
setLoading(false);
|
|
}
|
|
fetchData();
|
|
}, [setCampaign, setLoading]);
|
|
|
|
if (loading || campaign === null) {
|
|
return <Loader />;
|
|
}
|
|
|
|
return (
|
|
<TabbedLayout
|
|
title={
|
|
<h2 className="text-2xl font-bold text-slate-100">{campaign.name}</h2>
|
|
}
|
|
navigation={
|
|
<Link
|
|
to="/campaigns"
|
|
className="text-slate-400 hover:text-violet-400 text-sm underline underline-offset-2 transition-colors"
|
|
>
|
|
← Back to campaigns
|
|
</Link>
|
|
}
|
|
tabs={Object.entries(CampaignTabs).map(([key, { label }]) => (
|
|
<Tab
|
|
key={key}
|
|
label={label}
|
|
active={tab === key}
|
|
to={Route.to}
|
|
params={{
|
|
campaignId: campaign.id,
|
|
}}
|
|
search={{
|
|
tab: key,
|
|
}}
|
|
/>
|
|
))}
|
|
content={
|
|
<CampaignDocuments
|
|
campaignId={campaign.id}
|
|
docType={CampaignTabs[tab].docType}
|
|
/>
|
|
}
|
|
flyout={docId && <Flyout key={docId} docId={docId} />}
|
|
/>
|
|
);
|
|
}
|
|
function Flyout({ docId }: { docId: DocumentId }) {
|
|
const { docResult } = useDocument(docId);
|
|
|
|
if (docResult?.type !== "ready") {
|
|
return (
|
|
<DocumentLoader documentId={docId}>
|
|
<Loader />
|
|
</DocumentLoader>
|
|
);
|
|
}
|
|
|
|
const doc = docResult.value.doc;
|
|
|
|
return <DocumentPreview doc={doc} />;
|
|
}
|