WORKFLOW-APP/components/DocumentTypeManager.tsx
Dave da504c45a0 wip: registration wizard, address splits, document panel, appointments
Uncommitted work in progress at the point of starting the Kanban
project. Captures:
- Registration wizard refactor + multi-step routing
- structured physical/postal address fields on companies and directors
- appointments flow (sign + verify)
- document type manager + upload/panel for companies and directors
- status actions, approve button, action/dropdown buttons
- step2 review page

tsconfig.tsbuildinfo is now ignored (build artefact).
2026-08-17 18:33:56 +02:00

240 lines
9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
// components/DocumentTypeManager.tsx
// Modal for managing document types: rename name/description.
// Located under the filters bar on the Directors/Companies tab pages.
import { useState, useEffect, useRef } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
fetchDocumentTypes,
updateDocumentType,
deleteDocumentType,
type DocType,
} from "@/lib/api";
interface Props {
context: "director" | "company";
open: boolean;
onClose: () => void;
}
export default function DocumentTypeManager({ context, open, onClose }: Props) {
const qc = useQueryClient();
const overlayRef = useRef<HTMLDivElement>(null);
const [editingId, setEditingId] = useState<string | null>(null);
const [editName, setEditName] = useState("");
const [editDesc, setEditDesc] = useState("");
const [saving, setSaving] = useState(false);
const [deleteId, setDeleteId] = useState<string | null>(null);
const [deleting, setDeleting] = useState(false);
const [error, setError] = useState<string | null>(null);
const { data, isLoading } = useQuery({
queryKey: ["document-types", context],
queryFn: () => fetchDocumentTypes(context),
enabled: open,
});
useEffect(() => {
if (!open) {
setEditingId(null);
setDeleteId(null);
setError(null);
}
}, [open]);
if (!open) return null;
const types: DocType[] = data?.items ?? [];
const handleOverlayClick = (e: React.MouseEvent) => {
if (e.target === overlayRef.current) onClose();
};
const startEdit = (t: DocType) => {
setEditingId(t.id);
setEditName(t.name);
setEditDesc(t.description ?? "");
setError(null);
};
const handleSaveEdit = async () => {
if (!editName.trim()) return;
setSaving(true);
setError(null);
try {
await updateDocumentType(editingId!, {
name: editName.trim(),
description: editDesc.trim() || undefined,
});
await qc.invalidateQueries({ queryKey: ["document-types", context] });
setEditingId(null);
} catch (err) {
setError(err instanceof Error ? err.message : "Save failed");
} finally {
setSaving(false);
}
};
const handleDelete = async () => {
if (!deleteId) return;
setDeleting(true);
setError(null);
try {
await deleteDocumentType(deleteId);
await qc.invalidateQueries({ queryKey: ["document-types", context] });
setDeleteId(null);
} catch (err) {
setError(err instanceof Error ? err.message : "Delete failed");
} finally {
setDeleting(false);
}
};
return (
<div
ref={overlayRef}
onClick={handleOverlayClick}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
role="dialog"
aria-modal="true"
aria-label="Manage document types"
>
<div className="bg-surface border border-border rounded-xl shadow-2xl w-full max-w-lg flex flex-col max-h-[85vh]">
{/* Header */}
<div className="px-6 pt-6 pb-4 border-b border-border flex-shrink-0">
<div className="flex items-start justify-between">
<div>
<h2 className="text-lg font-bold text-fg">Document types</h2>
<p className="text-sm text-muted mt-0.5">
Rename or delete types. Docs using a deleted type will show the old type name.
</p>
</div>
<button
onClick={onClose}
className="ml-4 text-muted hover:text-fg text-xl leading-none p-1 rounded transition-colors"
>
×
</button>
</div>
</div>
{/* Body */}
<div className="flex-1 overflow-y-auto px-6 py-5">
{error && (
<div className="mb-4 bg-red-500/10 border border-red-500/30 rounded-lg px-4 py-3 text-sm text-red-400">
{error}
</div>
)}
{isLoading ? (
<p className="text-center text-muted py-8">Loading…</p>
) : types.length === 0 ? (
<p className="text-center text-muted py-8">
No {context} document types yet. Create one when you upload a document.
</p>
) : (
<ul className="space-y-2">
{types.map((t) => (
<li key={t.id} className="border border-border rounded-lg p-4">
{editingId === t.id ? (
/* Edit form */
<div className="space-y-3">
<input
type="text"
value={editName}
onChange={(e) => setEditName(e.target.value)}
className="w-full rounded border border-border bg-surface-warm px-3 py-2 text-sm text-fg focus:outline-none focus:border-accent"
placeholder="Type name"
required
/>
<input
type="text"
value={editDesc}
onChange={(e) => setEditDesc(e.target.value)}
className="w-full rounded border border-border bg-surface-warm px-3 py-2 text-sm text-fg placeholder:text-muted focus:outline-none focus:border-accent"
placeholder="Description (optional)"
/>
<div className="flex gap-2">
<button
onClick={() => setEditingId(null)}
className="flex-1 px-3 py-2 rounded border border-border bg-surface-warm text-xs font-semibold text-fg hover:bg-surface"
>
Cancel
</button>
<button
onClick={handleSaveEdit}
disabled={saving || !editName.trim()}
className="flex-1 px-3 py-2 rounded bg-accent text-accent-on text-xs font-bold hover:bg-accent-hover disabled:opacity-60"
>
{saving ? "Saving..." : "Save"}
</button>
</div>
</div>
) : deleteId === t.id ? (
/* Delete confirm */
<div className="space-y-3">
<p className="text-sm text-fg">
Delete type <strong>"{t.name}"</strong>? Existing documents will keep this name.
</p>
<div className="flex gap-2">
<button
onClick={() => setDeleteId(null)}
className="flex-1 px-3 py-2 rounded border border-border bg-surface-warm text-xs font-semibold text-fg hover:bg-surface"
>
Cancel
</button>
<button
onClick={handleDelete}
disabled={deleting}
className="flex-1 px-3 py-2 rounded bg-red-600 text-white text-xs font-bold hover:bg-red-700 disabled:opacity-60"
>
{deleting ? "Deleting..." : "Yes, delete"}
</button>
</div>
</div>
) : (
/* Display row */
<div className="flex items-center justify-between gap-3">
<div className="min-w-0">
<p className="text-sm font-semibold text-fg">{t.name}</p>
{t.description ? (
<p className="text-xs text-muted mt-0.5">{t.description}</p>
) : null}
</div>
<div className="flex items-center gap-1.5 shrink-0">
<button
onClick={() => startEdit(t)}
className="px-3 py-1.5 rounded border border-border bg-surface-warm text-xs font-semibold text-fg hover:border-accent hover:text-accent transition-colors"
>
Rename
</button>
<button
onClick={() => setDeleteId(t.id)}
className="px-3 py-1.5 rounded border border-border bg-surface-warm text-xs font-semibold text-muted hover:border-red-500 hover:text-red-500 transition-colors"
>
Delete
</button>
</div>
</div>
)}
</li>
))}
</ul>
)}
</div>
{/* Footer */}
<div className="px-6 pb-6 pt-4 border-t border-border flex-shrink-0">
<button
onClick={onClose}
className="w-full px-4 py-2.5 rounded-lg border border-border bg-surface-warm text-sm font-semibold text-fg hover:bg-surface transition-colors"
>
Done
</button>
</div>
</div>
</div>
);
}