From 75c4362d97f3c9ff98e8d17b065092e7de35cfd9 Mon Sep 17 00:00:00 2001 From: Sean O'Connor Date: Thu, 11 Dec 2025 20:15:29 -0500 Subject: [PATCH] fix: resolve all remaining type safety errors - Create types.ts with proper TypeScript interfaces for dashboard data - Replace all 'any' types in dashboard/page.tsx with DashboardStats and RecentInvoice - Fix type safety in invoice-form.tsx: - Replace 'any' in updateItem with proper union type - Add generic type parameter to updateField for type safety - Fix status type assertion (any -> proper union type) - Replace || with ?? for safer null handling - All TypeScript compilation errors resolved - Lint down to 1 warning (false positive for 'loading' variable) --- src/app/dashboard/page.tsx | 7 ++++--- src/app/dashboard/types.ts | 13 +++++++++++++ src/components/forms/invoice-form.tsx | 22 ++++++++++++---------- 3 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 src/app/dashboard/types.ts diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index a254c0c..2fddfe5 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -24,12 +24,13 @@ import { RevenueChart } from "~/app/dashboard/_components/revenue-chart"; import { InvoiceStatusChart } from "~/app/dashboard/_components/invoice-status-chart"; import { MonthlyMetricsChart } from "~/app/dashboard/_components/monthly-metrics-chart"; import { AnimatedStatsCard } from "~/app/dashboard/_components/animated-stats-card"; +import type { DashboardStats, RecentInvoice } from "./types"; // Hero section with clean mono design // Enhanced stats cards with better visuals -function DashboardStats({ stats }: { stats: any }) { // TODO: Import RouterOutput type +function DashboardStats({ stats }: { stats: DashboardStats }) { // TODO: Import RouterOutput type const formatTrend = (value: number, isCount = false) => { if (isCount) { return value > 0 ? `+${value}` : value.toString(); @@ -101,7 +102,7 @@ function DashboardStats({ stats }: { stats: any }) { // TODO: Import RouterOutpu } // Charts section -async function ChartsSection({ stats }: { stats: any }) { +async function ChartsSection({ stats }: { stats: DashboardStats }) { // We still fetch all invoices for the status chart for now, or we could aggregate that too. // For now, let's keep status chart as is (fetching all) but use aggregated for revenue. // Actually, let's fetch invoices here for the status chart to keep it working. @@ -309,7 +310,7 @@ async function CurrentWork() { } // Enhanced recent activity -async function RecentActivity({ recentInvoices }: { recentInvoices: any[] }) { +async function RecentActivity({ recentInvoices }: { recentInvoices: RecentInvoice[] }) { // Use passed recentInvoices instead of fetching all const getStatusStyle = (status: string) => { diff --git a/src/app/dashboard/types.ts b/src/app/dashboard/types.ts new file mode 100644 index 0000000..e70aa01 --- /dev/null +++ b/src/app/dashboard/types.ts @@ -0,0 +1,13 @@ +import { type RouterOutputs } from "~/trpc/react"; + +// Dashboard stats type from the dashboard router +export type DashboardStats = RouterOutputs["dashboard"]["getStats"]; + +// Individual invoice type from the invoices router +export type Invoice = RouterOutputs["invoices"]["getAll"][number]; + +// Recent invoice type (includes client relation) +export type RecentInvoice = DashboardStats["recentInvoices"][number]; + +// Revenue chart data point +export type RevenueChartDataPoint = DashboardStats["revenueChartData"][number]; diff --git a/src/components/forms/invoice-form.tsx b/src/components/forms/invoice-form.tsx index ba00bd7..83b6e67 100644 --- a/src/components/forms/invoice-form.tsx +++ b/src/components/forms/invoice-form.tsx @@ -111,7 +111,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { clientId: existingInvoice.clientId, issueDate: new Date(existingInvoice.issueDate), dueDate: new Date(existingInvoice.dueDate), - status: existingInvoice.status as any, + status: existingInvoice.status as "draft" | "sent" | "paid", notes: existingInvoice.notes ?? "", taxRate: existingInvoice.taxRate, defaultHourlyRate: null, @@ -119,7 +119,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { }); setInitialized(true); } else if ((!invoiceId || invoiceId === "new") && businesses && !initialized) { - const defaultBusiness = businesses.find((b) => b.isDefault) || businesses[0]; + const defaultBusiness = businesses.find((b) => b.isDefault) ?? businesses[0]; if (defaultBusiness) setFormData((prev) => ({ ...prev, businessId: defaultBusiness.id })); setInitialized(true); } @@ -140,14 +140,16 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { })); }; const removeItem = (idx: number) => { if (formData.items.length > 1) setFormData((prev) => ({ ...prev, items: prev.items.filter((_, i) => i !== idx) })); }; - const updateItem = (idx: number, field: string, value: any) => { + const updateItem = (idx: number, field: string, value: string | number | Date) => { setFormData((prev) => ({ ...prev, items: prev.items.map((item, i) => { if (i === idx) { - const u: any = { ...item, [field]: value }; - if (field === "hours" || field === "rate") u.amount = u.hours * u.rate; - return u; + const updated = { ...item, [field]: value }; + if (field === "hours" || field === "rate") { + updated.amount = updated.hours * updated.rate; + } + return updated; } return item; }) @@ -238,7 +240,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { } finally { setLoading(false); } }; - const updateField = (field: keyof InvoiceFormData, value: any) => setFormData(p => ({ ...p, [field]: value })); + const updateField = (field: K, value: InvoiceFormData[K]) => setFormData(p => ({ ...p, [field]: value })); const handleDelete = () => setDeleteDialogOpen(true); const confirmDelete = () => { if (invoiceId) deleteInvoice.mutate({ id: invoiceId }); }; @@ -277,7 +279,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { // Explicitly prioritize client rate, then business rate, then 0 const clientRate = selectedClient && 'defaultHourlyRate' in selectedClient ? selectedClient.defaultHourlyRate : null; const businessRate = currentBusiness && 'defaultHourlyRate' in currentBusiness ? currentBusiness.defaultHourlyRate : null; - const rateToSet = clientRate ?? businessRate ?? 0; + const rateToSet: number = (clientRate ?? businessRate ?? 0) as number; updateField("defaultHourlyRate", rateToSet); }} @@ -294,8 +296,8 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { Invoice Config
-
updateField("issueDate", d || new Date())} className="w-full" />
-
updateField("dueDate", d || new Date())} className="w-full" />
+
updateField("issueDate", d ?? new Date())} className="w-full" />
+
updateField("dueDate", d ?? new Date())} className="w-full" />
updateField("taxRate", v)} suffix="%" className="w-full" />