"use client"; import { Check, Loader2, Pause, Play, Plus, RefreshCw, Trash2, Zap, } from "lucide-react"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { DashboardPageHeader } from "~/components/layout/page-header"; import { DashboardPage } from "~/components/layout/dashboard-page"; import { EmptyState } from "~/components/layout/page-layout"; import { Badge } from "~/components/ui/badge"; import { Button } from "~/components/ui/button"; import { Card, CardContent } from "~/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "~/components/ui/dialog"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; import { NumberInput } from "~/components/ui/number-input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "~/components/ui/select"; import { Textarea } from "~/components/ui/textarea"; import { api } from "~/trpc/react"; const SCHEDULES = [ { value: "weekly", label: "Weekly" }, { value: "biweekly", label: "Every 2 weeks" }, { value: "monthly", label: "Monthly" }, { value: "quarterly", label: "Quarterly" }, { value: "yearly", label: "Yearly" }, ] as const; type Schedule = (typeof SCHEDULES)[number]["value"]; interface RecurringItemInput { description: string; hours: number; rate: number; } interface RecurringFormState { name: string; clientId: string; businessId: string; schedule: Schedule; invoicePrefix: string; taxRate: number; currency: string; notes: string; emailMessage: string; items: RecurringItemInput[]; } const defaultForm = (): RecurringFormState => ({ name: "", clientId: "", businessId: "", schedule: "monthly", invoicePrefix: "#", taxRate: 0, currency: "USD", notes: "", emailMessage: "", items: [{ description: "", hours: 0, rate: 0 }], }); function formatDate(date: Date) { return new Intl.DateTimeFormat("en-US", { year: "numeric", month: "short", day: "numeric", }).format(new Date(date)); } function scheduleLabel(s: string) { return SCHEDULES.find((x) => x.value === s)?.label ?? s; } function RecurringForm({ form, setForm, clients, businesses, }: { form: RecurringFormState; setForm: React.Dispatch>; clients: { id: string; name: string }[]; businesses: { id: string; name: string }[]; }) { const addItem = () => setForm((f) => ({ ...f, items: [...f.items, { description: "", hours: 0, rate: 0 }] })); const removeItem = (idx: number) => setForm((f) => ({ ...f, items: f.items.filter((_, i) => i !== idx) })); const updateItem = (idx: number, field: keyof RecurringItemInput, value: string | number) => setForm((f) => ({ ...f, items: f.items.map((item, i) => (i === idx ? { ...item, [field]: value } : item)), })); return (
setForm((f) => ({ ...f, name: e.target.value }))} />
setForm((f) => ({ ...f, currency: e.target.value.toUpperCase() }))} />
setForm((f) => ({ ...f, taxRate: v ?? 0 }))} min={0} max={100} step={0.1} />
{/* Line items */}
{form.items.map((item, idx) => (
updateItem(idx, "description", e.target.value)} className="flex-1" /> {form.items.length > 1 && ( )}
updateItem(idx, "hours", v ?? 0)} min={0} step={0.25} />
updateItem(idx, "rate", v ?? 0)} min={0} step={1} />
))}