From 6b6636d695610955acac4d094b636e7a8ea84157 Mon Sep 17 00:00:00 2001 From: Drew Haven Date: Sat, 31 May 2025 17:19:50 -0700 Subject: [PATCH] Makes a generic document row --- src/components/RelationshipList.tsx | 5 +-- src/components/documents/DocumentRow.tsx | 35 ++++++++++++++++ .../documents/secret/SecretForm.tsx | 12 ++++-- src/components/documents/secret/SecretRow.tsx | 41 ++++++++++++------- src/lib/types.ts | 8 ++++ .../_authenticated/document.$documentId.tsx | 7 +--- 6 files changed, 82 insertions(+), 26 deletions(-) create mode 100644 src/components/documents/DocumentRow.tsx diff --git a/src/components/RelationshipList.tsx b/src/components/RelationshipList.tsx index c00285d..d4a9012 100644 --- a/src/components/RelationshipList.tsx +++ b/src/components/RelationshipList.tsx @@ -3,11 +3,11 @@ import { pb } from "@/lib/pocketbase"; import type { Document } from "@/lib/types"; import { DocumentList } from "@/components/DocumentList"; import { Loader } from "./Loader"; +import { DocumentRow } from "./documents/DocumentRow"; interface RelationshipListProps { root: Document; relationshipType: string; - renderRow: (item: T) => React.ReactNode; newItemForm: (onCreate: (doc: T) => Promise) => React.ReactNode; } @@ -18,7 +18,6 @@ interface RelationshipListProps { export function RelationshipList({ root, relationshipType, - renderRow, newItemForm, }: RelationshipListProps) { const [items, setItems] = useState([]); @@ -104,7 +103,7 @@ export function RelationshipList({ } items={items} error={error} - renderRow={renderRow} + renderRow={(document) => } newItemForm={(onSubmit) => newItemForm(async (doc: T) => { await handleCreate(doc); diff --git a/src/components/documents/DocumentRow.tsx b/src/components/documents/DocumentRow.tsx new file mode 100644 index 0000000..61d7748 --- /dev/null +++ b/src/components/documents/DocumentRow.tsx @@ -0,0 +1,35 @@ +// DocumentRow.tsx +// Generic row component for displaying any document type. +import { SessionRow } from "@/components/documents/session/SessionRow"; +import { SecretRow } from "@/components/documents/secret/SecretRow"; +import { isSecret, isSession, type Document, type Session } from "@/lib/types"; + +/** + * Renders a row for any document type. Prioritizes Session, then Secret, then falls back to ID and creation time. + * If rendering a SecretRow, uses the provided session prop if available. + */ +export const DocumentRow = ({ + document, + session, +}: { + document: Document; + session?: Session; +}) => { + if (isSession(document)) { + // Use SessionRow for session documents + return ; + } + if (isSecret(document)) { + return ; + } + // Fallback: show ID and creation time + return ( +
+
+ Unrecognized Document +
+
ID: {document.id}
+
Created: {document.created}
+
+ ); +}; diff --git a/src/components/documents/secret/SecretForm.tsx b/src/components/documents/secret/SecretForm.tsx index 185f371..081ec92 100644 --- a/src/components/documents/secret/SecretForm.tsx +++ b/src/components/documents/secret/SecretForm.tsx @@ -1,13 +1,19 @@ // SecretForm.tsx // Form for adding a new secret to a session. import { useState } from "react"; -import type { Session, Secret } from "@/lib/types"; +import type { Secret } from "@/lib/types"; import { pb } from "@/lib/pocketbase"; /** * Renders a form to add a new secret. Calls onCreate with the new secret document. */ -export const SecretForm = ({ session, onCreate }: { session: Session; onCreate: (secret: Secret) => Promise; }) => { +export const SecretForm = ({ + campaign, + onCreate, +}: { + campaign: string; + onCreate: (secret: Secret) => Promise; +}) => { const [newSecret, setNewSecret] = useState(""); const [adding, setAdding] = useState(false); const [error, setError] = useState(null); @@ -19,7 +25,7 @@ export const SecretForm = ({ session, onCreate }: { session: Session; onCreate: setError(null); try { const secretDoc: Secret = await pb.collection("documents").create({ - campaign: session.campaign, + campaign, data: { secret: { text: newSecret, diff --git a/src/components/documents/secret/SecretRow.tsx b/src/components/documents/secret/SecretRow.tsx index 68a46ed..eee109b 100644 --- a/src/components/documents/secret/SecretRow.tsx +++ b/src/components/documents/secret/SecretRow.tsx @@ -8,8 +8,16 @@ import { useState } from "react"; * Renders a secret row with a discovered checkbox and secret text. * Handles updating the discovered state and discoveredIn relationship. */ -export const SecretRow = ({ secret, session }: { secret: Secret; session: Session }) => { - const [checked, setChecked] = useState(!!(secret.data as any)?.secret?.discovered); +export const SecretRow = ({ + secret, + session, +}: { + secret: Secret; + session?: Session; +}) => { + const [checked, setChecked] = useState( + !!(secret.data as any)?.secret?.discovered, + ); const [loading, setLoading] = useState(false); async function handleChange(e: React.ChangeEvent) { @@ -26,19 +34,24 @@ export const SecretRow = ({ secret, session }: { secret: Secret; session: Sessio }, }, }); - // Remove any existing discoveredIn relationship - const rels = await pb.collection("relationships").getList(1, 1, { - filter: `primary = "${secret.id}" && type = "discoveredIn"`, - }); - if (rels.items.length > 0) { - await pb.collection("relationships").delete(rels.items[0].id); - } - if (newChecked) { - await pb.collection("relationships").create({ - primary: secret.id, - secondary: [session.id], - type: "discoveredIn", + if (session || !newChecked) { + // If the session exists or the element is being unchecked, remove any + // existing discoveredIn relationship + const rels = await pb.collection("relationships").getList(1, 1, { + filter: `primary = "${secret.id}" && type = "discoveredIn"`, }); + if (rels.items.length > 0) { + await pb.collection("relationships").delete(rels.items[0].id); + } + } + if (session) { + if (newChecked) { + await pb.collection("relationships").create({ + primary: secret.id, + secondary: [session.id], + type: "discoveredIn", + }); + } } } finally { setLoading(false); diff --git a/src/lib/types.ts b/src/lib/types.ts index abb1335..e43d62d 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -33,6 +33,10 @@ export type Session = Document & } >; +export function isSession(doc: Document): doc is Session { + return Object.hasOwn(doc.data, "session"); +} + export type ISO8601Date = string & { __type: "iso8601date" }; export type Secret = Document & @@ -44,6 +48,10 @@ export type Secret = Document & } >; +export function isSecret(doc: Document): doc is Secret { + return Object.hasOwn(doc.data, "secret"); +} + export const RelationshipType = { Secrets: "secrets", DiscoveredIn: "discoveredIn", diff --git a/src/routes/_authenticated/document.$documentId.tsx b/src/routes/_authenticated/document.$documentId.tsx index 5e3f62d..364ad30 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 { SecretRow } from "@/components/documents/secret/SecretRow"; import { SecretForm } from "@/components/documents/secret/SecretForm"; export const Route = createFileRoute("/_authenticated/document/$documentId")({ @@ -29,12 +28,8 @@ function RouteComponent() { { - if (!(secret.data as any)?.secret) return null; - return ; - }} newItemForm={(onCreate) => ( - + )} />