Added status edit/deletion

X-Lovable-Edit-ID: edt-b3e5359c-9eb1-423a-a3dd-77f058bb572a
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-18 18:24:31 +00:00
co-authored by renee-png
2 changed files with 43 additions and 20 deletions
+41 -16
View File
@@ -6,7 +6,7 @@ import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { supabase } from "@/integrations/supabase/client";
import { useAuth } from "@/lib/auth";
import { Plus, Trash2, Loader2, FileDown } from "lucide-react";
import { Plus, Trash2, Loader2, FileDown, Pencil } from "lucide-react";
import { formatDateTime } from "@/lib/format";
import { toast } from "sonner";
import { downloadStatusReport } from "@/lib/status-pdf";
@@ -16,11 +16,18 @@ export function CaseStatusTab({ caseId, caseLabel }: { caseId: string; caseLabel
const [items, setItems] = useState<any[]>([]);
const [showForm, setShowForm] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null);
const nowLocal = () => {
const d = new Date();
d.setMinutes(d.getMinutes() - d.getTimezoneOffset());
return d.toISOString().slice(0, 16);
};
const toLocalInput = (iso: string) => {
const d = new Date(iso);
if (isNaN(d.getTime())) return nowLocal();
d.setMinutes(d.getMinutes() - d.getTimezoneOffset());
return d.toISOString().slice(0, 16);
};
const [form, setForm] = useState({ title: nowLocal(), body: "" });
const load = async () => {
@@ -41,19 +48,29 @@ export function CaseStatusTab({ caseId, caseLabel }: { caseId: string; caseLabel
useEffect(() => { load(); }, [caseId]);
const resetForm = () => {
setEditingId(null);
setShowForm(false);
setForm({ title: nowLocal(), body: "" });
};
const startEdit = (item: any) => {
setEditingId(item.id);
setForm({ title: toLocalInput(item.title), body: item.body });
setShowForm(true);
};
const submit = async (e: React.FormEvent) => {
e.preventDefault();
if (!form.title.trim() || !form.body.trim()) { toast.error("Date/time and details required"); return; }
setSubmitting(true);
const { error } = await supabase.from("status_updates").insert({
case_id: caseId,
title: form.title.trim(),
body: form.body.trim(),
created_by: user?.id,
});
const payload = { title: form.title.trim(), body: form.body.trim() };
const { error } = editingId
? await supabase.from("status_updates").update(payload).eq("id", editingId)
: await supabase.from("status_updates").insert({ case_id: caseId, created_by: user?.id, ...payload });
setSubmitting(false);
if (error) toast.error(error.message);
else { toast.success("Update logged"); setShowForm(false); setForm({ title: nowLocal(), body: "" }); load(); }
else { toast.success(editingId ? "Update saved" : "Update logged"); resetForm(); load(); }
};
const del = async (item: any) => {
@@ -83,7 +100,7 @@ export function CaseStatusTab({ caseId, caseLabel }: { caseId: string; caseLabel
<Button variant="outline" onClick={exportPdf} disabled={items.length === 0}>
<FileDown className="h-4 w-4 mr-2" /> Export PDF
</Button>
<Button onClick={() => setShowForm((s) => !s)}>
<Button onClick={() => (showForm ? resetForm() : setShowForm(true))}>
<Plus className="h-4 w-4 mr-2" /> {showForm ? "Cancel" : "Add status update"}
</Button>
</div>
@@ -105,10 +122,13 @@ export function CaseStatusTab({ caseId, caseLabel }: { caseId: string; caseLabel
<Label>Details</Label>
<Textarea rows={4} value={form.body} onChange={(e) => setForm({ ...form, body: e.target.value })} required maxLength={5000} />
</div>
<div className="flex justify-end">
<div className="flex justify-end gap-2">
{editingId && (
<Button type="button" variant="outline" onClick={resetForm}>Cancel</Button>
)}
<Button type="submit" disabled={submitting}>
{submitting && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
Log update
{editingId ? "Save changes" : "Log update"}
</Button>
</div>
</form>
@@ -126,7 +146,7 @@ export function CaseStatusTab({ caseId, caseLabel }: { caseId: string; caseLabel
<div className="relative">
{items.map((u) => {
const canDelete = isAdmin || u.created_by === user?.id;
const canEdit = isAdmin || u.created_by === user?.id;
return (
<div key={u.id} className="relative pl-8 pb-6">
<div className="absolute left-3 top-2 w-px h-full bg-border" />
@@ -140,10 +160,15 @@ export function CaseStatusTab({ caseId, caseLabel }: { caseId: string; caseLabel
Logged by {u.user?.full_name || u.user?.email || "Unknown"} · {formatDateTime(u.created_at)}
</div>
</div>
{canDelete && (
<Button variant="ghost" size="icon" onClick={() => del(u)}>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
{canEdit && (
<div className="flex items-center gap-1">
<Button variant="ghost" size="icon" onClick={() => startEdit(u)}>
<Pencil className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" onClick={() => del(u)}>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</div>
)}
</div>
<p className="text-sm whitespace-pre-wrap">{u.body}</p>
+2 -4
View File
@@ -98,7 +98,6 @@ export function generateStatusReportPdf(opts: StatusReportOptions): jsPDF {
items.map((e) => [
fmtDateTime(e.title),
e.body,
`${initials(e.author_name, e.author_email)}\n${fmtDateTime(e.created_at)}`,
]);
for (const g of groups) {
@@ -125,7 +124,7 @@ export function generateStatusReportPdf(opts: StatusReportOptions): jsPDF {
autoTable(doc, {
startY: y + 6,
head: [["Event date", "Details", "Logged by"]],
head: [["Date", "Details"]],
body: rowsForGroup(g.items),
margin: { left: margin, right: margin },
styles: {
@@ -145,9 +144,8 @@ export function generateStatusReportPdf(opts: StatusReportOptions): jsPDF {
},
alternateRowStyles: { fillColor: [248, 248, 246] },
columnStyles: {
0: { cellWidth: 96 },
0: { cellWidth: 110 },
1: { cellWidth: "auto" },
2: { cellWidth: 96, fontSize: 8.5, textColor: 80 },
},
didDrawPage: () => {
// Footer with page number