Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
a0b6c5cae8
commit
4516fd400a
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user