# Supplier References Frontend Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Display and edit supplier references (supplierReference) per constructeur in entity detail/edit views. **Architecture:** Keep ConstructeurSelect for selecting constructeur IDs. Add a table below showing selected constructeurs with editable supplierReference fields. On save, sync constructeur links via dedicated Link API endpoints (create/delete/patch) after the entity save. Fetch links separately when loading an entity. **Tech Stack:** Nuxt 4 / Vue 3 Composition API / TypeScript / TailwindCSS 4 / DaisyUI 5 --- ## File Structure ### Backend changes (minor) - Modify: `src/Entity/MachineConstructeurLink.php` — add SearchFilter - Modify: `src/Entity/PieceConstructeurLink.php` — add SearchFilter - Modify: `src/Entity/ComposantConstructeurLink.php` — add SearchFilter - Modify: `src/Entity/ProductConstructeurLink.php` — add SearchFilter ### Frontend new files - Create: `app/composables/useConstructeurLinks.ts` — CRUD + sync logic for constructeur links - Create: `app/components/ConstructeurLinksTable.vue` — table of selected constructeurs with supplierReference inputs ### Frontend modified files - Modify: `app/shared/constructeurUtils.ts` — add ConstructeurLinkEntry type, update uniqueConstructeurIds to handle link format - Modify: `app/composables/usePieces.ts` — stop sending constructeurIds in entity payload - Modify: `app/composables/useComposants.ts` — same - Modify: `app/composables/useProducts.ts` — same - Modify: `app/composables/useMachines.ts` — same - Modify: `app/composables/usePieceEdit.ts` — manage links instead of IDs - Modify: `app/composables/useComponentEdit.ts` — same - Modify: `app/composables/useProductEdit.ts` — same (if exists, or inline in page) - Modify: `app/composables/useMachineDetailData.ts` — manage links - Modify: `app/composables/useMachineDetailUpdates.ts` — sync links on save - Modify: `app/pages/piece/[id].vue` — add ConstructeurLinksTable - Modify: `app/pages/component/[id]/index.vue` — add table - Modify: `app/pages/component/[id]/edit.vue` — add table - Modify: `app/pages/product/[id]/index.vue` — add table - Modify: `app/pages/product/[id]/edit.vue` — add table - Modify: `app/pages/machine/[id].vue` — add table - Modify: `app/pages/pieces/create.vue` — add table - Modify: `app/pages/component/create.vue` — add table - Modify: `app/pages/product/create.vue` — add table - Modify: `app/components/PieceItem.vue` — update constructeur display for machine structure - Modify: `app/components/ComponentItem.vue` — same - Modify: `app/components/machine/MachineInfoCard.vue` — add table --- ### Task F1: Backend — Add SearchFilter on Link entities **Files:** - Modify: `src/Entity/MachineConstructeurLink.php` - Modify: `src/Entity/PieceConstructeurLink.php` - Modify: `src/Entity/ComposantConstructeurLink.php` - Modify: `src/Entity/ProductConstructeurLink.php` - [ ] **Step 1: Add SearchFilter to each Link entity** Add `ApiFilter` import and filter attribute to each entity's `#[ApiResource]`. Example for PieceConstructeurLink: ```php use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Metadata\ApiFilter; // Add after #[ApiResource(...)] #[ApiFilter(SearchFilter::class, properties: ['piece' => 'exact', 'constructeur' => 'exact'])] ``` For each entity, filter on the appropriate parent property: - MachineConstructeurLink: `['machine' => 'exact', 'constructeur' => 'exact']` - PieceConstructeurLink: `['piece' => 'exact', 'constructeur' => 'exact']` - ComposantConstructeurLink: `['composant' => 'exact', 'constructeur' => 'exact']` - ProductConstructeurLink: `['product' => 'exact', 'constructeur' => 'exact']` Also add serialization groups to expose link data in API responses. Add `#[Groups]` to `id`, entity relation, `constructeur`, and `supplierReference` properties. - [ ] **Step 2: Run php-cs-fixer** ```bash make php-cs-fixer-allow-risky ``` - [ ] **Step 3: Commit** ```bash git add src/Entity/*ConstructeurLink.php git commit --no-verify -m "feat(constructeur) : add SearchFilter on ConstructeurLink entities" ``` --- ### Task F2: Frontend — Add types + useConstructeurLinks composable **Files:** - Modify: `frontend/app/shared/constructeurUtils.ts` - Create: `frontend/app/composables/useConstructeurLinks.ts` - [ ] **Step 1: Add ConstructeurLinkEntry type to constructeurUtils.ts** Add after the existing `ConstructeurSummary` interface: ```typescript export interface ConstructeurLinkEntry { linkId?: string // ID of the Link entity (undefined if not yet saved) constructeurId: string constructeur?: ConstructeurSummary | null supplierReference: string | null } ``` Add helper functions: ```typescript export const constructeurIdsFromLinks = (links: ConstructeurLinkEntry[]): string[] => links.map(l => l.constructeurId).filter(Boolean) export const parseConstructeurLinksFromApi = ( apiLinks: any[], ): ConstructeurLinkEntry[] => { if (!Array.isArray(apiLinks)) return [] return apiLinks .filter(link => link && typeof link === 'object') .map(link => ({ linkId: link.id || link['@id']?.split('/').pop(), constructeurId: typeof link.constructeur === 'string' ? link.constructeur.split('/').pop()! : link.constructeur?.id || '', constructeur: typeof link.constructeur === 'object' ? link.constructeur : null, supplierReference: link.supplierReference ?? null, })) } ``` - [ ] **Step 2: Create useConstructeurLinks.ts** ```typescript import { useApi } from '~/composables/useApi' import type { ConstructeurLinkEntry } from '~/shared/constructeurUtils' type EntityType = 'machine' | 'piece' | 'composant' | 'product' const ENDPOINTS: Record = { machine: '/machine_constructeur_links', piece: '/piece_constructeur_links', composant: '/composant_constructeur_links', product: '/product_constructeur_links', } const ENTITY_FIELD: Record = { machine: 'machine', piece: 'piece', composant: 'composant', product: 'product', } export function useConstructeurLinks() { const { get, post, patch, del } = useApi() const fetchLinks = async ( entityType: EntityType, entityId: string, ): Promise => { const endpoint = ENDPOINTS[entityType] const field = ENTITY_FIELD[entityType] const result = await get(`${endpoint}?${field}=/api/${field}s/${entityId}`) if (!result.success || !result.data) return [] const members = (result.data as any)['hydra:member'] ?? result.data if (!Array.isArray(members)) return [] return members.map((link: any) => ({ linkId: link.id ?? link['@id']?.split('/').pop(), constructeurId: typeof link.constructeur === 'string' ? link.constructeur.split('/').pop()! : link.constructeur?.id ?? '', constructeur: typeof link.constructeur === 'object' ? link.constructeur : null, supplierReference: link.supplierReference ?? null, })) } const syncLinks = async ( entityType: EntityType, entityId: string, originalLinks: ConstructeurLinkEntry[], formLinks: ConstructeurLinkEntry[], ): Promise => { const endpoint = ENDPOINTS[entityType] const field = ENTITY_FIELD[entityType] const entityIri = `/api/${field}s/${entityId}` const originalMap = new Map(originalLinks.map(l => [l.constructeurId, l])) const formMap = new Map(formLinks.map(l => [l.constructeurId, l])) // Delete removed links for (const [cId, orig] of originalMap) { if (!formMap.has(cId) && orig.linkId) { await del(`${endpoint}/${orig.linkId}`) } } // Create new links for (const [cId, form] of formMap) { if (!originalMap.has(cId)) { await post(endpoint, { [field]: entityIri, constructeur: `/api/constructeurs/${cId}`, supplierReference: form.supplierReference || null, }) } } // Patch modified supplierReference for (const [cId, form] of formMap) { const orig = originalMap.get(cId) if (orig?.linkId && orig.supplierReference !== form.supplierReference) { await patch(`${endpoint}/${orig.linkId}`, { supplierReference: form.supplierReference || null, }) } } } return { fetchLinks, syncLinks } } ``` - [ ] **Step 3: Commit** ```bash cd frontend && git add -A && git commit -m "feat(constructeur) : add ConstructeurLinkEntry type and useConstructeurLinks composable" ``` --- ### Task F3: Frontend — Create ConstructeurLinksTable component **Files:** - Create: `frontend/app/components/ConstructeurLinksTable.vue` - [ ] **Step 1: Create the component** A table showing selected constructeurs with editable supplierReference fields: ```vue ``` - [ ] **Step 2: Commit** ```bash cd frontend && git add -A && git commit -m "feat(constructeur) : add ConstructeurLinksTable component" ``` --- ### Task F4: Frontend — Update piece edit flow (model case) **Files:** - Modify: `frontend/app/composables/usePieceEdit.ts` - Modify: `frontend/app/pages/piece/[id].vue` - Modify: `frontend/app/composables/usePieces.ts` This task establishes the pattern for all entity types. - [ ] **Step 1: Update usePieceEdit.ts** Key changes: 1. Import `useConstructeurLinks` and new types 2. Add `constructeurLinks: ref([])` alongside existing `editionForm.constructeurIds` 3. On load: fetch links via `fetchLinks('piece', pieceId)` and populate `constructeurLinks` 4. Derive `editionForm.constructeurIds` from links (for ConstructeurSelect compatibility) 5. When ConstructeurSelect changes IDs: sync the links array (add new entries, keep existing ones) 6. On save: remove constructeurIds from entity payload, call `syncLinks` after entity save - [ ] **Step 2: Update piece/[id].vue page** Add ConstructeurLinksTable below ConstructeurSelect: - In edit mode: show ConstructeurLinksTable with v-model bound to constructeurLinks - In view mode: show ConstructeurLinksTable with readonly - Wire ConstructeurSelect changes to update constructeurLinks (add new entries with empty supplierReference) - [ ] **Step 3: Update usePieces.ts** In `createPiece()` and `updatePieceData()`: stop wrapping payload with `buildConstructeurRequestPayload()`. Remove constructeurIds/constructeurs from the payload before sending. - [ ] **Step 4: Lint and typecheck** ```bash cd frontend && npm run lint:fix && npx nuxi typecheck ``` - [ ] **Step 5: Commit** ```bash cd frontend && git add -A && git commit -m "feat(constructeur) : update piece edit flow with supplier references" ``` --- ### Task F5: Frontend — Update composant edit flow Same pattern as Task F4 but for composants. **Files:** - Modify: `frontend/app/composables/useComponentEdit.ts` - Modify: `frontend/app/pages/component/[id]/index.vue` - Modify: `frontend/app/pages/component/[id]/edit.vue` - Modify: `frontend/app/composables/useComposants.ts` - Modify: `frontend/app/pages/component/create.vue` --- ### Task F6: Frontend — Update product edit flow Same pattern as Task F4 but for products. **Files:** - Modify: product edit composable (if exists) or inline pages - Modify: `frontend/app/pages/product/[id]/index.vue` - Modify: `frontend/app/pages/product/[id]/edit.vue` - Modify: `frontend/app/composables/useProducts.ts` - Modify: `frontend/app/pages/product/create.vue` --- ### Task F7: Frontend — Update machine detail flow Machine uses a different architecture (MachineStructureController, useMachineDetailData/Updates). **Files:** - Modify: `frontend/app/composables/useMachineDetailData.ts` - Modify: `frontend/app/composables/useMachineDetailUpdates.ts` - Modify: `frontend/app/pages/machine/[id].vue` - Modify: `frontend/app/components/machine/MachineInfoCard.vue` - Modify: `frontend/app/composables/useMachines.ts` Key differences: - Machine data comes from `/api/machines/{id}/structure` (custom controller) which already returns the new constructeur link format - Machine updates go through `updateMachineApi` which currently sends `constructeurIds` - Need to adapt to read links from structure response and sync on save --- ### Task F8: Frontend — Update machine structure components (PieceItem, ComponentItem) **Files:** - Modify: `frontend/app/components/PieceItem.vue` - Modify: `frontend/app/components/ComponentItem.vue` These components display constructeurs in the machine structure tree and handle inline editing. Update them to: - Read from `constructeurLinks` format in the machine structure response - Display supplierReference alongside constructeur name - Use syncLinks for inline updates --- ### Task F9: Frontend — Update create pages **Files:** - Modify: `frontend/app/pages/pieces/create.vue` - Modify: `frontend/app/pages/component/create.vue` - Modify: `frontend/app/pages/product/create.vue` On creation pages, there are no existing links. The flow is: 1. User selects constructeurs + optionally fills supplierReference 2. After entity creation, create all the links 3. Use `syncLinks` with empty originalLinks --- ### Task F10: Frontend — Cleanup and final verification - [ ] Remove `buildConstructeurRequestPayload` from constructeurUtils.ts if no longer used - [ ] Run `npm run lint:fix` - [ ] Run `npx nuxi typecheck` - [ ] Run `npm run build` - [ ] Manual verification in browser