feat: appointment scheduling, client/pet/service/staff CRUD UI

* feat: appointment scheduling, client/pet/service/staff CRUD UI

- Weekly calendar view with navigation, color-coded by status
- Booking form with client→pet→service→staff→date/time flow
- Double-booking conflict detection on POST/PATCH appointments
- DELETE /api/appointments endpoint
- Staff API route (/api/staff) with full CRUD
- Clients page: searchable list, create/edit clients, add/edit pets
- Services page: table with create/edit/toggle-active
- Staff page: table with create/edit/toggle-active
- Nav bar with active-link highlighting, Staff link added

Resolves GitHub groombook/groombook#1, #2, #8

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: remove unused import, fix useCallback deps

- Remove unused `or` import from drizzle-orm in appointments route
- Compute week end directly in loadAppointments callback to avoid
  exhaustive-deps lint warning (weekEnd derived from weekStart)

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* chore: add pnpm lockfile

Required for CI --frozen-lockfile installs.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: resolve all typecheck, lint, and test failures

- Add @types/node to packages/db devDependencies (typecheck was missing process)
- Re-export drizzle-orm helpers (eq, gte, etc.) from @groombook/db to avoid
  duplicate-instance type conflicts; remove drizzle-orm direct dep from API
- Add @hono/zod-validator and jose as direct API dependencies
- Merge duplicate @groombook/db imports in all route files
- Fix noUncheckedIndexedAccess errors: appointments PATCH, web calendar grid
- Fix weightKg/dateOfBirth type conversion in pets route (numeric→string, string→Date)
- Add eslint.config.js for API and web (ESLint 9 flat config format)
- Add vitest.config.ts with passWithNoTests for API and web

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit was merged in pull request #15.
This commit is contained in:
groombook-paperclip[bot]
2026-03-17 18:45:28 +00:00
committed by GitHub
parent f4101982bb
commit 4f92b8bffb
19 changed files with 7878 additions and 99 deletions
+45 -12
View File
@@ -1,26 +1,59 @@
import { Routes, Route, Link } from "react-router-dom";
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";
const NAV_LINKS = [
{ to: "/", label: "Appointments" },
{ to: "/clients", label: "Clients" },
{ to: "/services", label: "Services" },
{ to: "/staff", label: "Staff" },
];
export function App() {
const location = useLocation();
return (
<div>
<nav style={{ padding: "1rem", borderBottom: "1px solid #e2e8f0" }}>
<strong style={{ marginRight: "1.5rem" }}>Groom Book</strong>
<Link to="/" style={{ marginRight: "1rem" }}>
Appointments
</Link>
<Link to="/clients" style={{ marginRight: "1rem" }}>
Clients
</Link>
<Link to="/services">Services</Link>
<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" }}>
<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 />} />
</Routes>
</main>
</div>