"use client"; import { useState } from "react"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Badge } from "~/components/ui/badge"; import { toast } from "sonner"; import { api } from "~/trpc/react"; import { Send, DollarSign, FileText, AlertCircle, Clock, CheckCircle, RefreshCw, Calendar, Loader2, } from "lucide-react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "~/components/ui/alert-dialog"; import { getEffectiveInvoiceStatus, isInvoiceOverdue, getDaysPastDue, getStatusConfig, } from "~/lib/invoice-status"; import type { StoredInvoiceStatus } from "~/types/invoice"; interface StatusManagerProps { invoiceId: string; currentStatus: StoredInvoiceStatus; dueDate: Date; clientEmail?: string | null; onStatusChange?: () => void; } const statusIconConfig = { draft: FileText, sent: Send, paid: CheckCircle, overdue: AlertCircle, }; export function StatusManager({ invoiceId, currentStatus, dueDate, clientEmail, onStatusChange, }: StatusManagerProps) { const [isChangingStatus, setIsChangingStatus] = useState(false); const utils = api.useUtils(); const updateStatus = api.invoices.updateStatus.useMutation({ onSuccess: (data) => { toast.success(data.message); void utils.invoices.getById.invalidate({ id: invoiceId }); void utils.invoices.getAll.invalidate(); onStatusChange?.(); setIsChangingStatus(false); }, onError: (error) => { toast.error(error.message ?? "Failed to update status"); setIsChangingStatus(false); }, }); const sendEmail = api.email.sendInvoice.useMutation({ onSuccess: (data) => { toast.success(data.message); void utils.invoices.getById.invalidate({ id: invoiceId }); void utils.invoices.getAll.invalidate(); onStatusChange?.(); }, onError: (error) => { toast.error(error.message); }, }); const handleStatusUpdate = async (newStatus: StoredInvoiceStatus) => { setIsChangingStatus(true); updateStatus.mutate({ id: invoiceId, status: newStatus, }); }; const handleSendEmail = () => { sendEmail.mutate({ invoiceId }); }; const effectiveStatus = getEffectiveInvoiceStatus(currentStatus, dueDate); const isOverdue = isInvoiceOverdue(currentStatus, dueDate); const daysPastDue = getDaysPastDue(currentStatus, dueDate); const statusConfig = getStatusConfig(currentStatus, dueDate); const StatusIcon = statusIconConfig[effectiveStatus]; const getAvailableActions = () => { const actions = []; switch (effectiveStatus) { case "draft": if (clientEmail) { actions.push({ key: "send", label: "Send Invoice", action: handleSendEmail, variant: "default" as const, icon: Send, disabled: sendEmail.isPending, }); } actions.push({ key: "markPaid", label: "Mark as Paid", action: () => handleStatusUpdate("paid"), variant: "secondary" as const, icon: DollarSign, disabled: isChangingStatus, }); break; case "sent": actions.push({ key: "markPaid", label: "Mark as Paid", action: () => handleStatusUpdate("paid"), variant: "default" as const, icon: DollarSign, disabled: isChangingStatus, }); if (clientEmail) { actions.push({ key: "resend", label: "Resend Invoice", action: handleSendEmail, variant: "outline" as const, icon: Send, disabled: sendEmail.isPending, }); } actions.push({ key: "backToDraft", label: "Back to Draft", action: () => handleStatusUpdate("draft"), variant: "outline" as const, icon: FileText, disabled: isChangingStatus, }); break; case "overdue": actions.push({ key: "markPaid", label: "Mark as Paid", action: () => handleStatusUpdate("paid"), variant: "default" as const, icon: DollarSign, disabled: isChangingStatus, }); if (clientEmail) { actions.push({ key: "resend", label: "Resend Invoice", action: handleSendEmail, variant: "outline" as const, icon: Send, disabled: sendEmail.isPending, }); } actions.push({ key: "backToSent", label: "Mark as Sent", action: () => handleStatusUpdate("sent"), variant: "outline" as const, icon: Clock, disabled: isChangingStatus, }); break; case "paid": // Paid invoices can be reverted if needed (rare cases) actions.push({ key: "revert", label: "Revert to Sent", action: () => handleStatusUpdate("sent"), variant: "outline" as const, icon: RefreshCw, disabled: isChangingStatus, requireConfirmation: true, }); break; } return actions; }; const actions = getAvailableActions(); return ( Invoice Status {/* Current Status Display */}
{statusConfig.label} {statusConfig.description}
{/* Overdue Warning */} {isOverdue && (
{daysPastDue} day{daysPastDue !== 1 ? "s" : ""} overdue
)} {/* Due Date Info */}
Due:{" "} {new Intl.DateTimeFormat("en-US", { year: "numeric", month: "short", day: "numeric", }).format(new Date(dueDate))}
{/* Action Buttons */} {actions.length > 0 && (
Available Actions:
{actions.map((action) => { const ActionIcon = action.icon; if (action.requireConfirmation) { return ( Confirm Status Change Are you sure you want to change this invoice status? This action may affect your records. Cancel Confirm ); } return ( ); })}
)} {/* No Email Warning */} {!clientEmail && effectiveStatus !== "paid" && (
No email address on file for this client

Add an email address to the client to enable sending invoices.

)}
); }