Creates the generic new-document form
This commit is contained in:
@@ -28,14 +28,6 @@ export const DocumentEditForm = ({ document }: { document: AnyDocument }) => {
|
||||
case "treasure":
|
||||
return <TreasureEditForm treasure={document} />;
|
||||
case "thread":
|
||||
return (
|
||||
<GenericEditForm
|
||||
doc={document}
|
||||
fields={{
|
||||
text: "multiline",
|
||||
resolved: "checkbox",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return <GenericEditForm doc={document} />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
// <Link to={to} search={search} className={className}>
|
||||
// {children}
|
||||
// </Link>
|
||||
// );
|
||||
|
||||
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 (
|
||||
<Link to={to} search={search} className={className}>
|
||||
<Link to={to} className={className}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -56,6 +56,7 @@ const GenericEditFormField = <T extends AnyDocument>({
|
||||
multiline={true}
|
||||
value={field.getter(data) as string}
|
||||
onSave={saveField}
|
||||
id={field.name}
|
||||
/>
|
||||
);
|
||||
case "shortText":
|
||||
@@ -64,6 +65,7 @@ const GenericEditFormField = <T extends AnyDocument>({
|
||||
multiline={false}
|
||||
value={field.getter(data) as string}
|
||||
onSave={saveField}
|
||||
id={field.name}
|
||||
/>
|
||||
);
|
||||
case "toggle":
|
||||
@@ -73,6 +75,7 @@ const GenericEditFormField = <T extends AnyDocument>({
|
||||
checked={!!field.getter(data)}
|
||||
onChange={(e) => saveField(!!e.target.value)}
|
||||
className="accent-emerald-500 w-5 h-5"
|
||||
id={field.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
152
src/components/documents/GenericNewDocumentForm.tsx
Normal file
152
src/components/documents/GenericNewDocumentForm.tsx
Normal file
@@ -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<T extends DocumentType> = {
|
||||
docType: T;
|
||||
campaignId: CampaignId;
|
||||
onCreate: (doc: DocumentsByType[T]) => Promise<void>;
|
||||
};
|
||||
|
||||
export const GenericNewDocumentForm = <T extends DocumentType>({
|
||||
docType,
|
||||
campaignId,
|
||||
onCreate,
|
||||
}: Props<T>) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { dispatch } = useDocumentCache();
|
||||
|
||||
const fields = getFieldsForType(docType);
|
||||
|
||||
const [docData, setDocData] = useState<DocumentData<T>>(
|
||||
fields.reduce((d, f) => f.setDefault(d), {} as DocumentData<T>),
|
||||
);
|
||||
|
||||
const updateData =
|
||||
<F extends FieldType>(field: DocumentField<T, F>) =>
|
||||
(value: ValueForFieldType<F>) =>
|
||||
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 (
|
||||
<div className="">
|
||||
{error && (
|
||||
// TODO: class and style for errors
|
||||
<div className="error">{error}</div>
|
||||
)}
|
||||
{
|
||||
// The type checker seems to lose the types when using Object.entries here.
|
||||
fields.map((field) => (
|
||||
<GenericNewFormField
|
||||
field={field}
|
||||
value={field.getter(docData)}
|
||||
onUpdate={updateData(field)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
<button disabled={isLoading} onClick={saveData}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const GenericNewFormField = <T extends DocumentType, F extends FieldType>({
|
||||
field,
|
||||
value,
|
||||
onUpdate,
|
||||
}: {
|
||||
field: DocumentField<T, F>;
|
||||
value: ValueForFieldType<F>;
|
||||
onUpdate: (value: ValueForFieldType<F>) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={field.name}>{field.name}</label>
|
||||
<GenericNewFormInput field={field} value={value} onUpdate={onUpdate} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const GenericNewFormInput = <T extends DocumentType, F extends FieldType>({
|
||||
field,
|
||||
value,
|
||||
onUpdate,
|
||||
}: {
|
||||
field: DocumentField<T, F>;
|
||||
value: ValueForFieldType<F>;
|
||||
onUpdate: (value: ValueForFieldType<F>) => void;
|
||||
}) => {
|
||||
switch (field.fieldType) {
|
||||
case "longText":
|
||||
return (
|
||||
<textarea
|
||||
value={value as string}
|
||||
onChange={(e) =>
|
||||
onUpdate((e.target.value || "") as ValueForFieldType<F>)
|
||||
}
|
||||
/>
|
||||
);
|
||||
case "shortText":
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
value={value as string}
|
||||
onChange={(e) =>
|
||||
onUpdate((e.target.value || "") as ValueForFieldType<F>)
|
||||
}
|
||||
/>
|
||||
);
|
||||
case "toggle":
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value as boolean}
|
||||
onChange={(e) =>
|
||||
(onUpdate as (value: boolean) => void)(!!e.target.value)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -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 <NewSessionForm campaignId={campaignId} onCreate={onCreate} />;
|
||||
case "thread":
|
||||
return <NewThreadForm campaignId={campaignId} onCreate={onCreate} />;
|
||||
case "location":
|
||||
return (
|
||||
<GenericNewDocumentForm
|
||||
docType={docType}
|
||||
campaignId={campaignId}
|
||||
onCreate={onCreate}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
throw new Error(
|
||||
`Rendered NewCampaignDocumentForm with unsupported docType: ${docType}`,
|
||||
|
||||
Reference in New Issue
Block a user