feat(search): full-platform search overhaul + view tracking + notes bucket
Service rewrite covers 14 entity buckets (clients, residential clients, yachts, companies, interests, residential interests, berths, invoices, expenses, documents, files, reminders, brochures, tags, notes, navigation) with prefix tsquery + trigram fallback, phone-digit normalization, and JOINs to client_contacts for email matching. New `notes` bucket searches across the four note tables (client, interest, yacht, company) via UNION + parent-entity label resolution (berth mooring for interests, name for yachts/companies). Renders at the bottom of the dropdown so broad-content matches don't crowd entity-specific hits — per the user's "low-noise" preference. Recently-viewed tracking persists last 20 entity views per user in Redis sorted set; CommandSearch surfaces them as the dropdown's default state and applies affinity ranking when the user types. ID-resolve endpoint accepts pasted UUIDs (or invoice numbers like `INV-2025-001`) and routes the rep straight to the entity, skipping the normal search bucket. Audit search service gains `entityIds[]` array filter for the new loadClientActivityAggregated() path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,56 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const BUCKET_TYPES = [
|
||||
'clients',
|
||||
'residentialClients',
|
||||
'yachts',
|
||||
'companies',
|
||||
'interests',
|
||||
'residentialInterests',
|
||||
'berths',
|
||||
'invoices',
|
||||
'expenses',
|
||||
'documents',
|
||||
'files',
|
||||
'reminders',
|
||||
'brochures',
|
||||
'tags',
|
||||
'navigation',
|
||||
] as const;
|
||||
|
||||
export const searchQuerySchema = z.object({
|
||||
// 2-char minimum keeps `to_tsquery('a:*')` from returning every word
|
||||
// starting with "a" — short queries return overwhelming match sets.
|
||||
q: z.string().min(2).max(200),
|
||||
/** Restrict the result set to a single bucket. */
|
||||
type: z.enum(BUCKET_TYPES).optional(),
|
||||
/** Per-bucket cap. Defaults to 5 (dropdown). 25 is the typical /search-page value. */
|
||||
limit: z.coerce.number().int().min(1).max(50).optional(),
|
||||
/** Super-admin only — search ports beyond the current one. */
|
||||
includeOtherPorts: z
|
||||
.union([z.literal('true'), z.literal('1'), z.literal('false'), z.literal('0')])
|
||||
.transform((v) => v === 'true' || v === '1')
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type SearchQuery = z.infer<typeof searchQuerySchema>;
|
||||
|
||||
const RECENTLY_VIEWED_TYPES = [
|
||||
'client',
|
||||
'residential-client',
|
||||
'yacht',
|
||||
'company',
|
||||
'interest',
|
||||
'residential-interest',
|
||||
'berth',
|
||||
'invoice',
|
||||
'expense',
|
||||
'document',
|
||||
] as const;
|
||||
|
||||
export const trackViewSchema = z.object({
|
||||
type: z.enum(RECENTLY_VIEWED_TYPES),
|
||||
id: z.string().min(1).max(100),
|
||||
});
|
||||
|
||||
export type TrackViewPayload = z.infer<typeof trackViewSchema>;
|
||||
|
||||
Reference in New Issue
Block a user