Converts using full document state management

This commit is contained in:
2025-07-02 17:01:56 -07:00
parent 32c5c40466
commit f27432ef05
13 changed files with 468 additions and 211 deletions

View File

@@ -12,6 +12,7 @@ import { useEffect, useState } from "react";
import { Loader } from "./Loader";
import { DocumentRow } from "./documents/DocumentRow";
import { NewRelatedDocumentForm } from "./documents/NewRelatedDocumentForm";
import { useDocument } from "@/context/document/DocumentContext";
interface RelationshipListProps {
root: AnyDocument;
@@ -26,64 +27,145 @@ export function RelationshipList({
root,
relationshipType,
}: RelationshipListProps) {
const [items, setItems] = useState<AnyDocument[]>([]);
const [relationshipId, setRelationshipId] = useState<string | null>(null);
// const [items, setItems] = useState<AnyDocument[]>([]);
// const [relationshipId, setRelationshipId] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const queryClient = useQueryClient();
// const queryClient = useQueryClient();
useEffect(() => {
async function fetchItems() {
const { relationship } = await queryClient.fetchQuery({
queryKey: ["relationship", relationshipType, root.id],
staleTime: 5 * 60 * 1000, // 5 mintues
queryFn: async () => {
setLoading(true);
const relationship: Relationship = await pb
.collection("relationships")
.getFirstListItem(
`primary = "${root.id}" && type = "${relationshipType}"`,
{
expand: "secondary",
},
);
setLoading(false);
return { relationship };
},
});
setRelationshipId(relationship.id);
setItems(relationship.expand?.secondary ?? []);
}
fetchItems();
}, [root, relationshipType]);
// useEffect(() => {
// async function fetchItems() {
// const { relationship } = await queryClient.fetchQuery({
// queryKey: ["relationship", relationshipType, root.id],
// staleTime: 5 * 60 * 1000, // 5 mintues
// queryFn: async () => {
// setLoading(true);
// const relationship: Relationship = await pb
// .collection("relationships")
// .getFirstListItem(
// `primary = "${root.id}" && type = "${relationshipType}"`,
// {
// expand: "secondary",
// },
// );
//
// setLoading(false);
//
// return { relationship };
// },
// });
// setRelationshipId(relationship.id);
// setItems(relationship.expand?.secondary ?? []);
// }
//
// fetchItems();
// }, [root, relationshipType]);
// Handles creation of a new document and adds it to the relationship
// const handleCreate = async (doc: AnyDocument) => {
// setLoading(true);
// setError(null);
// try {
// // Check for existing relationship
// if (relationshipId) {
// console.debug("Adding to existing relationship", relationshipId);
// await pb.collection("relationships").update(relationshipId, {
// "+secondary": doc.id,
// });
// } else {
// console.debug("Creating new relationship");
// const relationship = await pb.collection("relationships").create({
// primary: root.id,
// secondary: [doc.id],
// type: relationshipType,
// });
// setRelationshipId(relationship.id);
// }
// queryClient.invalidateQueries({
// queryKey: ["relationship", relationshipType, root.id],
// });
// setItems((prev) => [doc, ...prev]);
// } catch (e: any) {
// setError(e?.message || "Failed to add document to relationship.");
// } finally {
// setLoading(false);
// }
// };
//
// const handleRemove = async (documentId: DocumentId) => {
// setLoading(true);
// setError(null);
//
// try {
// if (relationshipId) {
// console.debug("Removing from existing relationship", relationshipId);
// await pb.collection("relationships").update(relationshipId, {
// "secondary-": documentId,
// });
// }
// queryClient.invalidateQueries({
// queryKey: ["relationship", relationshipType, root.id],
// });
// setItems((prev) => prev.filter((item) => item.id != documentId));
// } catch (e: any) {
// setError(
// e?.message || `Failed to remove document from ${relationshipType}.`,
// );
// } finally {
// setLoading(false);
// }
// };
//
// if (loading) {
// <Loader />;
// }
const { state, dispatch } = useDocument();
if (state.status === "loading") {
return <Loader />;
}
console.info("Rendering relationship list: ", relationshipType);
const relationship = state.relationships[relationshipType];
const itemIds = relationship?.secondary ?? [];
const items = itemIds.map((id) => state.relatedDocs[id]).filter((d) => !!d);
const handleCreate = async (doc: AnyDocument) => {
setLoading(true);
setError(null);
try {
// Check for existing relationship
if (relationshipId) {
console.debug("Adding to existing relationship", relationshipId);
await pb.collection("relationships").update(relationshipId, {
"+secondary": doc.id,
if (relationship) {
console.debug("Adding to existing relationship", relationship.id);
const updatedRelationship: Relationship = await pb
.collection("relationships")
.update(relationship.id, {
"+secondary": doc.id,
});
dispatch({
type: "setRelationship",
relationship: updatedRelationship,
});
} else {
console.debug("Creating new relationship");
const relationship = await pb.collection("relationships").create({
primary: root.id,
secondary: [doc.id],
type: relationshipType,
const updatedRelationship: Relationship = await pb
.collection("relationships")
.create({
primary: root.id,
secondary: [doc.id],
type: relationshipType,
});
dispatch({
type: "setRelationship",
relationship: updatedRelationship,
});
setRelationshipId(relationship.id);
}
queryClient.invalidateQueries({
queryKey: ["relationship", relationshipType, root.id],
dispatch({
type: "setRelatedDocument",
doc,
});
setItems((prev) => [doc, ...prev]);
} catch (e: any) {
setError(e?.message || "Failed to add document to relationship.");
} finally {
@@ -96,16 +178,18 @@ export function RelationshipList({
setError(null);
try {
if (relationshipId) {
console.debug("Removing from existing relationship", relationshipId);
await pb.collection("relationships").update(relationshipId, {
"secondary-": documentId,
if (relationship) {
console.debug("Removing from existing relationship", relationship.id);
const updatedRelationship: Relationship = await pb
.collection("relationships")
.update(relationship.id, {
"secondary-": documentId,
});
dispatch({
type: "setRelationship",
relationship: updatedRelationship,
});
}
queryClient.invalidateQueries({
queryKey: ["relationship", relationshipType, root.id],
});
setItems((prev) => prev.filter((item) => item.id != documentId));
} catch (e: any) {
setError(
e?.message || `Failed to remove document from ${relationshipType}.`,
@@ -115,10 +199,6 @@ export function RelationshipList({
}
};
if (loading) {
<Loader />;
}
return (
<DocumentList
title={displayName(relationshipType)}

View File

@@ -0,0 +1,55 @@
import { RelationshipList } from "@/components/RelationshipList";
import { DocumentEditForm } from "@/components/documents/DocumentEditForm";
import { useDocument } from "@/context/document/DocumentContext";
import { displayName, relationshipsForDocument } from "@/lib/relationships";
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";
import { Link } from "@tanstack/react-router";
import { Loader } from "../Loader";
export function DocumentView() {
const { state } = useDocument();
if (state.status === "loading") {
return <Loader />;
}
const doc = state.doc;
const relationshipList = relationshipsForDocument(doc);
return (
<div key={doc.id} className="max-w-xl mx-auto py-2 px-4">
<Link
to="/document/$documentId/print"
params={{ documentId: doc.id }}
className="text-slate-400 hover:text-violet-400 text-sm underline underline-offset-2 transition-colors mb-4"
>
Print
</Link>
<DocumentEditForm document={doc} />
<TabGroup>
<TabList className="flex flex-row flex-wrap gap-1 mt-2">
{relationshipList.map((relationshipType) => (
<Tab
key={relationshipType}
className="px-3 py-2 rounded bg-slate-800 text-slate-100 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-violet-500 data-selected:bg-violet-900 data-selected:border-violet-700"
>
{displayName(relationshipType)}
</Tab>
))}
</TabList>
<TabPanels>
{relationshipList.map((relationshipType) => (
<TabPanel key={relationshipType}>
<RelationshipList
key={relationshipType}
root={doc}
relationshipType={relationshipType}
/>
</TabPanel>
))}
</TabPanels>
</TabGroup>
</div>
);
}

View File

@@ -45,7 +45,7 @@ export const NewSecretForm = ({
return (
<BaseForm
title="Create new treasure"
title="Create new secret"
isLoading={adding || !newSecret.trim()}
onSubmit={handleSubmit}
error={error}
@@ -53,7 +53,7 @@ export const NewSecretForm = ({
<SingleLineInput
value={newSecret}
onChange={setNewSecret}
placeholder="Treasure description"
placeholder="Secret description"
/>
}
/>

View File

@@ -1,10 +1,8 @@
import { AutoSaveTextarea } from "@/components/AutoSaveTextarea";
import { useCache } from "@/context/cache/CacheContext";
import { pb } from "@/lib/pocketbase";
import type { Session } from "@/lib/types";
export const SessionEditForm = ({ session }: { session: Session }) => {
const cache = useCache();
async function saveStrongStart(strongStart: string) {
const freshRecord: Session = await pb
.collection("documents")
@@ -14,7 +12,6 @@ export const SessionEditForm = ({ session }: { session: Session }) => {
strongStart,
},
});
cache.set(freshRecord);
}
return (