diff --git a/src/components/documents/DocumentEditForm.tsx b/src/components/documents/DocumentEditForm.tsx index b5eba63..06f11c8 100644 --- a/src/components/documents/DocumentEditForm.tsx +++ b/src/components/documents/DocumentEditForm.tsx @@ -28,14 +28,6 @@ export const DocumentEditForm = ({ document }: { document: AnyDocument }) => { case "treasure": return ; case "thread": - return ( - - ); + return ; } }; diff --git a/src/components/documents/DocumentLink.tsx b/src/components/documents/DocumentLink.tsx index bdce0f0..5cfe017 100644 --- a/src/components/documents/DocumentLink.tsx +++ b/src/components/documents/DocumentLink.tsx @@ -8,37 +8,45 @@ export type Props = React.PropsWithChildren<{ }>; export function DocumentLink({ childDocId, className, children }: Props) { - const docPath = useDocumentPath(); + // const docPath = useDocumentPath(); + // + // const params = useParams({ + // strict: false, + // }); + // + // const campaignSearch = useSearch({ + // from: "/_app/_authenticated/campaigns/$campaignId", + // shouldThrow: false, + // }); + // + // const to = params.campaignId + // ? `/campaigns/${params.campaignId}` + // : docPath + // ? makeDocumentPath( + // docPath.documentId, + // docPath?.relationshipType, + // childDocId, + // ) + // : undefined; + // + // const search = campaignSearch + // ? { tab: campaignSearch.tab, docId: childDocId } + // : undefined; + // + // if (to === undefined) { + // throw new Error("Not in a document or campaign context"); + // } + // + // return ( + // + // {children} + // + // ); - const params = useParams({ - strict: false, - }); - - const campaignSearch = useSearch({ - from: "/_app/_authenticated/campaigns/$campaignId", - shouldThrow: false, - }); - - const to = params.campaignId - ? `/campaigns/${params.campaignId}` - : docPath - ? makeDocumentPath( - docPath.documentId, - docPath?.relationshipType, - childDocId, - ) - : undefined; - - const search = campaignSearch - ? { tab: campaignSearch.tab, docId: childDocId } - : undefined; - - if (to === undefined) { - throw new Error("Not in a document or campaign context"); - } + const to = makeDocumentPath(childDocId); return ( - + {children} ); diff --git a/src/components/documents/GenericEditForm.tsx b/src/components/documents/GenericEditForm.tsx index a466d22..922415c 100644 --- a/src/components/documents/GenericEditForm.tsx +++ b/src/components/documents/GenericEditForm.tsx @@ -56,6 +56,7 @@ const GenericEditFormField = ({ multiline={true} value={field.getter(data) as string} onSave={saveField} + id={field.name} /> ); case "shortText": @@ -64,6 +65,7 @@ const GenericEditFormField = ({ multiline={false} value={field.getter(data) as string} onSave={saveField} + id={field.name} /> ); case "toggle": @@ -73,6 +75,7 @@ const GenericEditFormField = ({ checked={!!field.getter(data)} onChange={(e) => saveField(!!e.target.value)} className="accent-emerald-500 w-5 h-5" + id={field.name} /> ); } diff --git a/src/components/documents/GenericNewDocumentForm.tsx b/src/components/documents/GenericNewDocumentForm.tsx new file mode 100644 index 0000000..5b51c69 --- /dev/null +++ b/src/components/documents/GenericNewDocumentForm.tsx @@ -0,0 +1,152 @@ +import { AutoSaveTextarea } from "@/components/AutoSaveTextarea"; +import { useDocumentCache } from "@/context/document/hooks"; +import { + getFieldsForType, + type DocumentField, + type FieldType, + type ValueForFieldType, +} from "@/lib/fields"; +import { pb } from "@/lib/pocketbase"; +import { + type CampaignId, + type DocumentData, + type DocumentsByType, + type DocumentType, +} from "@/lib/types"; +import { useCallback, useState } from "react"; + +export type GenericFieldType = "multiline" | "singleline" | "checkbox"; + +export type Props = { + docType: T; + campaignId: CampaignId; + onCreate: (doc: DocumentsByType[T]) => Promise; +}; + +export const GenericNewDocumentForm = ({ + docType, + campaignId, + onCreate, +}: Props) => { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const { dispatch } = useDocumentCache(); + + const fields = getFieldsForType(docType); + + const [docData, setDocData] = useState>( + fields.reduce((d, f) => f.setDefault(d), {} as DocumentData), + ); + + const updateData = + (field: DocumentField) => + (value: ValueForFieldType) => + setDocData(field.setter(value, docData)); + + const saveData = useCallback(async () => { + setIsLoading(true); + console.log(`Creating ${docType}: `, docData); + try { + const newDocument: DocumentsByType[T] = await pb + .collection("documents") + .create({ + campaign: campaignId, + type: docType, + data: docData, + }); + await onCreate(newDocument); + dispatch({ + type: "setDocument", + doc: newDocument, + }); + } catch (e: unknown) { + if (e instanceof Error) { + setError(e.message); + } else { + setError("An unknown error occurred while creating the session."); + } + } + setIsLoading(false); + }, [campaignId, setIsLoading, setError, docData]); + + return ( + + {error && ( + // TODO: class and style for errors + {error} + )} + { + // The type checker seems to lose the types when using Object.entries here. + fields.map((field) => ( + + )) + } + + Save + + + ); +}; + +const GenericNewFormField = ({ + field, + value, + onUpdate, +}: { + field: DocumentField; + value: ValueForFieldType; + onUpdate: (value: ValueForFieldType) => void; +}) => { + return ( + + {field.name} + + + ); +}; + +const GenericNewFormInput = ({ + field, + value, + onUpdate, +}: { + field: DocumentField; + value: ValueForFieldType; + onUpdate: (value: ValueForFieldType) => void; +}) => { + switch (field.fieldType) { + case "longText": + return ( + + onUpdate((e.target.value || "") as ValueForFieldType) + } + /> + ); + case "shortText": + return ( + + onUpdate((e.target.value || "") as ValueForFieldType) + } + /> + ); + case "toggle": + return ( + + (onUpdate as (value: boolean) => void)(!!e.target.value) + } + /> + ); + } +}; diff --git a/src/components/documents/NewCampaignDocumentForm.tsx b/src/components/documents/NewCampaignDocumentForm.tsx index 5b09628..2460b2d 100644 --- a/src/components/documents/NewCampaignDocumentForm.tsx +++ b/src/components/documents/NewCampaignDocumentForm.tsx @@ -4,6 +4,7 @@ import { type DocumentType, } from "@/lib/types"; import { NewSessionForm } from "./session/NewSessionForm"; +import { GenericNewDocumentForm } from "./GenericNewDocumentForm"; /** * Renders a form for any document type depending on the relationship. @@ -21,7 +22,14 @@ export const NewCampaignDocumentForm = ({ case "session": return ; case "thread": - return ; + case "location": + return ( + + ); default: throw new Error( `Rendered NewCampaignDocumentForm with unsupported docType: ${docType}`, diff --git a/src/lib/fields.ts b/src/lib/fields.ts index 87d5578..6c91d14 100644 --- a/src/lib/fields.ts +++ b/src/lib/fields.ts @@ -1,16 +1,22 @@ -import { - type DocumentData, - type DocumentsByType, - type DocumentType, -} from "./types"; +import { type DocumentData, type DocumentType } from "./types"; export type FieldType = "shortText" | "longText" | "toggle"; -export type ValueForFieldType = { +export type ValueForFieldType = { shortText: string; longText: string; toggle: boolean; -}[T]; +}[F]; + +function defaultValue(fieldType: F): ValueForFieldType { + switch (fieldType) { + case "shortText": + case "longText": + return "" as ValueForFieldType; + case "toggle": + return false as ValueForFieldType; + } +} export type DocumentField = { name: string; @@ -20,6 +26,7 @@ export type DocumentField = { value: ValueForFieldType, doc: DocumentData, ) => DocumentData; + setDefault: (doc: DocumentData) => DocumentData; }; const simpleField = ( @@ -31,6 +38,7 @@ const simpleField = ( fieldType, getter: (doc) => doc[key] as unknown as ValueForFieldType, setter: (value, doc) => ({ ...doc, [key]: value }), + setDefault: (doc) => ({ ...doc, [key]: defaultValue(fieldType) }), }); const simpleFields = (