WIP: Linking in document list. Working on copying relationships to new sessions

This commit is contained in:
2025-06-27 17:52:57 -07:00
parent 93536b0ac2
commit 611eaca5b6
15 changed files with 281 additions and 52 deletions

View File

@@ -1,4 +1,4 @@
import type { Document } from "@/lib/types";
import type { Document, DocumentId } from "@/lib/types";
import {
Dialog,
DialogPanel,
@@ -6,7 +6,8 @@ import {
Transition,
TransitionChild,
} from "@headlessui/react";
import { Fragment, useState } from "react";
import { Fragment, useCallback, useState } from "react";
import * as Icons from "@/components/Icons.tsx";
type Props<T extends Document> = {
title: React.ReactNode;
@@ -14,6 +15,7 @@ type Props<T extends Document> = {
items: T[];
renderRow: (item: T) => React.ReactNode;
newItemForm: (onSubmit: () => void) => React.ReactNode;
removeItem: (itemId: DocumentId) => void;
};
/**
@@ -30,8 +32,15 @@ export function DocumentList<T extends Document>({
items,
renderRow,
newItemForm,
removeItem,
}: Props<T>) {
const [open, setOpen] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const toggleEditMode = useCallback(
() => setIsEditing((x) => !x),
[setIsEditing],
);
// Handles closing the dialog after form submission
const handleFormSubmit = (): void => {
@@ -42,35 +51,50 @@ export function DocumentList<T extends Document>({
<section className="w-full max-w-2xl mx-auto">
<div className="flex items-center justify-between my-4">
<h2 className="text-xl font-bold text-slate-100">{title}</h2>
<button
type="button"
className="inline-flex items-center justify-center rounded-full bg-violet-600 hover:bg-violet-700 text-white w-8 h-8 focus:outline-none focus:ring-2 focus:ring-violet-400"
aria-label="Add new item"
onClick={() => setOpen(true)}
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
strokeWidth={2}
viewBox="0 0 24 24"
aria-hidden="true"
<div className="flex gap-2">
{isEditing && (
<button
type="button"
className="inline-flex items-center justify-center rounded-full bg-violet-600 hover:bg-violet-700 text-white w-8 h-8 focus:outline-none focus:ring-2 focus:ring-violet-400"
aria-label="Add new item"
onClick={() => setOpen(true)}
>
<Icons.Cross />
</button>
)}
<button
type="button"
className="inline-flex items-center justify-center rounded-full bg-violet-600 hover:bg-violet-700 text-white w-8 h-8 focus:outline-none focus:ring-2 focus:ring-violet-400"
aria-label={isEditing ? "Exit edit mode" : "Enter edit mode"}
onClick={toggleEditMode}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 4v16m8-8H4"
/>
</svg>
</button>
{isEditing ? <Icons.Done /> : <Icons.Edit />}
</button>
</div>
</div>
{error && (
<div className="bg-red-900 rounded p-4 text-slate-100">{error}</div>
)}
<ul className="space-y-2">
{items.map((item) => (
<li key={item.id} className="bg-slate-800 rounded p-4 text-slate-100">
{renderRow(item)}
<li
key={item.id}
className="bg-slate-800 rounded p-4 text-slate-100 flex flex-row justify-between items-center"
>
<div>{renderRow(item)}</div>
{isEditing && (
<div>
<button
type="button"
className="inline-flex items-center justify-center rounded-full bg-violet-600 hover:bg-violet-700 text-white w-8 h-8 focus:outline-none focus:ring-2 focus:ring-violet-400"
aria-label="Remove item"
onClick={() => removeItem(item.id)}
>
<Icons.Remove />
</button>
</div>
)}
</li>
))}
</ul>