mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2025-12-15 10:34:43 -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 { 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) => {
|
||||
|
||||
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,
|
||||
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 = <K extends keyof InvoiceFormData>(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) {
|
||||
<CardHeader><CardTitle className="flex gap-2 text-base"><Tag className="w-4 h-4" /> Invoice Config</CardTitle></CardHeader>
|
||||
<CardContent className="space-y-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>Due Date</Label><DatePicker date={formData.dueDate} onDateChange={(d) => updateField("dueDate", 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>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user