Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
14e43c576d
commit
5b31a252e4
@@ -62,6 +62,7 @@ export function CaseTimeTab({ caseRecord, onInvoice }: CaseTimeTabProps) {
|
||||
const [selectedFeeId, setSelectedFeeId] = useState<string>("");
|
||||
const [showNewFee, setShowNewFee] = useState(false);
|
||||
const [showImport, setShowImport] = useState(false);
|
||||
const [editEntry, setEditEntry] = useState<any | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@@ -355,6 +356,7 @@ export function CaseTimeTab({ caseRecord, onInvoice }: CaseTimeTabProps) {
|
||||
</Button>
|
||||
)}
|
||||
{!e.invoice_id && <Button variant="ghost" size="icon" onClick={() => del(e.id)}><Trash2 className="h-4 w-4 text-destructive" /></Button>}
|
||||
{!e.invoice_id && <Button variant="ghost" size="icon" onClick={() => setEditEntry(e)} title="Edit"><Pencil className="h-4 w-4" /></Button>}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -394,6 +396,14 @@ export function CaseTimeTab({ caseRecord, onInvoice }: CaseTimeTabProps) {
|
||||
clientId={caseRecord.client_id ?? null}
|
||||
onImported={load}
|
||||
/>
|
||||
|
||||
<EditTimeEntryDialog
|
||||
entry={editEntry}
|
||||
staff={staff}
|
||||
currentUserId={user?.id ?? ""}
|
||||
onClose={() => setEditEntry(null)}
|
||||
onSaved={() => { setEditEntry(null); load(); }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -406,3 +416,117 @@ function Stat({ label, value }: { label: string; value: string }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EditTimeEntryDialog({
|
||||
entry,
|
||||
staff,
|
||||
currentUserId,
|
||||
onClose,
|
||||
onSaved,
|
||||
}: {
|
||||
entry: any | null;
|
||||
staff: Array<{ id: string; full_name: string | null; email: string | null; hourly_rate: number | null }>;
|
||||
currentUserId: string;
|
||||
onClose: () => void;
|
||||
onSaved: () => void;
|
||||
}) {
|
||||
const [form, setForm] = useState({
|
||||
work_date: "",
|
||||
user_id: "",
|
||||
hours: "",
|
||||
hourly_rate: "",
|
||||
description: "",
|
||||
billable: true,
|
||||
});
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (entry) {
|
||||
setForm({
|
||||
work_date: entry.work_date ?? "",
|
||||
user_id: entry.user_id ?? currentUserId,
|
||||
hours: String(entry.hours ?? ""),
|
||||
hourly_rate: String(entry.hourly_rate ?? ""),
|
||||
description: entry.description ?? "",
|
||||
billable: !!entry.billable,
|
||||
});
|
||||
}
|
||||
}, [entry, currentUserId]);
|
||||
|
||||
const save = async () => {
|
||||
if (!entry) return;
|
||||
const hours = roundToTenth(parseFloat(form.hours || "0") || 0);
|
||||
const rate = parseFloat(form.hourly_rate || "0") || 0;
|
||||
if (hours <= 0) { toast.error("Hours must be greater than 0"); return; }
|
||||
if (!form.description.trim()) { toast.error("Description required"); return; }
|
||||
setSaving(true);
|
||||
const { error } = await supabase
|
||||
.from("time_entries")
|
||||
.update({
|
||||
work_date: form.work_date,
|
||||
user_id: form.user_id,
|
||||
hours,
|
||||
hourly_rate: rate,
|
||||
description: form.description.trim(),
|
||||
billable: form.billable,
|
||||
})
|
||||
.eq("id", entry.id);
|
||||
setSaving(false);
|
||||
if (error) { toast.error(error.message); return; }
|
||||
toast.success("Time entry updated");
|
||||
onSaved();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={!!entry} onOpenChange={(o) => { if (!o) onClose(); }}>
|
||||
<DialogContent>
|
||||
<DialogHeader><DialogTitle>Edit time entry</DialogTitle></DialogHeader>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label>Date</Label>
|
||||
<Input type="date" value={form.work_date} onChange={(e) => setForm({ ...form, work_date: e.target.value })} />
|
||||
</div>
|
||||
<div>
|
||||
<Label>Entered by</Label>
|
||||
<Select value={form.user_id} onValueChange={(v) => setForm({ ...form, user_id: v })}>
|
||||
<SelectTrigger><SelectValue placeholder="Select staff…" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{staff.map((s) => (
|
||||
<SelectItem key={s.id} value={s.id}>
|
||||
{(s.full_name || s.email || "Unknown") + (s.id === currentUserId ? " (me)" : "")}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label>Hours (decimal)</Label>
|
||||
<Input type="number" step="0.1" min="0" value={form.hours} onChange={(e) => setForm({ ...form, hours: e.target.value })} />
|
||||
</div>
|
||||
<div>
|
||||
<Label>Hourly rate ($)</Label>
|
||||
<Input type="number" step="0.01" min="0" value={form.hourly_rate} onChange={(e) => setForm({ ...form, hourly_rate: e.target.value })} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Description</Label>
|
||||
<Textarea rows={3} value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} maxLength={1000} />
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<Checkbox checked={form.billable} onCheckedChange={(c) => setForm({ ...form, billable: !!c })} />
|
||||
Billable
|
||||
</label>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={save} disabled={saving}>
|
||||
{saving && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user