Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
970a94c7d5
commit
d59f0378f0
@@ -4,17 +4,15 @@ import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { supabase } from "@/integrations/supabase/client";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import { FilePlus, Loader2 } from "lucide-react";
|
||||
import { FilePlus } from "lucide-react";
|
||||
import { formatCurrency, formatDate, statusBadgeClass } from "@/lib/format";
|
||||
import { toast } from "sonner";
|
||||
import { GenerateInvoiceDialog } from "@/components/invoices/generate-invoice-dialog";
|
||||
|
||||
export function CaseInvoicesTab({ caseRecord }: { caseRecord: any }) {
|
||||
const { user } = useAuth();
|
||||
const [invoices, setInvoices] = useState<any[]>([]);
|
||||
const [unbilledTime, setUnbilledTime] = useState<any[]>([]);
|
||||
const [unbilledExpenses, setUnbilledExpenses] = useState<any[]>([]);
|
||||
const [generating, setGenerating] = useState(false);
|
||||
const [genOpen, setGenOpen] = useState(false);
|
||||
|
||||
const load = async () => {
|
||||
const [{ data: invs }, { data: t }, { data: ex }] = await Promise.all([
|
||||
@@ -33,48 +31,19 @@ export function CaseInvoicesTab({ caseRecord }: { caseRecord: any }) {
|
||||
const expensesTotal = unbilledExpenses.reduce((s, e) => s + Number(e.amount), 0);
|
||||
const subtotal = timeTotal + expensesTotal;
|
||||
|
||||
const generate = async () => {
|
||||
if (subtotal <= 0) { toast.error("Nothing to invoice"); return; }
|
||||
setGenerating(true);
|
||||
const yr = new Date().getFullYear();
|
||||
const num = `INV-${yr}-${Math.floor(1000 + Math.random() * 9000)}`;
|
||||
const due = new Date(); due.setDate(due.getDate() + 30);
|
||||
const { data: inv, error } = await supabase.from("invoices").insert({
|
||||
invoice_number: num,
|
||||
client_id: caseRecord.client.id,
|
||||
case_id: caseRecord.id,
|
||||
status: "draft",
|
||||
issue_date: new Date().toISOString().slice(0, 10),
|
||||
due_date: due.toISOString().slice(0, 10),
|
||||
subtotal,
|
||||
tax: 0,
|
||||
total: subtotal,
|
||||
created_by: user?.id,
|
||||
}).select("id").single();
|
||||
if (error || !inv) { toast.error(error?.message || "Failed"); setGenerating(false); return; }
|
||||
// Link entries
|
||||
const timeIds = unbilledTime.map((t) => t.id);
|
||||
const expIds = unbilledExpenses.map((e) => e.id);
|
||||
if (timeIds.length) await supabase.from("time_entries").update({ invoice_id: inv.id }).in("id", timeIds);
|
||||
if (expIds.length) await supabase.from("expenses").update({ invoice_id: inv.id }).in("id", expIds);
|
||||
setGenerating(false);
|
||||
toast.success(`Invoice ${num} created (draft)`);
|
||||
load();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Card className="border-border/60 bg-muted/20">
|
||||
<CardContent className="p-4 flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||
<div className="text-sm space-y-0.5">
|
||||
<div className="font-medium">Unbilled work</div>
|
||||
<div className="font-medium">Unbilled work on this case</div>
|
||||
<div className="text-muted-foreground text-xs">
|
||||
{unbilledTime.length} time entries · {unbilledExpenses.length} expenses
|
||||
</div>
|
||||
<div className="font-serif text-2xl text-foreground mt-1">{formatCurrency(subtotal)}</div>
|
||||
</div>
|
||||
<Button onClick={generate} disabled={subtotal <= 0 || generating}>
|
||||
{generating ? <Loader2 className="h-4 w-4 mr-2 animate-spin" /> : <FilePlus className="h-4 w-4 mr-2" />}
|
||||
<Button onClick={() => setGenOpen(true)} disabled={subtotal <= 0}>
|
||||
<FilePlus className="h-4 w-4 mr-2" />
|
||||
Generate invoice
|
||||
</Button>
|
||||
</CardContent>
|
||||
@@ -111,6 +80,14 @@ export function CaseInvoicesTab({ caseRecord }: { caseRecord: any }) {
|
||||
</table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<GenerateInvoiceDialog
|
||||
open={genOpen}
|
||||
onOpenChange={setGenOpen}
|
||||
clientId={caseRecord.client.id}
|
||||
clientName={caseRecord.client.name}
|
||||
presetCaseId={caseRecord.id}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user