WORKFLOW-APP/components/DeleteConfirmModal.tsx
Ysterbaard a79145f0d2 Fix: clearing a field in the edit modal did not persist
The modal's submit handler wrapped every optional field with
"if (value.trim()) fields.x = value.trim()". When the user
deleted the content of a field, the value was empty, the guard
returned false, and the field was OMITTED from the PATCH entirely.
PB then left the existing value in place.

Symptom: edit a field, delete its content, save. The DB still
has the old value.

Fix: always send every field. Empty string ("") clears a value
in PocketBase (verified end-to-end). Required-field validation
(surname, first_names, enterprise_no, enterprise_name) still
guards against empty required values up-front.

Also fixed the modal's React Query invalidation to target the
list pages' actual keys (["companies"], ["directors"]) in
addition to the incorrect ["pb-list"] key. This makes the
list page refetch after save.

Same fix applied to DeleteConfirmModal for consistency.
2026-08-19 12:04:58 +02:00

109 lines
4.1 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/DeleteConfirmModal.tsx
// Generic delete confirmation modal.
// Accepts any PocketBase record and calls DELETE on confirm.
// On success: invalidates list cache so the table refreshes.
import { useState } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { pbDelete } from "@/lib/api";
interface Props {
/** "ysterbaard_directors" | "ysterbaard_companies" */
collection: "ysterbaard_directors" | "ysterbaard_companies";
/** The record to delete — used only for display (id + name fields) */
record: { id: string; surname?: string; first_names?: string; enterprise_name?: string; [key: string]: unknown };
open: boolean;
onClose: () => void;
}
export default function DeleteConfirmModal({ collection, record, open, onClose }: Props) {
const qc = useQueryClient();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
if (!open) return null;
const isDirector = collection === "ysterbaard_directors";
const displayName = isDirector
? `${record.surname ?? ""}, ${record.first_names ?? ""}`.trim()
: (record.enterprise_name as string) ?? record.id;
const handleConfirm = async () => {
setError(null);
setLoading(true);
try {
await pbDelete({ collection, id: record.id });
// Match the actual query keys used by the list pages
// ("companies" / "directors"), not just "pb-list".
await qc.invalidateQueries({ queryKey: ["pb-list", collection] });
await qc.invalidateQueries({ queryKey: ["companies"] });
await qc.invalidateQueries({ queryKey: ["directors"] });
await qc.invalidateQueries({ queryKey: ["company", record.id] });
await qc.invalidateQueries({ queryKey: ["director", record.id] });
onClose();
} catch (err) {
setError(err instanceof Error ? err.message : "Delete failed. Please try again.");
} finally {
setLoading(false);
}
};
return (
<div
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
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={`Delete ${isDirector ? "director" : "company"}`}
>
<div className="bg-surface border border-border rounded-xl shadow-2xl w-full max-w-sm">
{/* Header */}
<div className="px-6 pt-6 pb-4">
<div className="flex items-start justify-between">
<div>
<h2 className="text-lg font-bold text-fg">Delete {isDirector ? "director" : "company"}?</h2>
<p className="text-sm text-muted mt-1">
This will permanently delete <strong className="text-fg">{displayName}</strong> from PocketBase. This action cannot be undone.
</p>
</div>
<button
onClick={onClose}
className="ml-4 text-muted hover:text-fg text-xl leading-none p-1 rounded transition-colors"
aria-label="Close"
>
×
</button>
</div>
</div>
{/* Body */}
<div className="px-6 pb-6">
{error && (
<div className="bg-red-500/10 border border-red-500/30 rounded-lg px-4 py-3 text-sm text-red-400 mb-4">
{error}
</div>
)}
<div className="flex gap-3">
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-2.5 rounded-lg border border-border bg-surface-warm text-sm font-semibold text-fg hover:bg-surface transition-colors"
>
Cancel
</button>
<button
type="button"
onClick={handleConfirm}
disabled={loading}
className="flex-1 px-4 py-2.5 rounded-lg bg-red-600 text-white text-sm font-bold hover:bg-red-700 transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
>
{loading ? "Deleting..." : "Yes, delete"}
</button>
</div>
</div>
</div>
</div>
);
}