From 81fd84790b93308b5d0a4caa4198d40af141f12e Mon Sep 17 00:00:00 2001 From: Drew Haven Date: Sat, 31 May 2025 17:37:38 -0700 Subject: [PATCH] Makes a generic document form --- src/components/RelationshipList.tsx | 37 ++++++++++--------- src/components/documents/DocumentForm.tsx | 28 ++++++++++++++ .../documents/secret/SecretForm.tsx | 4 +- src/lib/types.ts | 7 +++- .../_authenticated/document.$documentId.tsx | 4 -- 5 files changed, 55 insertions(+), 25 deletions(-) create mode 100644 src/components/documents/DocumentForm.tsx diff --git a/src/components/RelationshipList.tsx b/src/components/RelationshipList.tsx index d4a9012..0729da2 100644 --- a/src/components/RelationshipList.tsx +++ b/src/components/RelationshipList.tsx @@ -1,26 +1,25 @@ import { useEffect, useState } from "react"; import { pb } from "@/lib/pocketbase"; -import type { Document } from "@/lib/types"; +import type { Document, RelationshipType } from "@/lib/types"; import { DocumentList } from "@/components/DocumentList"; import { Loader } from "./Loader"; import { DocumentRow } from "./documents/DocumentRow"; +import { DocumentForm } from "./documents/DocumentForm"; -interface RelationshipListProps { +interface RelationshipListProps { root: Document; - relationshipType: string; - newItemForm: (onCreate: (doc: T) => Promise) => React.ReactNode; + relationshipType: RelationshipType; } /** * RelationshipList manages a list of documents related to a root document via a relationship type. * It handles fetching, creation, and relationship management, and renders a DocumentList. */ -export function RelationshipList({ +export function RelationshipList({ root, relationshipType, - newItemForm, -}: RelationshipListProps) { - const [items, setItems] = useState([]); +}: RelationshipListProps) { + const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -40,13 +39,13 @@ export function RelationshipList({ relationships.items.length > 0 ? relationships.items[0].secondary : []; - let docs: T[] = []; + let docs: Document[] = []; if (Array.isArray(secondaryIds) && secondaryIds.length > 0) { docs = (await pb.collection("documents").getFullList({ filter: secondaryIds .map((id: string) => `id = "${id}"`) .join(" || "), - })) as T[]; + })) as Document[]; } if (!cancelled) setItems(docs); } catch (e: any) { @@ -63,7 +62,7 @@ export function RelationshipList({ }, [root.id, relationshipType]); // Handles creation of a new document and adds it to the relationship - const handleCreate = async (doc: T) => { + const handleCreate = async (doc: Document) => { setLoading(true); setError(null); try { @@ -104,12 +103,16 @@ export function RelationshipList({ items={items} error={error} renderRow={(document) => } - newItemForm={(onSubmit) => - newItemForm(async (doc: T) => { - await handleCreate(doc); - onSubmit(); - }) - } + newItemForm={(onSubmit) => ( + { + await handleCreate(doc); + onSubmit(); + }} + /> + )} /> ); } diff --git a/src/components/documents/DocumentForm.tsx b/src/components/documents/DocumentForm.tsx new file mode 100644 index 0000000..5984e57 --- /dev/null +++ b/src/components/documents/DocumentForm.tsx @@ -0,0 +1,28 @@ +import { RelationshipType, type CampaignId, type Document } from "@/lib/types"; +import { SecretForm } from "./secret/SecretForm"; + +function assertUnreachable(_x: never): never { + throw new Error("DocumentForm switch is not exhaustive"); +} + +/** + * Renders a form for any document type depending on the relationship. + */ +export const DocumentForm = ({ + campaignId, + relationshipType, + onCreate, +}: { + campaignId: CampaignId; + relationshipType: RelationshipType; + onCreate: (document: Document) => Promise; +}) => { + switch (relationshipType) { + case RelationshipType.Secrets: + return ; + case RelationshipType.DiscoveredIn: + return "Form not supported here"; + } + + return assertUnreachable(relationshipType); +}; diff --git a/src/components/documents/secret/SecretForm.tsx b/src/components/documents/secret/SecretForm.tsx index 081ec92..474cb95 100644 --- a/src/components/documents/secret/SecretForm.tsx +++ b/src/components/documents/secret/SecretForm.tsx @@ -1,7 +1,7 @@ // SecretForm.tsx // Form for adding a new secret to a session. import { useState } from "react"; -import type { Secret } from "@/lib/types"; +import type { CampaignId, Secret } from "@/lib/types"; import { pb } from "@/lib/pocketbase"; /** @@ -11,7 +11,7 @@ export const SecretForm = ({ campaign, onCreate, }: { - campaign: string; + campaign: CampaignId; onCreate: (secret: Secret) => Promise; }) => { const [newSecret, setNewSecret] = useState(""); diff --git a/src/lib/types.ts b/src/lib/types.ts index e43d62d..5908c07 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -14,7 +14,7 @@ export type Campaign = RecordModel & { export type Document = RecordModel & { id: DocumentId; - campaign: Campaign; + campaign: CampaignId; data: {}; // These two are not in Pocketbase's types, but they seem to always be present created: string; @@ -57,8 +57,11 @@ export const RelationshipType = { DiscoveredIn: "discoveredIn", } as const; +export type RelationshipType = + (typeof RelationshipType)[keyof typeof RelationshipType]; + export type Relationship = RecordModel & { primary: DocumentId; secondary: DocumentId[]; - type: (typeof RelationshipType)[keyof typeof RelationshipType]; + type: RelationshipType; }; diff --git a/src/routes/_authenticated/document.$documentId.tsx b/src/routes/_authenticated/document.$documentId.tsx index 364ad30..0f5a344 100644 --- a/src/routes/_authenticated/document.$documentId.tsx +++ b/src/routes/_authenticated/document.$documentId.tsx @@ -3,7 +3,6 @@ import { pb } from "@/lib/pocketbase"; import { RelationshipType, type Session } from "@/lib/types"; import { RelationshipList } from "@/components/RelationshipList"; import { SessionForm } from "@/components/documents/session/SessionForm"; -import { SecretForm } from "@/components/documents/secret/SecretForm"; export const Route = createFileRoute("/_authenticated/document/$documentId")({ loader: async ({ params }) => { @@ -28,9 +27,6 @@ function RouteComponent() { ( - - )} /> );