f0185524ff
- Add invoice_status and payment_method enums to schema - Add invoices table: appointmentId, clientId, subtotal/tax/tip/total cents, status (draft/pending/paid/void), paymentMethod, paidAt, notes - Add invoice_line_items table: invoiceId, description, qty, unitPrice, total - Migration 0002_invoices.sql with FK constraints and journal entry - POST /api/invoices — create invoice with line items - POST /api/invoices/from-appointment/:id — one-click invoice from appointment, pre-populated with service name and price; returns 409 if already invoiced - GET /api/invoices — list with optional ?status/clientId/appointmentId filters - GET /api/invoices/:id — invoice with line items - PATCH /api/invoices/:id — update status, payment method, tip, notes; auto-sets paidAt when marking paid; blocks edits on voided invoices - Add Invoice/InvoiceLineItem types to @groombook/types - InvoicesPage: list view with status filter, create from appointment modal, detail modal with tip input, payment method selector, Mark as Paid/Void actions - Add Invoices nav link in App.tsx Co-Authored-By: Paperclip <noreply@paperclip.ing>
65 lines
2.1 KiB
TypeScript
65 lines
2.1 KiB
TypeScript
import { Routes, Route, Link, useLocation } from "react-router-dom";
|
|
import { AppointmentsPage } from "./pages/Appointments.js";
|
|
import { ClientsPage } from "./pages/Clients.js";
|
|
import { ServicesPage } from "./pages/Services.js";
|
|
import { StaffPage } from "./pages/Staff.js";
|
|
import { InvoicesPage } from "./pages/Invoices.js";
|
|
|
|
const NAV_LINKS = [
|
|
{ to: "/", label: "Appointments" },
|
|
{ to: "/clients", label: "Clients" },
|
|
{ to: "/services", label: "Services" },
|
|
{ to: "/staff", label: "Staff" },
|
|
{ to: "/invoices", label: "Invoices" },
|
|
];
|
|
|
|
export function App() {
|
|
const location = useLocation();
|
|
return (
|
|
<div style={{ minHeight: "100vh", fontFamily: "system-ui, sans-serif" }}>
|
|
<nav
|
|
style={{
|
|
padding: "0.75rem 1rem",
|
|
borderBottom: "1px solid #e2e8f0",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "0.25rem",
|
|
background: "#fff",
|
|
}}
|
|
>
|
|
<strong style={{ marginRight: "1rem", fontSize: 16 }}>Groom Book</strong>
|
|
{NAV_LINKS.map(({ to, label }) => {
|
|
const active =
|
|
to === "/" ? location.pathname === "/" : location.pathname.startsWith(to);
|
|
return (
|
|
<Link
|
|
key={to}
|
|
to={to}
|
|
style={{
|
|
padding: "0.35rem 0.75rem",
|
|
borderRadius: 4,
|
|
textDecoration: "none",
|
|
fontSize: 14,
|
|
fontWeight: active ? 600 : 400,
|
|
color: active ? "#1d4ed8" : "#374151",
|
|
background: active ? "#eff6ff" : "transparent",
|
|
}}
|
|
>
|
|
{label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
<main style={{ padding: "1rem 1.5rem" }}>
|
|
<Routes>
|
|
<Route path="/" element={<AppointmentsPage />} />
|
|
<Route path="/clients" element={<ClientsPage />} />
|
|
<Route path="/services" element={<ServicesPage />} />
|
|
<Route path="/staff" element={<StaffPage />} />
|
|
<Route path="/invoices" element={<InvoicesPage />} />
|
|
</Routes>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|