This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
app/apps/web/src/App.tsx
T
Groom Book CTO f0185524ff feat: basic POS & invoicing (closes groombook/groombook#5)
- 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>
2026-03-17 20:00:42 +00:00

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>
);
}