diff --git a/fe-admin/src/components/contracts/ContractDetailContent.tsx b/fe-admin/src/components/contracts/ContractDetailContent.tsx index b6ca917..cd5ce88 100644 --- a/fe-admin/src/components/contracts/ContractDetailContent.tsx +++ b/fe-admin/src/components/contracts/ContractDetailContent.tsx @@ -182,6 +182,47 @@ export function ContractDetailContent({ + {/* [Plan B S29 2026-05-22 Chunk E3] Section 5 — Ý kiến cấp duyệt V2 dynamic. + Mirror PE LevelOpinionsSectionV2 pattern. Chỉ render khi V2 pin + (approvalWorkflowId set). V1 legacy contract KHÔNG hiển thị. */} + {c.approvalWorkflowId && ( +
+

+ + Ý kiến cấp duyệt (Quy trình V2) +

+ {(c.levelOpinions?.length ?? 0) === 0 ? ( +

Chưa có ý kiến — workflow vừa bắt đầu hoặc chưa có ai duyệt.

+ ) : ( + + )} +
+ )} + setActionOpen(false)} diff --git a/fe-admin/src/types/contracts.ts b/fe-admin/src/types/contracts.ts index 9025d16..6844810 100644 --- a/fe-admin/src/types/contracts.ts +++ b/fe-admin/src/types/contracts.ts @@ -171,4 +171,28 @@ export type ContractDetail = { comments: ContractComment[] attachments: ContractAttachment[] workflow: WorkflowSummary + // [Plan B S29 2026-05-22 Chunk E3] V2 workflow state — mirror PE pattern. + // Khi pin V2 (approvalWorkflowId set) → render Section 5 dynamic. + // V1 contract: 3 fields = null. + approvalWorkflowId: string | null + currentApprovalLevelOrder: number | null + levelOpinions: ContractLevelOpinion[] | null +} + +// [Plan B S29 2026-05-22 Chunk E3] Mirror BE ContractLevelOpinionDto 12 fields. +// Service ApproveV2Async UPSERT (Plan B Chunk B2). Comment empty → "(duyệt — +// không ý kiến)". signedByUserId !== approverUserId → banner "Admin duyệt thay". +export type ContractLevelOpinion = { + id: string + approvalWorkflowLevelId: string + stepOrder: number + stepName: string + levelOrder: number + levelName: string | null + approverUserId: string + approverFullName: string | null + comment: string + signedAt: string + signedByUserId: string + signedByFullName: string | null } diff --git a/fe-user/src/components/contracts/ContractDetailContent.tsx b/fe-user/src/components/contracts/ContractDetailContent.tsx index b6ca917..cd5ce88 100644 --- a/fe-user/src/components/contracts/ContractDetailContent.tsx +++ b/fe-user/src/components/contracts/ContractDetailContent.tsx @@ -182,6 +182,47 @@ export function ContractDetailContent({ + {/* [Plan B S29 2026-05-22 Chunk E3] Section 5 — Ý kiến cấp duyệt V2 dynamic. + Mirror PE LevelOpinionsSectionV2 pattern. Chỉ render khi V2 pin + (approvalWorkflowId set). V1 legacy contract KHÔNG hiển thị. */} + {c.approvalWorkflowId && ( +
+

+ + Ý kiến cấp duyệt (Quy trình V2) +

+ {(c.levelOpinions?.length ?? 0) === 0 ? ( +

Chưa có ý kiến — workflow vừa bắt đầu hoặc chưa có ai duyệt.

+ ) : ( +
    + {c.levelOpinions!.map(o => { + const adminProxy = o.signedByUserId !== o.approverUserId + return ( +
  • +
    + + Bước {o.stepOrder} {o.stepName ? `(${o.stepName})` : ''} — Cấp {o.levelOrder} + {o.levelName ? ` (${o.levelName})` : ''} + + {new Date(o.signedAt).toLocaleString('vi-VN')} +
    +

    {o.comment}

    +
    + NV duyệt: {o.approverFullName ?? o.approverUserId.slice(0, 8)} + {adminProxy && ( + + ⚡ Admin duyệt thay ({o.signedByFullName ?? o.signedByUserId.slice(0, 8)}) + + )} +
    +
  • + ) + })} +
+ )} +
+ )} + setActionOpen(false)} diff --git a/fe-user/src/types/contracts.ts b/fe-user/src/types/contracts.ts index 9025d16..6844810 100644 --- a/fe-user/src/types/contracts.ts +++ b/fe-user/src/types/contracts.ts @@ -171,4 +171,28 @@ export type ContractDetail = { comments: ContractComment[] attachments: ContractAttachment[] workflow: WorkflowSummary + // [Plan B S29 2026-05-22 Chunk E3] V2 workflow state — mirror PE pattern. + // Khi pin V2 (approvalWorkflowId set) → render Section 5 dynamic. + // V1 contract: 3 fields = null. + approvalWorkflowId: string | null + currentApprovalLevelOrder: number | null + levelOpinions: ContractLevelOpinion[] | null +} + +// [Plan B S29 2026-05-22 Chunk E3] Mirror BE ContractLevelOpinionDto 12 fields. +// Service ApproveV2Async UPSERT (Plan B Chunk B2). Comment empty → "(duyệt — +// không ý kiến)". signedByUserId !== approverUserId → banner "Admin duyệt thay". +export type ContractLevelOpinion = { + id: string + approvalWorkflowLevelId: string + stepOrder: number + stepName: string + levelOrder: number + levelName: string | null + approverUserId: string + approverFullName: string | null + comment: string + signedAt: string + signedByUserId: string + signedByFullName: string | null }