The picker in the bell now governs both lists, so note_subscriptions is renamed to colleague_subscriptions -- a name that only mentions notes would mislead the next reader. Reading and writing a draft now reach differently far. hire_drafts_owner (for all) is split into four policies: select lets in your own drafts and those of the people you added, while insert/update/delete stay with the owner. A draft is unfinished work with no lock and no history; two people writing into the same row would overwrite each other silently. That split forces a change in the actions: a policy does not reject a write, it lets it hit no rows. saveHireDraft and deleteHireDraft now read the row count instead of reporting success over a row that never changed. The card shows a foreign draft with its author and without Fortsetzen or Loeschen -- offering a button that reliably ends in a database error is a promise without cover. check-schema-types.mjs learns `alter table ... rename to`; without it the drift check reports one rename as two errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
88 lines
3.6 KiB
TypeScript
88 lines
3.6 KiB
TypeScript
"use client";
|
||
|
||
import { Trash2 } from "lucide-react";
|
||
import { useRouter } from "next/navigation";
|
||
import { useTransition } from "react";
|
||
import { deleteHireDraft } from "@/actions/hireDrafts";
|
||
import { useHireWizard } from "@/components/hire/HireWizardContext";
|
||
import { useToast } from "@/components/ui/Toast";
|
||
import type { Entwurf } from "@/lib/entwuerfe";
|
||
import { fmtDate } from "@/lib/format";
|
||
|
||
// Angefangene Neueinstellungen — die eigenen und, wer in der Glocke
|
||
// hinzugewählt ist, auch dessen.
|
||
//
|
||
// Fremde Entwürfe stehen hier zum Ansehen. Fortsetzen und Löschen fehlen bei
|
||
// ihnen nicht aus Vorsicht, sondern weil die Datenbank sie ohnehin abwiese
|
||
// (hire_drafts_update, hire_drafts_delete): eine Schaltfläche anzubieten, die
|
||
// verlässlich in eine Fehlermeldung führt, wäre ein Versprechen ohne Deckung.
|
||
|
||
export function DraftsCard({ drafts }: { drafts: Entwurf[] }) {
|
||
const { openWizard } = useHireWizard();
|
||
const { showToast } = useToast();
|
||
const router = useRouter();
|
||
const [pending, startTransition] = useTransition();
|
||
|
||
if (drafts.length === 0) return null;
|
||
|
||
function handleDelete(id: string) {
|
||
startTransition(async () => {
|
||
const result = await deleteHireDraft(id);
|
||
if (result.success) {
|
||
showToast("Entwurf gelöscht.");
|
||
router.refresh();
|
||
} else {
|
||
showToast(result.error ?? "Fehler beim Löschen.", "error");
|
||
}
|
||
});
|
||
}
|
||
|
||
return (
|
||
<div className="rounded border border-border bg-white p-4">
|
||
<h2 className="mb-3 text-sm font-bold text-ink">Entwürfe – Neueinstellungen</h2>
|
||
<ul className="flex flex-col divide-y divide-border">
|
||
{drafts.map((d) => {
|
||
const firstName = typeof d.payload.firstName === "string" ? d.payload.firstName : "";
|
||
const lastName = typeof d.payload.lastName === "string" ? d.payload.lastName : "";
|
||
const name = [firstName, lastName].filter(Boolean).join(" ") || "Ohne Namen";
|
||
return (
|
||
<li key={d.id} className="flex items-center justify-between gap-3 py-2 text-sm">
|
||
<div>
|
||
<span className="font-semibold text-ink">{name}</span>
|
||
<span className="ml-2 text-xs text-ink-muted">Gespeichert am {fmtDate(d.updated_at)}</span>
|
||
{/* Nur bei fremden Entwürfen. Der eigene Name stünde sonst an
|
||
jeder Zeile und sagte nichts. */}
|
||
{d.autor && <span className="ml-2 text-xs text-ink-muted">von {d.autor}</span>}
|
||
</div>
|
||
{d.vonMir ? (
|
||
<div className="flex items-center gap-3">
|
||
<button
|
||
type="button"
|
||
onClick={() => openWizard({ draftId: d.id })}
|
||
className="text-xs font-semibold text-brand-700 hover:underline"
|
||
>
|
||
Fortsetzen
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => handleDelete(d.id)}
|
||
disabled={pending}
|
||
aria-label="Entwurf löschen"
|
||
className="text-ink-muted hover:text-danger-solid"
|
||
>
|
||
<Trash2 className="h-4 w-4" />
|
||
</button>
|
||
</div>
|
||
) : (
|
||
// Warum hier nichts steht, ist sonst nicht zu erraten: die
|
||
// Zeile sähe aus wie eine, an der die Schaltflächen fehlen.
|
||
<span className="shrink-0 text-xs text-ink-muted">Nur zur Ansicht</span>
|
||
)}
|
||
</li>
|
||
);
|
||
})}
|
||
</ul>
|
||
</div>
|
||
);
|
||
}
|