Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
6fa8e0fb63
commit
b11e12f348
@@ -177,39 +177,27 @@ export function GenerateInvoiceDialog({ open, onOpenChange, clientId, clientName
|
||||
<DialogTitle>Generate invoice — {clientName}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{loading ? (
|
||||
<div className="py-8 text-center text-muted-foreground">Loading unbilled work…</div>
|
||||
) : cases.length === 0 ? (
|
||||
<div className="py-8 text-center text-muted-foreground text-sm">
|
||||
No unbilled time or expenses across this client's cases.
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label className="text-xs uppercase tracking-wider text-muted-foreground">Cases to bill</Label>
|
||||
<div className="mt-2 border rounded-md divide-y max-h-[260px] overflow-auto">
|
||||
{cases.map((c) => (
|
||||
<label key={c.id} className="flex items-start gap-3 p-3 cursor-pointer hover:bg-muted/40">
|
||||
<Checkbox
|
||||
checked={!!selected[c.id]}
|
||||
onCheckedChange={(v) => setSelected((p) => ({ ...p, [c.id]: !!v }))}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="font-medium text-sm truncate">{c.title}</span>
|
||||
<span className="text-sm tabular-nums font-medium">{formatCurrency(c.total)}</span>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{c.case_number} · {c.timeCount} time entries ({formatCurrency(c.timeAmount)}) · {c.expenseCount} expenses ({formatCurrency(c.expenseAmount)})
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
<div className="space-y-4">
|
||||
<label className="flex items-start gap-3 p-3 rounded-md border cursor-pointer hover:bg-muted/40">
|
||||
<Checkbox
|
||||
checked={isRetainer}
|
||||
onCheckedChange={(v) => setIsRetainer(!!v)}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<div className="text-sm font-medium">Retainer / trust deposit invoice</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
When paid, the amount is automatically deposited into this client's trust account.
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
{isRetainer ? (
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<Label>Retainer amount</Label>
|
||||
<Input id="retainer-amount" type="number" step="0.01" placeholder="0.00" />
|
||||
</div>
|
||||
<div>
|
||||
<Label>Tax rate (%)</Label>
|
||||
<Input type="number" step="0.01" value={taxPct} onChange={(e) => setTaxPct(e.target.value)} />
|
||||
@@ -218,27 +206,73 @@ export function GenerateInvoiceDialog({ open, onOpenChange, clientId, clientName
|
||||
<Label>Due in (days)</Label>
|
||||
<Input type="number" value={dueDays} onChange={(e) => setDueDays(e.target.value)} />
|
||||
</div>
|
||||
<div className="col-span-1 flex items-end justify-end">
|
||||
<div className="text-right">
|
||||
<div className="text-[10px] uppercase tracking-wider text-muted-foreground">Estimated total</div>
|
||||
<div className="font-serif text-xl">{formatCurrency(total)}</div>
|
||||
{tax > 0 && <div className="text-[11px] text-muted-foreground">{formatCurrency(subtotal)} + {formatCurrency(tax)} tax</div>}
|
||||
</div>
|
||||
) : loading ? (
|
||||
<div className="py-8 text-center text-muted-foreground">Loading unbilled work…</div>
|
||||
) : cases.length === 0 ? (
|
||||
<div className="py-8 text-center text-muted-foreground text-sm">
|
||||
No unbilled time or expenses across this client's cases.
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<Label className="text-xs uppercase tracking-wider text-muted-foreground">Cases to bill</Label>
|
||||
<div className="mt-2 border rounded-md divide-y max-h-[260px] overflow-auto">
|
||||
{cases.map((c) => (
|
||||
<label key={c.id} className="flex items-start gap-3 p-3 cursor-pointer hover:bg-muted/40">
|
||||
<Checkbox
|
||||
checked={!!selected[c.id]}
|
||||
onCheckedChange={(v) => setSelected((p) => ({ ...p, [c.id]: !!v }))}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="font-medium text-sm truncate">{c.title}</span>
|
||||
<span className="text-sm tabular-nums font-medium">{formatCurrency(c.total)}</span>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{c.case_number} · {c.timeCount} time entries ({formatCurrency(c.timeAmount)}) · {c.expenseCount} expenses ({formatCurrency(c.expenseAmount)})
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>Notes (optional)</Label>
|
||||
<Textarea rows={2} value={notes} onChange={(e) => setNotes(e.target.value)} placeholder="Payment terms, thank-you message…" />
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<Label>Tax rate (%)</Label>
|
||||
<Input type="number" step="0.01" value={taxPct} onChange={(e) => setTaxPct(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<Label>Due in (days)</Label>
|
||||
<Input type="number" value={dueDays} onChange={(e) => setDueDays(e.target.value)} />
|
||||
</div>
|
||||
<div className="col-span-1 flex items-end justify-end">
|
||||
<div className="text-right">
|
||||
<div className="text-[10px] uppercase tracking-wider text-muted-foreground">Estimated total</div>
|
||||
<div className="font-serif text-xl">{formatCurrency(total)}</div>
|
||||
{tax > 0 && <div className="text-[11px] text-muted-foreground">{formatCurrency(subtotal)} + {formatCurrency(tax)} tax</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Label>Notes (optional)</Label>
|
||||
<Textarea rows={2} value={notes} onChange={(e) => setNotes(e.target.value)} placeholder="Payment terms, thank-you message…" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>Cancel</Button>
|
||||
<Button onClick={submit} disabled={saving || cases.length === 0 || selectedIds.length === 0}>
|
||||
<Button
|
||||
onClick={submit}
|
||||
disabled={saving || (!isRetainer && (cases.length === 0 || selectedIds.length === 0))}
|
||||
>
|
||||
{saving ? <Loader2 className="h-4 w-4 mr-2 animate-spin" /> : <FilePlus className="h-4 w-4 mr-2" />}
|
||||
Generate draft invoice
|
||||
{isRetainer ? "Create retainer invoice" : "Generate draft invoice"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user