--- import DashboardLayout from '../../../layouts/DashboardLayout.astro'; import { Icon } from 'astro-icon/components'; import { db } from '../../../db'; import { invoices, clients, members } from '../../../db/schema'; import { eq, desc, and, gte, lte, sql } from 'drizzle-orm'; const user = Astro.locals.user; if (!user) return Astro.redirect('/login'); // Get current team from cookie const currentTeamId = Astro.cookies.get('currentTeamId')?.value; const userMemberships = await db.select() .from(members) .where(eq(members.userId, user.id)) .all(); if (userMemberships.length === 0) return Astro.redirect('/dashboard'); // Use current team or fallback to first membership const userMembership = currentTeamId ? userMemberships.find(m => m.organizationId === currentTeamId) || userMemberships[0] : userMemberships[0]; const currentTeamIdResolved = userMembership.organizationId; // Get filter parameters const currentYear = new Date().getFullYear(); const yearParam = Astro.url.searchParams.get('year'); const selectedYear = yearParam === 'current' || !yearParam ? 'current' : parseInt(yearParam); const selectedType = Astro.url.searchParams.get('type') || 'all'; const selectedStatus = Astro.url.searchParams.get('status') || 'all'; const sortBy = Astro.url.searchParams.get('sort') || 'date-desc'; // Fetch all invoices for the organization (for year dropdown) const allInvoicesRaw = await db.select({ invoice: invoices, client: clients, }) .from(invoices) .leftJoin(clients, eq(invoices.clientId, clients.id)) .where(eq(invoices.organizationId, currentTeamIdResolved)) .all(); // Get unique years from invoices const availableYears = [...new Set(allInvoicesRaw.map(i => i.invoice.issueDate.getFullYear()))].sort((a, b) => b - a); // Ensure current year is in the list if (!availableYears.includes(currentYear)) { availableYears.unshift(currentYear); } // Filter by year const yearStart = selectedYear === 'current' ? new Date(currentYear, 0, 1) : new Date(selectedYear, 0, 1); const yearEnd = selectedYear === 'current' ? new Date() : new Date(selectedYear, 11, 31, 23, 59, 59); let filteredInvoices = allInvoicesRaw.filter(i => { const issueDate = i.invoice.issueDate; return issueDate >= yearStart && issueDate <= yearEnd; }); // Filter by type if (selectedType !== 'all') { filteredInvoices = filteredInvoices.filter(i => i.invoice.type === selectedType); } // Filter by status if (selectedStatus !== 'all') { filteredInvoices = filteredInvoices.filter(i => i.invoice.status === selectedStatus); } // Sort invoices const allInvoices = filteredInvoices.sort((a, b) => { switch (sortBy) { case 'date-desc': return b.invoice.issueDate.getTime() - a.invoice.issueDate.getTime(); case 'date-asc': return a.invoice.issueDate.getTime() - b.invoice.issueDate.getTime(); case 'amount-desc': return b.invoice.total - a.invoice.total; case 'amount-asc': return a.invoice.total - b.invoice.total; case 'number-desc': return b.invoice.number.localeCompare(a.invoice.number); case 'number-asc': return a.invoice.number.localeCompare(b.invoice.number); default: return b.invoice.issueDate.getTime() - a.invoice.issueDate.getTime(); } }); // Calculate stats for the selected year const yearInvoices = allInvoicesRaw.filter(i => { const issueDate = i.invoice.issueDate; return issueDate >= yearStart && issueDate <= yearEnd; }); const formatCurrency = (amount: number, currency: string) => { return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency, }).format(amount / 100); }; const getStatusColor = (status: string) => { switch (status) { case 'paid': return 'badge-success'; case 'accepted': return 'badge-success'; case 'sent': return 'badge-info'; case 'draft': return 'badge-ghost'; case 'void': return 'badge-error'; case 'declined': return 'badge-error'; default: return 'badge-ghost'; } }; ---

Invoices & Quotes

Manage your billing and estimates

Create New
Total Invoices
{yearInvoices.filter(i => i.invoice.type === 'invoice').length}
{selectedYear === 'current' ? `${currentYear} (YTD)` : selectedYear}
Open Quotes
{yearInvoices.filter(i => i.invoice.type === 'quote' && i.invoice.status === 'sent').length}
Waiting for approval
Total Revenue
{formatCurrency(yearInvoices .filter(i => i.invoice.type === 'invoice' && i.invoice.status === 'paid') .reduce((acc, curr) => acc + curr.invoice.total, 0), 'USD')}
Paid invoices ({selectedYear === 'current' ? `${currentYear} YTD` : selectedYear})
{(selectedYear !== 'current' || selectedType !== 'all' || selectedStatus !== 'all' || sortBy !== 'date-desc') && ( )}

Showing {allInvoices.length} {allInvoices.length === 1 ? 'result' : 'results'} {selectedYear === 'current' ? ` for ${currentYear} (year to date)` : ` for ${selectedYear}`}

{allInvoices.length === 0 ? ( ) : ( allInvoices.map(({ invoice, client }) => ( )) )}
Number Client Date Due Date Amount Status Type
No invoices or quotes found. Create one to get started.
{invoice.number} {client ? (
{client.name}
) : ( Deleted Client )}
{invoice.issueDate.toLocaleDateString()} {invoice.dueDate.toLocaleDateString()} {formatCurrency(invoice.total, invoice.currency)}
{invoice.status}
{invoice.type}