Dashboard API Integration Audit
Generated: Thu Jun 4 13:55:40 +07 2026
Source: audit.json (machine-readable, same directory)
Summary
| Status | Count | Meaning |
|---|---|---|
| EXISTS | 5 | Wired or already implemented — can integrate now |
| PARTIAL | 1 | Partial implementation; read side works, gaps documented |
| GAP | 23 | BE endpoint does not exist — hand to backend team |
| misWired | 1 | Real path bug — wrong URL in the RTK slice |
EXISTS units: scanners, audits, companies-public, invoices, notification-prefs
PARTIAL units: collections-orders
misWired units: userApi.ts:97 (upload-avatar)
1. EXISTS — Integrate Now
scanners
Wire MAPPERS["scanners"] in apiAdapter.ts to scannersApi.endpoints.getScanners.initiate. The RTK slice at apps/dashboard/src/features/scanners/services/scannersApi.ts already has all 5 endpoints.
| Method | Path | RTK Hook | Canvas Source |
|---|---|---|---|
| GET | company/scanners | useGetScannersQuery | scannersApi.ts:44-53 |
| POST | company/scanner | useCreateScannerMutation | scannersApi.ts:55-62 |
| POST | post-auditor-update-scanner | useUpdateScannerMutation | scannersApi.ts:95-102 |
| PUT | company/scanner/transfer/:id | useTransferScannerMutation | scannersApi.ts:103-110 |
| GET | get-list-scanner-status-condition | useGetScannerStatusConditionQuery | scannersApi.ts:63-94 |
Adapter mapper (add to apps/dashboard/src/lib/apiAdapter.ts):
// Add imports:
// import scannersApi from "@/features/scanners/services/scannersApi";
// import type { BeScanner } from "@/features/scanners/services/scannersApi";
function mapBeScannerToAdapterRow(s: BeScanner): Record<string, unknown> {
return {
id: String(s.id),
serial_number: s.code,
model: s.type ?? "",
name: s.name,
status: s.status ?? "active",
status_id: s.status_id,
condition: s.condition ?? "",
condition_id: s.condition_id,
type: s.type ?? "",
type_id: s.type_id,
auditor: s.auditor ?? null,
assigned_date: s.assigned_date ?? null,
created_at: s.assigned_date ?? "",
};
}
MAPPERS["scanners"] = {
fetch: async (filters) => {
try {
const result = await store
.dispatch(
scannersApi.endpoints.getScanners.initiate({
page: 1,
number_per_page: 100,
sort: "desc",
sort_by: "assigned_date",
}),
)
.unwrap();
const rows = (result.data ?? []).map(mapBeScannerToAdapterRow);
return { data: applyFilters(rows, filters) as unknown[], error: null };
} catch (error) {
console.error("MAPPERS[scanners] fetch failed:", error);
return { data: null, error: error as Error };
}
},
};
// scanner_facility_assignments: GAP — noop stub stays until BE ships endpoint.
Caveats:
useScanners.tsalready bypasses the adapter (callsuseGetScannersQuerydirectly). The mapper covers only residualsupabase.from("scanners")call sites (e.g.confidence.tsindirect paths).scanner_facility_assignmentsis a confirmed GAP — see BE spec below. Scanners page degrades gracefully: facility column shows “Pending assignment”. Confidence score underweights scanner count (always 0) until BE ships.- “last used by” column uses a mock hash function — no BE audit-log source. Intentional per inline comment.
- No
DELETE /company/scannerendpoint exists. Delete button already disabled in AdminScanners.tsx. transferScannertakesuser_id(numeric), NOTfacility_id. The “Assign to Facility” dialog is intentionally disabled (BE-blocked).
audits
MAPPERS["audits"] is already wired at apiAdapter.ts:226-254. The existing mapper is correct. One potential fix noted in caveats.
| Method | Path | RTK Hook | Canvas Source |
|---|---|---|---|
| GET | sat-audits | useGetSatAuditsQuery | auditApi.ts:17 (SAT), auditsApi.ts:70 (Dashboard) |
| POST | sat-audit | useSubmitSatAuditMutation | auditApi.ts:33 (SAT) |
| GET | user/audits/:uid/attribute-template | useGetOrderProcessAuditTemplateQuery | canvas auditApi.ts:301 |
Adapter mapper (already live — reproduced for reference):
function mapSatAuditToRow(a: SatAuditItem): Record<string, unknown> {
const poNumber = (a.reference as any)?.purchase_order
?? (a.reference as any)?.po_number
?? null;
return {
id: String(a.id),
session_id: a.id,
ft_id: a.fibretrace_id ?? null,
blockchain_guid: null,
result: a.status ?? null,
notes: poNumber ? `PO: ${poNumber}` : (a.reference as any)?.notes ?? null,
created_at: a.created_at,
deleted_at: null,
};
}
MAPPERS["audits"] = {
fetch: async (filters) => {
try {
const result = await store
.dispatch(
auditsApi.endpoints.getSatAudits.initiate({
page: 1,
number_per_page: 100,
sort: "desc",
sort_by: "created_at",
}),
)
.unwrap();
const rows = (result.data ?? []).map(mapSatAuditToRow);
return { data: applyFilters(rows, filters) as unknown[], error: null };
} catch (error) {
console.error("MAPPERS[audits] fetch error:", error);
return { data: null, error: error as Error };
}
},
};
Caveats:
- Reference field name discrepancy: dashboard’s
SatAuditListReferenceusespo_numberbut canonical SAT usespurchase_order. The fixed mapper above reads both with a fallback. Verify actual BE response field before deploying. session_iduses audit’s ownidas stand-in (sat-audits has no real session join). Sessions column shows “—” — correct behavior.blockchain_guidis always null. AuditsTable does not render this column.resultmaps BE “PROCESSED”/“PENDING” to Lovable’s “pass”/“fail”/“warning” column. Badge color will fall through to “warning” style — semantic mismatch until BE exposes pass/fail/warning.- All AdminVerifications mutations already stub-blocked with
toast.info(). No call-site RTK migration needed.
companies-public
MAPPERS["companies"] and companiesPublicApi.ts are already live. No action needed.
| Method | Path | RTK Hook | Canvas Source |
|---|---|---|---|
| GET | public-companies | useGetPublicCompaniesQuery | companyApi.ts:210-217 |
Adapter mapper (already live — DO NOT re-apply):
function mapPublicCompanyToRow(c: PublicCompanyBeRow): Record<string, unknown> {
return {
id: String(c.uid ?? c.id ?? ""),
name: c.name ?? "",
confidence_score: null, // no BE source; Lovable-only field
confidence_rating: null, // no BE source; Lovable-only field
confidence_summary: null, // no BE source; Lovable-only field
};
}
MAPPERS["companies"] = {
fetch: async (filters) => {
try {
const result = await store
.dispatch(companiesPublicApi.endpoints.getPublicCompanies.initiate({}))
.unwrap();
const rows = (result.data ?? []).map(mapPublicCompanyToRow);
return { data: applyFilters(rows, filters) as unknown[], error: null };
} catch (error) {
return { data: null, error: error as Error };
}
},
};
Caveats:
confidence_score/confidence_ratinghard-coded null — BE returns onlyuid+name. Confidence UI remains empty until BE adds those fields.- No mutations target
companies.usePartners.tsmutations targetcompany_partners— separate scope. - Task is DONE — no new files or edits required.
invoices
MAPPERS["invoices"] and billingApi.ts are already wired. No action needed for fetch path.
| Method | Path | RTK Hook | Canvas Source |
|---|---|---|---|
| GET | company/invoices | useGetCompanyInvoicesQuery | billingApi.ts:15-24 |
| GET | company/invoices/:code | useGetInvoiceByCodeQuery | billingApi.ts:26-31 |
Adapter mapper (already live — reproduced for reference):
function mapBeInvoiceToRow(inv: BeInvoice): Record<string, unknown> {
return {
id: String(inv.id),
invoice_number: inv.code, // BE: code -> Lovable: invoice_number
purchase_order: inv.po_number ?? "",
amount: Number(inv.total ?? 0), // BE: total (string) -> Lovable: amount (number)
status: inv.status ?? "",
issued_at: inv.created_at ?? "", // BE: created_at -> Lovable: issued_at
pdf_url: "", // no BE source; Lovable-only field
notes: "", // no BE source; Lovable-only field
companies: null, // list endpoint does not join company name
};
}
MAPPERS["invoices"] = {
fetch: async (filters) => {
try {
const result = await store
.dispatch(
billingApi.endpoints.getCompanyInvoices.initiate({
page: 1,
number_per_page: 100,
sort: "desc",
sort_by: "created_at",
search: "",
}),
)
.unwrap();
const rows = (result.data ?? []).map(mapBeInvoiceToRow);
return { data: applyFilters(rows, filters) as unknown[], error: null };
} catch (error) {
return { data: null, error: error as Error };
}
},
};
Caveats:
- Already implemented — no new files or edits needed for the fetch path.
companies(name)join: list endpoint does not join company info. Column always shows “—” for single-company users.pdf_urlandnotesare Lovable-only fields — mapped to empty strings.- Admin invoice CRUD mutations already throw
not_implemented. No canvas equivalent. - Verify BE returns paginated shape
{ data: Invoice[]; meta: { total: number } }— if flatInvoice[], change mapper to readresult as Invoice[].
notification-prefs
SettingsNotifications.tsx is fully wired via useGetUserQuery + useUpdateNotificationEmailsMutation. No action needed.
| Method | Path | RTK Hook | Canvas Source |
|---|---|---|---|
| GET | user | useGetUserQuery | userApi.ts:55-60 |
| PUT | v2/user | useUpdateNotificationEmailsMutation | userApi.ts:70-74 |
Caveats:
- notification_preferences / SettingsNotifications.tsx is FULLY WIRED. The apiAdapter no-op stub is a crash-guard only.
notification_templatesis a genuine GAP (see Section 3 below).PUT /api/v2/user: VITE_API_URL must be the bare API origin without/api/v1suffix. Verify.env.is_company_admingating: canvas gates 3 admin-only OPT_IN keys. Dashboard currently shows all 10 unconditionally. Wireuser.is_company_adminif product requires gating.
2. PARTIAL — Integrate Read Part, Flag the Rest
collections-orders
Collections list is already wired (MAPPERS["collections"] at apiAdapter.ts:388-408). Sessions and full verifications are GAPs requiring BE extension.
What works:
| Method | Path | RTK Hook | Canvas Source | Status |
|---|---|---|---|---|
| GET | orders | useGetOrdersQuery | orderApi.ts:32 | WIRED (MAPPERS[“collections”]) |
| GET | orders/:orderCode | useGetOrderByCodeQuery | orderApi.ts:278 | EXISTS |
| GET | orders/:orderCode/processes | useGetOrderProcessesQuery | processesApi.ts:24 | EXISTS (basis for verifications mapper) |
| GET | orders/:orderCode/processes/:processId | useGetOneProcessQuery | processesApi.ts:29 | EXISTS (needs slice extension) |
What is missing / field-set gaps:
MAPPERS["verifications"]: can partially wire toGET /orders/:code/processes. Filtered calls (.eq("collection_id", orderCode)) will work; unfiltered calls return[].nameandstatusfields are absent fromProcessProcesList— stub with safe defaults until BE extends the response.MAPPERS["sessions"]: GAP.ProcessProcesListis missingsession_code,conducted_at,fibre_detected,pigment_id,facility_id,auditor_name. Stub returns[].MAPPERS["session_shares"]: GAP. Returns[].
Adapter changes (apply these):
// MAPPERS["collections"] is ALREADY wired — DO NOT replace.
// MAPPERS["verifications"] — wire to GET /orders/:code/processes.
MAPPERS["verifications"] = {
fetch: async (filters) => {
try {
const orderCode = filters.eq?.["collection_id"] as string | undefined;
if (!orderCode) {
return { data: [], error: null };
}
const result = await store
.dispatch(
collectionsApi.endpoints.getOrderProcesses.initiate(orderCode),
)
.unwrap();
const rows = (result.data ?? []).map((p: OrderProcess) =>
mapProcessToVerification(p, orderCode),
);
return { data: applyFilters(rows, filters) as unknown[], error: null };
} catch (error) {
console.error("MAPPERS[verifications] fetch error:", error);
return { data: null, error: error as Error };
}
},
};
function mapProcessToVerification(
p: OrderProcess,
orderCode: string,
): Record<string, unknown> {
return {
id: String(p.id),
collection_id: orderCode,
name: (p as any).name ?? `Process ${p.id}`,
status: (p as any).status != null ? String((p as any).status) : "active",
};
}
// MAPPERS["sessions"] — GAP stub.
MAPPERS["sessions"] = {
fetch: async (_filters) => ({ data: [], error: null }),
};
// MAPPERS["session_shares"] — GAP stub.
MAPPERS["session_shares"] = {
fetch: async () => ({ data: [], error: null }),
};
Slice extension (add getOneProcess to collectionsApi.ts):
getOneProcess: builder.query<{ data: OrderProcess }, { orderCode: string; processId: string | number }>({
query: ({ orderCode, processId }) => ({
url: `orders/${orderCode}/processes/${processId}`,
method: 'GET',
}),
providesTags: (_result, _error, { orderCode, processId }) => [
{ type: 'Verifications', id: `${orderCode}-process-${processId}` },
],
}),
Call-site mutations that need BE-pending toasts:
CreateCollectionFlow.tsx:151-160—supabase.from('collections').insert(...)— adapter throws. Replace with BE-pending toast until BE confirms POST /orders field mapping.CreateCollectionFlow.tsx:174-177—supabase.from('verifications').update({ collection_id })— GAP mutation. BE-pending toast.AddVerificationsModal.tsx:137-140— same GAP mutation. BE-pending toast.
BE spec for gaps:
TABLE: sessions
Required columns (from SessionDetail.tsx, CollectionDetail.tsx, useVerifications.ts):
id uuid PK
session_code text
conducted_at timestamptz
fibre_detected text
scan_count int
facility_id uuid FK -> facilities.id
verification_id uuid FK -> verifications.id
production_record_id uuid nullable FK
auditor_name text
pigment_id uuid nullable FK -> pigment_ids.id
deleted_at timestamptz nullable
Nearest BE candidate: GET /orders/:code/processes/:id (OrderProcessDetailData)
Field-set mismatch: BE has process_scanned_date, process_scanned_by, process_name, etc.
UI needs: session_code, conducted_at, fibre_detected, facility_id, verification_id, pigment_id, auditor_name
BE spec option A: extend GET /orders/:code/processes/:id response to include a `session` sub-object.
BE spec option B: new endpoint GET /orders/:code/sessions returning the above shape per-row.
TABLE: verifications
Required columns: id, collection_id, name, status
Nearest BE candidate: GET /orders/:code/processes (ProcessProcesList)
Field-set mismatch: BE has id, due_date, scan_count, auditor, manufacturer_process; missing name, status.
BE spec: extend GET /orders/:code/processes items to include { name, status, collection_id (= order_code) }.
3. GAP — Hand to Backend Team
These 23 tables have no confirmed Fibretrace REST endpoint. The adapter stubs return [] to prevent crashes.
| Table | Affected Consumers | Notes |
|---|---|---|
| sessions | CollectionDetail.tsx, SessionDetail.tsx, DirectorySearchModal.tsx, AddVerificationsModal.tsx, LinkVerificationModal.tsx, useVerifications.ts, useGuideSignals.ts, ProducerDashboard.tsx | Nearest BE: /orders/:code/processes/:id but field-set mismatch. See PARTIAL section for spec. |
| session_shares | useVerifications.ts, AdminVerifications.tsx | Lovable-only many-to-many share join. Canvas POST /user/audits/:uid/send has different semantics. |
| verifications | CollectionDetail.tsx, VerificationPreview.tsx, CreateCollectionFlow.tsx, AddVerificationsModal.tsx, useVerifications.ts | Lovable join between collections and sessions. Partial wire possible via processes — see PARTIAL section. |
| fibre_programmes | useProgrammes.ts, useEvidenceRequests.ts, useProgrammeHeatmap.ts, FibreCreation.tsx, AdminProgrammes.tsx | Entirely new BE domain. MAPPERS[“fibre_programmes”] has TODO: ‘wire to /programs endpoint once BE confirms’. Zero /programs or /fibre-programmes routes found across all 6 canvas inventory files. |
| programme_participants | useProgrammes.ts, AdminProgrammes.tsx | Part of Programmes domain. No canvas or legacy endpoint. |
| programme_producers | useProgrammes.ts, AdminProgrammes.tsx | Part of Programmes domain. No canvas or legacy endpoint. |
| programme_producer_facilities | useProgrammes.ts, AdminProgrammes.tsx | Part of Programmes domain. No canvas or legacy endpoint. |
| programme_reservations | useProgrammes.ts | Part of Programmes domain. No canvas or legacy endpoint. |
| pigment_ids | useProgrammes.ts, useProgrammeHeatmap.ts, AdminPigmentIds.tsx, SessionDetail.tsx | Lovable-native fibre pigment tracking. No canvas or legacy /pigment-ids found. |
| pigment_orders | useProgrammes.ts, useProgrammeHeatmap.ts | Lovable-native pigment ordering. No canvas or legacy endpoint. |
| pigment_company_assignments | useProgrammes.ts | Lovable-native pigment-to-company join. No canvas or legacy endpoint. |
| production_records | FibreCreation.tsx, LogProductionModal.tsx, useGuideSignals.ts, AdminFibreProduction.tsx | No /production-records endpoint. Note: sduApi.ts correctly wires GET /sdu/devices (Nathan spec 2026-05-29) — separate from this table. |
| production_claims | FibreCreation.tsx, usePersona.ts, AdminFibreProduction.tsx | No /production-claims endpoint. |
| sliver_delivery_units | SDU.tsx, AdminSDU.tsx, useGuideSignals.ts | SDU CRUD only. READ side is already wired via sduApi.ts -> GET /sdu/devices (called directly via hooks, not through this adapter). Noop stub remains for Lovable code paths. |
| evidence_requests | useEvidenceRequests.ts, EvidencePack.tsx | Lovable-native evidence pack workflow. No /evidence-requests endpoint. |
| evidence_request_shares | useEvidenceRequests.ts | Lovable-native evidence share join. No endpoint. |
| facility_licenses | AdminFacilities.tsx, AdminLicensing.tsx | No /facility-licenses endpoint. |
| onboarding_tasks | AdminOnboarding.tsx | No /onboarding-tasks endpoint. |
| company_details | AdminCompanies.tsx, EvidencePack.tsx | Canvas closest: POST /company-meta but semantics differ (company_code field + direct CRUD). Multiple candidates, no clean 1:1. |
| company_users | AdminUsers.tsx, AdminCompanies.tsx | Canvas GET /users exists (teamApi.ts) but admin insert/update/delete semantics differ from useSetUserStatusMutation/useDeleteUserMutation/useRestoreUserMutation. |
| notification_templates_mutations | AdminNotifications.tsx | Admin CRUD on notification templates. No canvas write endpoint. Read path (GET /service-tab) separately flagged as fixable. Supabase shape: { id, created_at, description, is_default_enabled, label }. BE spec: GET/POST /notification-templates, PUT/DELETE /notification-templates/:id. |
| scanner_facility_assignments | ProducerDashboard.tsx, useScanners.ts | Lovable-only scanner-facility join. Canvas GET /company/scanners returns count_assigned_scanner in meta but no flat join table endpoint. BE spec: GET /company/scanner-facility-assignments { data: Array<{ id, scanner_id, facility_id, created_at }> }; POST body { scanner_id, facility_id }; DELETE /:id. |
| companies_confidence_score | useConfidence.ts, useVerifications.ts, useProgrammeHeatmap.ts, AdminCronJobs.tsx | PARTIAL: MAPPERS[“companies”] is wired to GET /public-companies (returns uid + name). confidence_score and confidence_rating are Lovable-only and resolve to null — no Fibretrace endpoint returns these fields. |
4. misWired — Real Path Bugs
| Slice File | Bad Path | Evidence | Suggested Fix |
|---|---|---|---|
apps/dashboard/src/services/userApi.ts:97 |
POST upload-avatar |
Not present in any of the 6 canvas inventory files (01-06-*.md), not in legacy storefront/b2c/fim API files, flagged in gap-scan-2026-05-31/canvas-missed.json as ‘new endpoint not in scanned 24’. Canvas avatar/logo uploads go through POST /media (commonApi.ts:60). Consumed by SettingsAccount.tsx:126. | POST /media (commonApi.ts useUploadToS3Mutation) — verify with BE whether /upload-avatar is a real separate endpoint or should route to /media |
5. Recommended Order
Work in this sequence to maximize unblocked wiring and surface BE gaps early:
- Fix misWired —
userApi.ts:97upload-avatar path. 1 file, low risk. Verify with BE first. - Wire scanners adapter mapper —
apiAdapter.ts: addMAPPERS["scanners"]block + imports. Slice already complete. Unblocks confidence.ts fallback paths. - Wire verifications adapter mapper —
apiAdapter.ts: addMAPPERS["verifications"]+mapProcessToVerification+ session/session_shares stubs. ExtendcollectionsApi.tswithgetOneProcess. Unblocks CollectionDetail.tsx filtered views. - Fix mutation crash sites —
CreateCollectionFlow.tsxandAddVerificationsModal.tsx: replace supabase mutation calls with BE-pending toasts. - notification_templates read (worklist.fixable) — new RTK endpoint for GET /service-tab -> wire MAPPERS[“notification_templates”] read. Admin mutations remain BE-blocked.
- Hand BE spec to backend — sessions, verifications field extension, scanner_facility_assignments, notification_templates CRUD, fibre_programmes (see Section 3 above for full specs).
- After BE ships each endpoint: wire the corresponding adapter mapper + RTK slice + remove noop stub.