From 8aa1acd8ef8e03eb3f05b801a2e9cbc807d1bee7 Mon Sep 17 00:00:00 2001 From: ilhom Date: Tue, 30 Jun 2026 10:57:21 +0700 Subject: [PATCH] fix(finance): sort cost breakdown groups by min display_order of members Previously groups were ordered by first-occurrence, causing Machine to appear before Raw Material. Now groups are sorted by the minimum display_order among their member parameters, matching the reference Excel group sequence. Co-Authored-By: Claude Sonnet 4.6 --- .../cost-results/cost-breakdown-modal.tsx | 77 ++++++++++++++----- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/src/components/finance/cost-results/cost-breakdown-modal.tsx b/src/components/finance/cost-results/cost-breakdown-modal.tsx index 788dc3d..5925674 100644 --- a/src/components/finance/cost-results/cost-breakdown-modal.tsx +++ b/src/components/finance/cost-results/cost-breakdown-modal.tsx @@ -154,21 +154,53 @@ function SummaryTab({ breakdown, productSysId }: { breakdown: CostBreakdown; pro const s = breakdown.summary const snapshotEntries = Object.entries(breakdown.paramSnapshot) - // Fetch parameter definitions for this product to get the proper displayGroup labels. + // Fetch parameter definitions for this product (already sorted by display_group, + // capp_display_order, param_code from the backend query). const { data: requiredParams = [] } = useProductRequiredParams(productSysId) - const groupMap = new Map(requiredParams.map((p) => [p.paramCode, p.displayGroup || "Other"])) - // Group snapshot entries by displayGroup from the parameter master. - // Fall back to "Other" for params not found in the map. - const grouped: Record = {} + // Walk requiredParams in backend order (sorted by display_order, param_code). + // Params in the snapshot but NOT in requiredParams go at the end ungrouped. + const snapshotMap = new Map(snapshotEntries) + const seenCodes = new Set() + const orderedEntries: Array<{ code: string; value: string; group: string; order: number }> = [] + + for (const p of requiredParams) { + const value = snapshotMap.get(p.paramCode) + if (value !== undefined) { + orderedEntries.push({ code: p.paramCode, value, group: p.displayGroup, order: p.displayOrder }) + seenCodes.add(p.paramCode) + } + } + // Remaining snapshot entries not covered by requiredParams — ungrouped at end for (const [k, v] of snapshotEntries) { - const group = groupMap.get(k) || "General" - if (!grouped[group]) grouped[group] = [] - grouped[group].push([k, v]) + if (!seenCodes.has(k)) { + orderedEntries.push({ code: k, value: v, group: "", order: 9999 }) + } } - const groupEntries = Object.entries(grouped).sort(([a], [b]) => - a === "General" ? 1 : b === "General" ? -1 : a.localeCompare(b), - ) + + // Group while collecting the minimum display_order seen per group. + // This lets us sort groups by their first-appearing param's order (not alphabetically). + const groupMinOrder: Record = {} + const grouped: Record> = {} + for (const { code, value, group, order } of orderedEntries) { + if (!grouped[group]) { + grouped[group] = [] + groupMinOrder[group] = order + } else { + groupMinOrder[group] = Math.min(groupMinOrder[group], order) + } + grouped[group].push([code, value]) + } + // Sort groups by their minimum display_order: named groups first (by order), ungrouped last + const groupEntries = Object.keys(grouped) + .sort((a, b) => { + if (!a && !b) return 0 + if (!a) return 1 // ungrouped after all named groups + if (!b) return -1 + return (groupMinOrder[a] ?? 9999) - (groupMinOrder[b] ?? 9999) + }) + .map((g) => [g, grouped[g]] as [string, [string, string][]]) + const namedGroupCount = groupEntries.filter(([g]) => g !== "").length return (
@@ -215,20 +247,27 @@ function SummaryTab({ breakdown, productSysId }: { breakdown: CostBreakdown; pro Parameter snapshot - {snapshotEntries.length} params · {groupEntries.length} groups + {snapshotEntries.length} params + {namedGroupCount > 0 ? ` · ${namedGroupCount} groups` : ""} - + {snapshotEntries.length === 0 ? (

No parameters captured.

) : ( - groupEntries.map(([groupName, entries]) => ( -
-

- {groupName} - ({entries.length}) -

+ groupEntries.map(([groupName, entries], idx) => ( +
0 ? "mt-5" : ""}> + {groupName !== "" && ( +
+ + {groupName} + + + {entries.length} + +
+ )}
{entries.map(([k, v]) => (