diff --git a/app/api/dashboard/route.ts b/app/api/dashboard/route.ts index b5db9be..92b7a3a 100644 --- a/app/api/dashboard/route.ts +++ b/app/api/dashboard/route.ts @@ -1,7 +1,7 @@ import { getServerSession } from 'next-auth'; import { authOptions } from '@/auth'; import { prisma } from '@/lib/prisma'; -import type { TimeEntry, Client } from '@prisma/client'; +import type { TimeEntry, Client, Prisma } from '@prisma/client'; import { validateEnv } from '@/lib/env'; export async function GET() { @@ -105,6 +105,27 @@ export async function GET() { return sum + hours * Number(entry.client.hourlyRate); }, 0); + // Invoice counts: unpaid (not PAID) and overdue (status OVERDUE or dueDate passed and not PAID) + const now = new Date(); + const unpaidCount = await prisma.invoice.count({ + where: { userId: user.id, NOT: { status: 'PAID' } } as Prisma.InvoiceWhereInput, + }); + + const overdueCount = await prisma.invoice.count({ + where: ({ + userId: user.id, + AND: [ + { NOT: { status: 'PAID' } }, + { + OR: [ + { status: 'OVERDUE' }, + { dueDate: { lt: now } }, + ], + }, + ], + } as Prisma.InvoiceWhereInput), + }); + return Response.json({ totalEarnings: parseFloat(totalEarnings.toFixed(2)), weeklyEarnings: parseFloat(weeklyEarnings.toFixed(2)), @@ -115,6 +136,8 @@ export async function GET() { hours: parseFloat(data.hours.toFixed(2)), })), recentEntries, + unpaidCount, + overdueCount, }); } catch (error) { console.error('Error fetching dashboard data:', error); diff --git a/app/api/invoices/[id]/route.ts b/app/api/invoices/[id]/route.ts index 1a693f0..37fbd84 100644 --- a/app/api/invoices/[id]/route.ts +++ b/app/api/invoices/[id]/route.ts @@ -2,6 +2,7 @@ import { getServerSession } from 'next-auth'; import { authOptions } from '@/auth'; import { prisma } from '@/lib/prisma'; import { validateEnv } from '@/lib/env'; +import type { Prisma, InvoiceStatus } from '@prisma/client'; export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) { validateEnv(); @@ -87,10 +88,20 @@ export async function PATCH(req: Request, { params }: { params: Promise<{ id: st } catch { return Response.json({ error: 'Invalid JSON body' }, { status: 400 }); } - const { isPaid } = (body ?? {}) as { isPaid?: unknown }; + const { isPaid, status } = (body ?? {}) as { isPaid?: unknown; status?: unknown }; - if (typeof isPaid !== 'boolean') { - return Response.json({ error: 'Missing or invalid isPaid' }, { status: 400 }); + const allowedStatuses = ['DRAFT', 'SENT', 'PAID', 'OVERDUE']; + + if (typeof isPaid === 'undefined' && typeof status === 'undefined') { + return Response.json({ error: 'Missing update fields' }, { status: 400 }); + } + + if (typeof status !== 'undefined' && typeof status !== 'string') { + return Response.json({ error: 'Invalid status' }, { status: 400 }); + } + + if (typeof status === 'string' && !allowedStatuses.includes(status)) { + return Response.json({ error: 'Unknown status' }, { status: 400 }); } const user = await prisma.user.findUnique({ @@ -108,10 +119,20 @@ export async function PATCH(req: Request, { params }: { params: Promise<{ id: st } try { - const invoice = await prisma.invoice.update({ - where: { id }, - data: { isPaid }, - }); + const updateData: Prisma.InvoiceUncheckedUpdateInput = {}; + + if (typeof isPaid === 'boolean') updateData.isPaid = isPaid; + if (typeof status === 'string') updateData.status = status as InvoiceStatus; + + // Keep isPaid in sync if status is set to PAID + if (status === 'PAID') updateData.isPaid = true; + + // If marking as not paid, ensure isPaid flag reflects that + if (status && status !== 'PAID' && typeof isPaid === 'undefined') { + updateData.isPaid = false; + } + + const invoice = await prisma.invoice.update({ where: { id }, data: updateData }); return Response.json({ message: 'Invoice updated successfully', invoice }); } catch (error: unknown) { diff --git a/app/api/invoices/route.ts b/app/api/invoices/route.ts index bb6e0c3..ed79f69 100644 --- a/app/api/invoices/route.ts +++ b/app/api/invoices/route.ts @@ -29,7 +29,7 @@ export async function GET() { const now = new Date(); const invoicesWithOverdue = invoices.map((inv) => ({ ...inv, - isOverdue: !inv.isPaid && !!inv.dueDate && new Date(inv.dueDate) < now, + isOverdue: inv.status !== 'PAID' && !!inv.dueDate && new Date(inv.dueDate) < now, })); return Response.json({ invoices: invoicesWithOverdue }); @@ -123,6 +123,7 @@ export async function POST(req: Request) { clientId, userId: user.id, dueDate: due, + status: 'DRAFT', }; const invoice = await prisma.invoice.create({ diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index cff0d33..1ce25e5 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -19,6 +19,8 @@ interface DashboardData { hours: number; earnings: number; }>; + unpaidCount?: number; + overdueCount?: number; } export default function DashboardPage() { @@ -76,6 +78,18 @@ export default function DashboardPage() { +