Threads done with generic forms.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { makeDocumentPath, useDocumentPath } from "@/lib/documentPath";
|
||||
import { makeDocumentPath } from "@/lib/documentPath";
|
||||
import type { DocumentId } from "@/lib/types";
|
||||
import { Link, useParams, useSearch } from "@tanstack/react-router";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
export type Props = React.PropsWithChildren<{
|
||||
childDocId: DocumentId;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
type DocumentField,
|
||||
type FieldType,
|
||||
} from "@/lib/fields";
|
||||
import { ToggleInput } from "../form/ToggleInput";
|
||||
|
||||
export type GenericFieldType = "multiline" | "singleline" | "checkbox";
|
||||
|
||||
@@ -70,12 +71,11 @@ const GenericEditFormField = <T extends AnyDocument>({
|
||||
);
|
||||
case "toggle":
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!field.getter(data)}
|
||||
onChange={(e) => saveField(!!e.target.value)}
|
||||
className="accent-emerald-500 w-5 h-5"
|
||||
id={field.name}
|
||||
<ToggleInput
|
||||
label={field.name}
|
||||
value={!!field.getter(data)}
|
||||
onChange={saveField}
|
||||
placeholder={field.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AutoSaveTextarea } from "@/components/AutoSaveTextarea";
|
||||
import { useDocumentCache } from "@/context/document/hooks";
|
||||
import { DocumentTypeLabel } from "@/lib/documents";
|
||||
import {
|
||||
getFieldsForType,
|
||||
type DocumentField,
|
||||
@@ -14,6 +14,10 @@ import {
|
||||
type DocumentType,
|
||||
} from "@/lib/types";
|
||||
import { useCallback, useState } from "react";
|
||||
import { BaseForm } from "../form/BaseForm";
|
||||
import { MultiLineInput } from "../form/MultiLineInput";
|
||||
import { SingleLineInput } from "../form/SingleLineInput";
|
||||
import { ToggleInput } from "../form/ToggleInput";
|
||||
|
||||
export type GenericFieldType = "multiline" | "singleline" | "checkbox";
|
||||
|
||||
@@ -69,83 +73,68 @@ export const GenericNewDocumentForm = <T extends DocumentType>({
|
||||
setIsLoading(false);
|
||||
}, [campaignId, setIsLoading, setError, docData]);
|
||||
|
||||
// TODO: display name for docType
|
||||
return (
|
||||
<div className="">
|
||||
{error && (
|
||||
// TODO: class and style for errors
|
||||
<div className="error">{error}</div>
|
||||
)}
|
||||
{
|
||||
<BaseForm
|
||||
title={`Create new ${DocumentTypeLabel[docType]}`}
|
||||
onSubmit={saveData}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
content={
|
||||
// The type checker seems to lose the types when using Object.entries here.
|
||||
fields.map((field) => (
|
||||
<GenericNewFormField
|
||||
field={field}
|
||||
value={field.getter(docData)}
|
||||
isLoading={isLoading}
|
||||
onUpdate={updateData(field)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
<button disabled={isLoading} onClick={saveData}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const GenericNewFormField = <T extends DocumentType, F extends FieldType>({
|
||||
field,
|
||||
value,
|
||||
isLoading,
|
||||
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>;
|
||||
isLoading: boolean;
|
||||
onUpdate: (value: ValueForFieldType<F>) => void;
|
||||
}) => {
|
||||
switch (field.fieldType) {
|
||||
case "longText":
|
||||
return (
|
||||
<textarea
|
||||
<MultiLineInput
|
||||
label={field.name}
|
||||
value={value as string}
|
||||
onChange={(e) =>
|
||||
onUpdate((e.target.value || "") as ValueForFieldType<F>)
|
||||
}
|
||||
onChange={onUpdate as (v: string) => void}
|
||||
disabled={isLoading}
|
||||
placeholder={field.name}
|
||||
/>
|
||||
);
|
||||
case "shortText":
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
<SingleLineInput
|
||||
label={field.name}
|
||||
value={value as string}
|
||||
onChange={(e) =>
|
||||
onUpdate((e.target.value || "") as ValueForFieldType<F>)
|
||||
}
|
||||
onChange={onUpdate as (v: string) => void}
|
||||
disabled={isLoading}
|
||||
placeholder={field.name}
|
||||
/>
|
||||
);
|
||||
case "toggle":
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value as boolean}
|
||||
onChange={(e) =>
|
||||
(onUpdate as (value: boolean) => void)(!!e.target.value)
|
||||
}
|
||||
<ToggleInput
|
||||
label={field.name}
|
||||
value={value as boolean}
|
||||
onChange={onUpdate as (v: boolean) => void}
|
||||
disabled={isLoading}
|
||||
placeholder={field.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { BaseForm } from "@/components/form/BaseForm";
|
||||
import { SingleLineInput } from "@/components/form/SingleLineInput";
|
||||
import { useDocumentCache } from "@/context/document/hooks";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import type {
|
||||
@@ -8,7 +6,8 @@ import type {
|
||||
Relationship,
|
||||
Session,
|
||||
} from "@/lib/types";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { GenericNewDocumentForm } from "../GenericNewDocumentForm";
|
||||
|
||||
export type Props = {
|
||||
campaignId: CampaignId;
|
||||
@@ -16,14 +15,10 @@ export type Props = {
|
||||
};
|
||||
|
||||
export const NewSessionForm = ({ campaignId, onCreate }: Props) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [name, setName] = useState<string>("");
|
||||
const { dispatch } = useDocumentCache();
|
||||
|
||||
const createNewSession = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const createSessionRelations = useCallback(
|
||||
async (newSession: Session) => {
|
||||
// Check for a previous session
|
||||
const prevSession = await pb
|
||||
.collection("documents")
|
||||
@@ -31,15 +26,6 @@ export const NewSessionForm = ({ campaignId, onCreate }: Props) => {
|
||||
sort: "-created",
|
||||
});
|
||||
|
||||
const newSession: Session = await pb.collection("documents").create({
|
||||
campaign: campaignId,
|
||||
type: "session",
|
||||
data: {
|
||||
name,
|
||||
strongStart: "",
|
||||
},
|
||||
});
|
||||
|
||||
// If any relations, then copy things over
|
||||
if (prevSession) {
|
||||
const prevRelations = await pb
|
||||
@@ -56,38 +42,16 @@ export const NewSessionForm = ({ campaignId, onCreate }: Props) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
dispatch({
|
||||
type: "setDocument",
|
||||
doc: newSession,
|
||||
});
|
||||
await onCreate?.(newSession);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
setError("An unknown error occurred while creating the session.");
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, [campaignId, name, dispatch, setIsLoading, setError]);
|
||||
await onCreate(newSession);
|
||||
},
|
||||
[campaignId, dispatch],
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseForm
|
||||
title="Create new session"
|
||||
onSubmit={createNewSession}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
content={
|
||||
<>
|
||||
<SingleLineInput
|
||||
label="Name"
|
||||
value={name}
|
||||
onChange={setName}
|
||||
disabled={isLoading}
|
||||
placeholder="Enter session name"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
<GenericNewDocumentForm
|
||||
docType="session"
|
||||
campaignId={campaignId}
|
||||
onCreate={createSessionRelations}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user