diff --git a/fe-admin/src/components/pe/PeDetailTabs.tsx b/fe-admin/src/components/pe/PeDetailTabs.tsx index bae82d4..81ca743 100644 --- a/fe-admin/src/components/pe/PeDetailTabs.tsx +++ b/fe-admin/src/components/pe/PeDetailTabs.tsx @@ -391,6 +391,10 @@ function OpinionBox({ // Layout 5A: header "Bước N — Phòng X" badge + grid-cols-2 cho N approvers // (wrap nếu N>2). Admin override badge khi SignedByUserId !== ApproverUserId. +// Session 20 Chunk C: gộp opinions đồng cấp cùng Phòng → 1 box / Step. +// Trước: 1 box / NV (mỗi Level × mỗi approver = 1 OpinionBox). +// Bây giờ: 1 box / Step (Phòng), chỉ hiển thị các NV ĐÃ duyệt — opinion entries +// sort theo Cấp tăng dần. NV chưa duyệt KHÔNG hiển thị (user yêu cầu Q3=a). function LevelOpinionsSectionV2({ ev }: { ev: PeDetailBundle }) { const flow = ev.approvalFlow const opinions = ev.levelOpinions @@ -404,97 +408,90 @@ function LevelOpinionsSectionV2({ ev }: { ev: PeDetailBundle }) { } return ( -
+
{flow.steps.map(step => { const totalApprovers = step.levels.reduce((n, l) => n + l.approvers.length, 0) + const stepOpinions = opinions + .filter(o => o.stepOrder === step.order) + .slice() + .sort((a, b) => a.levelOrder - b.levelOrder || a.signedAt.localeCompare(b.signedAt)) return ( -
-
- - Bước {step.order} — {step.name} - - {step.departmentName && ( - - {step.departmentName} - - )} - {totalApprovers > 1 && ( - - ({totalApprovers} người duyệt) - - )} -
-
- {step.levels.flatMap(level => - level.approvers.map(approver => { - const opinion = opinions.find(o => - o.stepOrder === step.order - && o.levelOrder === level.order - && o.approverUserId === approver.userId, - ) ?? null - return ( - - ) - }), - )} -
-
+ ) })}
) } -function LevelOpinionBox({ - levelOrder, - approverUserId, - approverName, - opinion, +function StepOpinionsBox({ + stepOrder, stepName, departmentName, totalApprovers, opinions, }: { - levelOrder: number - approverUserId: string - approverName: string - opinion: PeLevelOpinion | null + stepOrder: number + stepName: string + departmentName?: string | null + totalApprovers: number + opinions: PeLevelOpinion[] }) { - const isSigned = !!opinion - const isAdminOverride = isSigned && opinion!.signedByUserId !== approverUserId - return ( -
-
+
+
+ + Bước {stepOrder} — {stepName} + + {departmentName && ( + + {departmentName} + + )} + + {opinions.length}/{totalApprovers} đã duyệt + +
+
+ {opinions.length === 0 ? ( +
— Chưa có ý kiến duyệt.
+ ) : ( +
+ {opinions.map(o => )} +
+ )} +
+
+ ) +} + +function StepOpinionEntry({ opinion }: { opinion: PeLevelOpinion }) { + const isAdminOverride = opinion.signedByUserId !== opinion.approverUserId + return ( +
+
-

- Cấp {levelOrder} — {approverName} -

+
+ {opinion.approverFullName} + + Cấp {opinion.levelOrder} + +
{isAdminOverride && (
- ⚠ Admin {opinion!.signedByFullName} duyệt thay + ⚠ Admin {opinion.signedByFullName} duyệt thay
)}
- {isSigned && ( - - Đã duyệt - - )} + + {new Date(opinion.signedAt).toLocaleString('vi-VN')} +
-
- {opinion?.comment ?? — chưa duyệt} +
+ {opinion.comment}
- {isSigned && ( -
- {new Date(opinion!.signedAt).toLocaleString('vi-VN')} -
- )}
) } diff --git a/fe-user/src/components/pe/PeDetailTabs.tsx b/fe-user/src/components/pe/PeDetailTabs.tsx index bae82d4..81ca743 100644 --- a/fe-user/src/components/pe/PeDetailTabs.tsx +++ b/fe-user/src/components/pe/PeDetailTabs.tsx @@ -391,6 +391,10 @@ function OpinionBox({ // Layout 5A: header "Bước N — Phòng X" badge + grid-cols-2 cho N approvers // (wrap nếu N>2). Admin override badge khi SignedByUserId !== ApproverUserId. +// Session 20 Chunk C: gộp opinions đồng cấp cùng Phòng → 1 box / Step. +// Trước: 1 box / NV (mỗi Level × mỗi approver = 1 OpinionBox). +// Bây giờ: 1 box / Step (Phòng), chỉ hiển thị các NV ĐÃ duyệt — opinion entries +// sort theo Cấp tăng dần. NV chưa duyệt KHÔNG hiển thị (user yêu cầu Q3=a). function LevelOpinionsSectionV2({ ev }: { ev: PeDetailBundle }) { const flow = ev.approvalFlow const opinions = ev.levelOpinions @@ -404,97 +408,90 @@ function LevelOpinionsSectionV2({ ev }: { ev: PeDetailBundle }) { } return ( -
+
{flow.steps.map(step => { const totalApprovers = step.levels.reduce((n, l) => n + l.approvers.length, 0) + const stepOpinions = opinions + .filter(o => o.stepOrder === step.order) + .slice() + .sort((a, b) => a.levelOrder - b.levelOrder || a.signedAt.localeCompare(b.signedAt)) return ( -
-
- - Bước {step.order} — {step.name} - - {step.departmentName && ( - - {step.departmentName} - - )} - {totalApprovers > 1 && ( - - ({totalApprovers} người duyệt) - - )} -
-
- {step.levels.flatMap(level => - level.approvers.map(approver => { - const opinion = opinions.find(o => - o.stepOrder === step.order - && o.levelOrder === level.order - && o.approverUserId === approver.userId, - ) ?? null - return ( - - ) - }), - )} -
-
+ ) })}
) } -function LevelOpinionBox({ - levelOrder, - approverUserId, - approverName, - opinion, +function StepOpinionsBox({ + stepOrder, stepName, departmentName, totalApprovers, opinions, }: { - levelOrder: number - approverUserId: string - approverName: string - opinion: PeLevelOpinion | null + stepOrder: number + stepName: string + departmentName?: string | null + totalApprovers: number + opinions: PeLevelOpinion[] }) { - const isSigned = !!opinion - const isAdminOverride = isSigned && opinion!.signedByUserId !== approverUserId - return ( -
-
+
+
+ + Bước {stepOrder} — {stepName} + + {departmentName && ( + + {departmentName} + + )} + + {opinions.length}/{totalApprovers} đã duyệt + +
+
+ {opinions.length === 0 ? ( +
— Chưa có ý kiến duyệt.
+ ) : ( +
+ {opinions.map(o => )} +
+ )} +
+
+ ) +} + +function StepOpinionEntry({ opinion }: { opinion: PeLevelOpinion }) { + const isAdminOverride = opinion.signedByUserId !== opinion.approverUserId + return ( +
+
-

- Cấp {levelOrder} — {approverName} -

+
+ {opinion.approverFullName} + + Cấp {opinion.levelOrder} + +
{isAdminOverride && (
- ⚠ Admin {opinion!.signedByFullName} duyệt thay + ⚠ Admin {opinion.signedByFullName} duyệt thay
)}
- {isSigned && ( - - Đã duyệt - - )} + + {new Date(opinion.signedAt).toLocaleString('vi-VN')} +
-
- {opinion?.comment ?? — chưa duyệt} +
+ {opinion.comment}
- {isSigned && ( -
- {new Date(opinion!.signedAt).toLocaleString('vi-VN')} -
- )}
) }