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:24 +00:00
co-authored by renee-png
parent a0b6c5cae8
commit 4516fd400a
+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>