feat: quick-find search for clients and pets (GH #97) #103
Reference in New Issue
Block a user
Delete Branch "feat/quick-find-search-gh97"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements unified quick-find search across clients and pets per #97 (GRO-140).
Backend —
GET /api/search?q={query}New route at
apps/api/src/routes/search.ts, registered under the protected API so auth + RBAC middleware applies automatically (all authenticated staff roles have read access).ILIKE%,_,\) escaped before wrapping in%…%patternilikeexport to@groombook/dbResponse shape:
Frontend — GlobalSearch component
Added to the sticky admin nav in
AdminLayout(App.tsx):/admin/clientsTests
apps/api/src/__tests__/search.test.ts:qclientsandpetskeysCloses #97
cc @cpfarhood
Product Scope Review — Approved
This PR matches the spec in #97 exactly. No scope creep.
Checklist:
GET /api/search?q={query}with ILIKE on clients (name, email, phone) and pets (name, breed)Clean delivery. Ready for QA and CTO review.
CTO Review — Request Changes
CI Blocker
makeSelectChainis defined but never used insearch.test.ts:37. Remove the dead function.Architecture
The search endpoint and global search bar align well with the spec. Good use of Drizzle
ilike,Promise.allfor parallel queries, and proper LIKE-character escaping.Issues to Address
Medium:
handleClientClick(client)andhandlePetClick(pet)both navigate to/admin/clientswith no use of the passed parameter. At minimum, navigate to the specific client (e.g., select/highlight the client). The unused parameters are misleading.authMiddleware+resolveStaffMiddlewarefrom theapiHono instance. If middleware is applied per-route rather than globally, the search endpoint could be unauthenticated.Low:
3. Single-character queries (
?q=a) will match broadly. Consider a minimum 2-character requirement, or accept it as MVP behavior.4. Fetch errors in
GlobalSearch.tsxare silently swallowed — aconsole.warnwould aid debugging.Good
escapeLike()correctly handles%,_,\role="combobox",aria-expanded, etc.)Fix the lint error and address #1-2 before re-requesting review.
CTO Feedback Addressed
All required changes from the CTO review have been fixed in
0c182da:Lint blocker (fixed):
makeSelectChainfunction fromsearch.test.ts:37Navigation deep-linking (fixed):
handleClientClicknow navigates to/admin/clients?highlight={client.id}— the client ID is passed in the URL so the page can use it (e.g., scroll/highlight)handlePetClicknow navigates to/admin/clients?highlight={pet.clientId}— routes to the owner's recordAuth middleware (verified):
searchRouteris registered on theapiHono instance (line 102 ofindex.ts), which appliesauthMiddleware+resolveStaffMiddlewareglobally at lines 61–62. The search endpoint is fully protected.Fetch error logging (fixed):
GlobalSearch.tsxnow logsconsole.warn("GlobalSearch: fetch error", err)instead of silently swallowing errors.Re-requesting CTO review.
All CTO feedback addressed. Architecture is solid. Approved.
QA Review: PR #103 — Quick-Find Search
Status: APPROVED
Testing Performed
feat/quick-find-search-gh97at commit0c182dapnpm typecheck— all packages passpnpm test— 128/128 tests pass (101 API + 27 web)Architecture
GET /api/search?q={query}correctly implements search with:escapeLike()properly escapes%,_,\before pattern wrappingeq(clients.status, \active\))innerJoin— pets of disabled owners excludedPromise.allparallel executionGlobalSearchcomponent: debounced 300ms, grouped dropdown, outside-click close, correct navigation to/admin/clients?highlight=Test Coverage Assessment
Backend tests (
apps/api/src/__tests__/search.test.ts): 9 tests covering:Minor Notes (not blocking)
GlobalSearch.tsx— New component with no frontend test coverage. Not blocking since CTO already approved and backend logic is well-tested.escapeLikeonly escapes ASCII special chars. Works fine for typical Latin queries.CI Status
✅ Latest CI run (
23395410343) passed:successonfeat/quick-find-search-gh97✅ CTO approval already given after addressing unused
makeSelectChainlint issue