diff --git a/INSTRUCTIONS.md b/INSTRUCTIONS.md index 1f2299b..a567a82 100644 --- a/INSTRUCTIONS.md +++ b/INSTRUCTIONS.md @@ -20,7 +20,7 @@ This repository implements the platform foundation milestone: 3. Add Prisma models and migrations for all persisted schema changes. 4. Keep uploaded files on disk under `/app/data/uploads`; never store blobs in SQLite. 5. Reuse shared DTOs and permission keys from the `shared` package. -6. Any UI that looks up items by SKU or item name must use a searchable picker/autocomplete, not a long dropdown. +6. Any non-filter UI that looks up records or items must use a searchable picker/autocomplete, not a long static dropdown. 7. Maintain the denser UI baseline on active screens; avoid reintroducing oversized `px-4 py-3` style controls, tall action bars, or overly loose card spacing without a specific reason. ## Operational notes @@ -31,7 +31,7 @@ This repository implements the platform foundation milestone: - Prefer Node 22 locally when running Prisma migration commands to match the Docker runtime. - Branding defaults live in the frontend theme token layer and are overridden by the persisted company profile. - Back up the whole `/app/data` volume to capture both the database and attachments. -- Treat searchable SKU lookup as a standing UX requirement for inventory, BOM, sales, purchasing, and manufacturing flows. +- Treat searchable lookup as a standing UX requirement for inventory, BOM, sales, purchasing, manufacturing, customer, vendor, and other operational record-picking flows. Filter-only controls can still use dropdowns. ## Next roadmap candidates diff --git a/README.md b/README.md index cd3dff7..1ef371c 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ The current inventory foundation supports: This module introduces `inventory.read` and `inventory.write` permissions. After updating the code, restart the server against the migrated database so bootstrap can upsert the new permissions onto the default administrator role. -Moving forward, any UI that requires searching for an item by SKU or item name should use a searchable picker/autocomplete rather than a static dropdown. +Moving forward, any UI that requires searching for records or items should use a searchable picker/autocomplete rather than a static dropdown. Filter controls can remain dropdowns, but non-filter lookup fields such as SKU pickers, customer selectors, vendor selectors, and similar operational search inputs should not be implemented as long static selects. ## UI Density diff --git a/STRUCTURE.md b/STRUCTURE.md index d1405ea..858ded8 100644 --- a/STRUCTURE.md +++ b/STRUCTURE.md @@ -15,7 +15,7 @@ - Keep reusable UI primitives in `src/components`. - Theme state and brand tokens belong in `src/theme`. - PDF screen components must remain separate from API-rendered document templates. -- Any item/SKU lookup UI must be implemented as a searchable picker or autocomplete; do not use long static dropdowns for inventory-scale datasets. +- Any non-filter lookup UI must be implemented as a searchable picker or autocomplete; do not use long static dropdowns for operational datasets such as items, customers, vendors, or document-linked records. - Preserve the current dense operations UI style on active module pages: compact controls, tighter card padding, and shorter empty states unless a screen has a clear reason to be more spacious. ## Backend rules diff --git a/client/src/modules/sales/SalesFormPage.tsx b/client/src/modules/sales/SalesFormPage.tsx index 1e4cc52..a9986af 100644 --- a/client/src/modules/sales/SalesFormPage.tsx +++ b/client/src/modules/sales/SalesFormPage.tsx @@ -17,6 +17,8 @@ export function SalesFormPage({ entity, mode }: { entity: SalesDocumentEntity; m const [form, setForm] = useState(emptySalesDocumentInput); const [status, setStatus] = useState(mode === "create" ? `Create a new ${config.singularLabel.toLowerCase()}.` : `Loading ${config.singularLabel.toLowerCase()}...`); const [customers, setCustomers] = useState([]); + const [customerSearchTerm, setCustomerSearchTerm] = useState(""); + const [customerPickerOpen, setCustomerPickerOpen] = useState(false); const [itemOptions, setItemOptions] = useState([]); const [lineSearchTerms, setLineSearchTerms] = useState([]); const [activeLinePicker, setActiveLinePicker] = useState(null); @@ -54,6 +56,7 @@ export function SalesFormPage({ entity, mode }: { entity: SalesDocumentEntity; m position: line.position, })), }); + setCustomerSearchTerm(document.customerName); setLineSearchTerms(document.lines.map((line: SalesDocumentDetailDto["lines"][number]) => line.itemSku)); setStatus(`${config.singularLabel} loaded.`); }) @@ -67,6 +70,10 @@ export function SalesFormPage({ entity, mode }: { entity: SalesDocumentEntity; m setForm((current: SalesDocumentInput) => ({ ...current, [key]: value })); } + function getSelectedCustomerName(customerId: string) { + return customers.find((customer) => customer.id === customerId)?.name ?? ""; + } + function updateLine(index: number, nextLine: SalesLineInput) { setForm((current: SalesDocumentInput) => ({ ...current, @@ -152,18 +159,73 @@ export function SalesFormPage({ entity, mode }: { entity: SalesDocumentEntity; m