mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2026-02-05 00:06:36 -05:00
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)
This commit is contained in:
@@ -24,12 +24,13 @@ import { RevenueChart } from "~/app/dashboard/_components/revenue-chart";
|
|||||||
import { InvoiceStatusChart } from "~/app/dashboard/_components/invoice-status-chart";
|
import { InvoiceStatusChart } from "~/app/dashboard/_components/invoice-status-chart";
|
||||||
import { MonthlyMetricsChart } from "~/app/dashboard/_components/monthly-metrics-chart";
|
import { MonthlyMetricsChart } from "~/app/dashboard/_components/monthly-metrics-chart";
|
||||||
import { AnimatedStatsCard } from "~/app/dashboard/_components/animated-stats-card";
|
import { AnimatedStatsCard } from "~/app/dashboard/_components/animated-stats-card";
|
||||||
|
import type { DashboardStats, RecentInvoice } from "./types";
|
||||||
|
|
||||||
// Hero section with clean mono design
|
// Hero section with clean mono design
|
||||||
|
|
||||||
|
|
||||||
// Enhanced stats cards with better visuals
|
// 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) => {
|
const formatTrend = (value: number, isCount = false) => {
|
||||||
if (isCount) {
|
if (isCount) {
|
||||||
return value > 0 ? `+${value}` : value.toString();
|
return value > 0 ? `+${value}` : value.toString();
|
||||||
@@ -101,7 +102,7 @@ function DashboardStats({ stats }: { stats: any }) { // TODO: Import RouterOutpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Charts section
|
// 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.
|
// 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.
|
// 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.
|
// Actually, let's fetch invoices here for the status chart to keep it working.
|
||||||
@@ -309,7 +310,7 @@ async function CurrentWork() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enhanced recent activity
|
// Enhanced recent activity
|
||||||
async function RecentActivity({ recentInvoices }: { recentInvoices: any[] }) {
|
async function RecentActivity({ recentInvoices }: { recentInvoices: RecentInvoice[] }) {
|
||||||
// Use passed recentInvoices instead of fetching all
|
// Use passed recentInvoices instead of fetching all
|
||||||
|
|
||||||
const getStatusStyle = (status: string) => {
|
const getStatusStyle = (status: string) => {
|
||||||
|
|||||||
13
src/app/dashboard/types.ts
Normal file
13
src/app/dashboard/types.ts
Normal file
@@ -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];
|
||||||
@@ -111,7 +111,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
clientId: existingInvoice.clientId,
|
clientId: existingInvoice.clientId,
|
||||||
issueDate: new Date(existingInvoice.issueDate),
|
issueDate: new Date(existingInvoice.issueDate),
|
||||||
dueDate: new Date(existingInvoice.dueDate),
|
dueDate: new Date(existingInvoice.dueDate),
|
||||||
status: existingInvoice.status as any,
|
status: existingInvoice.status as "draft" | "sent" | "paid",
|
||||||
notes: existingInvoice.notes ?? "",
|
notes: existingInvoice.notes ?? "",
|
||||||
taxRate: existingInvoice.taxRate,
|
taxRate: existingInvoice.taxRate,
|
||||||
defaultHourlyRate: null,
|
defaultHourlyRate: null,
|
||||||
@@ -119,7 +119,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
});
|
});
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
} else if ((!invoiceId || invoiceId === "new") && businesses && !initialized) {
|
} 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 }));
|
if (defaultBusiness) setFormData((prev) => ({ ...prev, businessId: defaultBusiness.id }));
|
||||||
setInitialized(true);
|
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 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) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
items: prev.items.map((item, i) => {
|
items: prev.items.map((item, i) => {
|
||||||
if (i === idx) {
|
if (i === idx) {
|
||||||
const u: any = { ...item, [field]: value };
|
const updated = { ...item, [field]: value };
|
||||||
if (field === "hours" || field === "rate") u.amount = u.hours * u.rate;
|
if (field === "hours" || field === "rate") {
|
||||||
return u;
|
updated.amount = updated.hours * updated.rate;
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
})
|
})
|
||||||
@@ -238,7 +240,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
} finally { setLoading(false); }
|
} finally { setLoading(false); }
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateField = (field: keyof InvoiceFormData, value: any) => setFormData(p => ({ ...p, [field]: value }));
|
const updateField = <K extends keyof InvoiceFormData>(field: K, value: InvoiceFormData[K]) => setFormData(p => ({ ...p, [field]: value }));
|
||||||
const handleDelete = () => setDeleteDialogOpen(true);
|
const handleDelete = () => setDeleteDialogOpen(true);
|
||||||
const confirmDelete = () => { if (invoiceId) deleteInvoice.mutate({ id: invoiceId }); };
|
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
|
// Explicitly prioritize client rate, then business rate, then 0
|
||||||
const clientRate = selectedClient && 'defaultHourlyRate' in selectedClient ? selectedClient.defaultHourlyRate : null;
|
const clientRate = selectedClient && 'defaultHourlyRate' in selectedClient ? selectedClient.defaultHourlyRate : null;
|
||||||
const businessRate = currentBusiness && 'defaultHourlyRate' in currentBusiness ? currentBusiness.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);
|
updateField("defaultHourlyRate", rateToSet);
|
||||||
}}
|
}}
|
||||||
@@ -294,8 +296,8 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
<CardHeader><CardTitle className="flex gap-2 text-base"><Tag className="w-4 h-4" /> Invoice Config</CardTitle></CardHeader>
|
<CardHeader><CardTitle className="flex gap-2 text-base"><Tag className="w-4 h-4" /> Invoice Config</CardTitle></CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2"><Label>Issue Date</Label><DatePicker date={formData.issueDate} onDateChange={(d) => updateField("issueDate", d || new Date())} className="w-full" /></div>
|
<div className="space-y-2"><Label>Issue Date</Label><DatePicker date={formData.issueDate} onDateChange={(d) => updateField("issueDate", d ?? new Date())} className="w-full" /></div>
|
||||||
<div className="space-y-2"><Label>Due Date</Label><DatePicker date={formData.dueDate} onDateChange={(d) => updateField("dueDate", d || new Date())} className="w-full" /></div>
|
<div className="space-y-2"><Label>Due Date</Label><DatePicker date={formData.dueDate} onDateChange={(d) => updateField("dueDate", d ?? new Date())} className="w-full" /></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2"><Label>Tax Rate</Label><NumberInput value={formData.taxRate} onChange={(v) => updateField("taxRate", v)} suffix="%" className="w-full" /></div>
|
<div className="space-y-2"><Label>Tax Rate</Label><NumberInput value={formData.taxRate} onChange={(v) => updateField("taxRate", v)} suffix="%" className="w-full" /></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user