[CLAUDE] FE-Admin·FE-User: badge " Đã được CEO duyệt" cho phiếu đi hết lên CEO (anh Kiệt FDC UAT)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m13s

- PeFinalizeChip (single-source-of-truth pill): +nhánh phase===DaDuyet && !endsBeforeCeo → " Đã được CEO duyệt" (đối xứng chip "⚑ Kết thúc · X" cho endsBeforeCeo=true)
- Wire phase ở 6 call-site (PeListPanel + PurchaseEvaluationsListPage ×2 app + Inbox + Dashboard user)
- FE-only, tận dụng endsBeforeCeo runtime (Mig 60 fix-3), no BE/mig; mirror 2-app SHA da9c3ed9, build ×2 PASS 0-TS-err

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-01 20:52:17 +07:00
parent 07c89058d7
commit 9e12e80b48
8 changed files with 52 additions and 18 deletions

View File

@ -1,17 +1,34 @@
// PE — chip "Quy trình kết thúc tại cấp này" (không trình CEO). Hiện khi quy // PE — chip trạng thái cuối phiếu ở MỌI danh sách (cây danh sách + picker + inbox):
// trình của phiếu có cấp bật AllowApproverFinalize (Mig 58, anh Kiệt FDC S89). // [S89] "⚑ Kết thúc · X" khi quy trình có cấp AllowApproverFinalize (kết thúc, không lên CEO).
// SINGLE SOURCE OF TRUTH cho pill này dùng ở MỌI danh sách phiếu (cây danh sách // [S97 anh Kiệt FDC] "✅ Đã được CEO duyệt" khi phiếu ĐÃ DUYỆT đi HẾT lên CEO (endsBeforeCeo=false).
// + picker workspace/chờ-duyệt + inbox) để mọi người NẮM ngay "phiếu này duyệt // SINGLE SOURCE OF TRUTH — mọi người NẮM ngay trạng thái mà KHÔNG cần mở phiếu.
// đến đây là xong, không lên CEO" mà KHÔNG cần mở phiếu. Shape khớp PeUrgentChips.
import { PurchaseEvaluationPhase } from '@/types/purchaseEvaluation'
export function PeFinalizeChip({ export function PeFinalizeChip({
endsBeforeCeo, endsBeforeCeo,
finalizeStepName, finalizeStepName,
phase,
}: { }: {
endsBeforeCeo: boolean endsBeforeCeo: boolean
finalizeStepName: string | null finalizeStepName: string | null
// PeListItem.phase là number (mirror BE) → prop nhận number, so khớp literal DaDuyet.
phase?: number
}) { }) {
if (!endsBeforeCeo) return null // [S97 anh Kiệt FDC] Phiếu đã duyệt KHÔNG kết thúc sớm = đi hết lên CEO/Ban Giám đốc.
if (!endsBeforeCeo) {
if (phase === PurchaseEvaluationPhase.DaDuyet) {
return (
<span
className="inline-flex shrink-0 items-center rounded bg-emerald-100 px-1.5 py-0.5 text-[11px] font-semibold text-emerald-800"
title="Phiếu đã được CEO / Ban Giám đốc duyệt (đi hết quy trình, không kết thúc sớm ở cấp dưới)"
>
Đã đưc CEO duyệt
</span>
)
}
return null
}
return ( return (
<span <span
className="inline-flex shrink-0 items-center rounded bg-violet-100 px-1.5 py-0.5 text-[11px] font-semibold text-violet-800" className="inline-flex shrink-0 items-center rounded bg-violet-100 px-1.5 py-0.5 text-[11px] font-semibold text-violet-800"

View File

@ -159,7 +159,7 @@ export function PeListPanel({
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex min-w-0 items-center gap-1 text-[13px] font-medium text-slate-900"> <div className="flex min-w-0 items-center gap-1 text-[13px] font-medium text-slate-900">
<PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} /> <PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} />
<PeFinalizeChip endsBeforeCeo={p.endsBeforeCeo} finalizeStepName={p.finalizeStepName} /> <PeFinalizeChip endsBeforeCeo={p.endsBeforeCeo} finalizeStepName={p.finalizeStepName} phase={p.phase} />
<span className="truncate">{p.tenGoiThau}</span> <span className="truncate">{p.tenGoiThau}</span>
</div> </div>
<div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500"> <div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500">

View File

@ -489,7 +489,7 @@ export function PurchaseEvaluationsListPage() {
<div className="flex min-w-0 flex-1 items-center gap-1 truncate text-[13px] font-medium text-slate-900"> <div className="flex min-w-0 flex-1 items-center gap-1 truncate text-[13px] font-medium text-slate-900">
{/* S77 — cờ gấp PILL đồng bộ mọi danh sách: 🔴 GẤP (PRO) / 🟢 GẤP (CCM). */} {/* S77 — cờ gấp PILL đồng bộ mọi danh sách: 🔴 GẤP (PRO) / 🟢 GẤP (CCM). */}
<PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} /> <PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} />
<PeFinalizeChip endsBeforeCeo={p.endsBeforeCeo} finalizeStepName={p.finalizeStepName} /> <PeFinalizeChip endsBeforeCeo={p.endsBeforeCeo} finalizeStepName={p.finalizeStepName} phase={p.phase} />
<span className="truncate">{p.tenGoiThau}</span> <span className="truncate">{p.tenGoiThau}</span>
</div> </div>
<span <span

View File

@ -1,17 +1,34 @@
// PE — chip "Quy trình kết thúc tại cấp này" (không trình CEO). Hiện khi quy // PE — chip trạng thái cuối phiếu ở MỌI danh sách (cây danh sách + picker + inbox):
// trình của phiếu có cấp bật AllowApproverFinalize (Mig 58, anh Kiệt FDC S89). // [S89] "⚑ Kết thúc · X" khi quy trình có cấp AllowApproverFinalize (kết thúc, không lên CEO).
// SINGLE SOURCE OF TRUTH cho pill này dùng ở MỌI danh sách phiếu (cây danh sách // [S97 anh Kiệt FDC] "✅ Đã được CEO duyệt" khi phiếu ĐÃ DUYỆT đi HẾT lên CEO (endsBeforeCeo=false).
// + picker workspace/chờ-duyệt + inbox) để mọi người NẮM ngay "phiếu này duyệt // SINGLE SOURCE OF TRUTH — mọi người NẮM ngay trạng thái mà KHÔNG cần mở phiếu.
// đến đây là xong, không lên CEO" mà KHÔNG cần mở phiếu. Shape khớp PeUrgentChips.
import { PurchaseEvaluationPhase } from '@/types/purchaseEvaluation'
export function PeFinalizeChip({ export function PeFinalizeChip({
endsBeforeCeo, endsBeforeCeo,
finalizeStepName, finalizeStepName,
phase,
}: { }: {
endsBeforeCeo: boolean endsBeforeCeo: boolean
finalizeStepName: string | null finalizeStepName: string | null
// PeListItem.phase là number (mirror BE) → prop nhận number, so khớp literal DaDuyet.
phase?: number
}) { }) {
if (!endsBeforeCeo) return null // [S97 anh Kiệt FDC] Phiếu đã duyệt KHÔNG kết thúc sớm = đi hết lên CEO/Ban Giám đốc.
if (!endsBeforeCeo) {
if (phase === PurchaseEvaluationPhase.DaDuyet) {
return (
<span
className="inline-flex shrink-0 items-center rounded bg-emerald-100 px-1.5 py-0.5 text-[11px] font-semibold text-emerald-800"
title="Phiếu đã được CEO / Ban Giám đốc duyệt (đi hết quy trình, không kết thúc sớm ở cấp dưới)"
>
Đã đưc CEO duyệt
</span>
)
}
return null
}
return ( return (
<span <span
className="inline-flex shrink-0 items-center rounded bg-violet-100 px-1.5 py-0.5 text-[11px] font-semibold text-violet-800" className="inline-flex shrink-0 items-center rounded bg-violet-100 px-1.5 py-0.5 text-[11px] font-semibold text-violet-800"

View File

@ -159,7 +159,7 @@ export function PeListPanel({
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex min-w-0 items-center gap-1 text-[13px] font-medium text-slate-900"> <div className="flex min-w-0 items-center gap-1 text-[13px] font-medium text-slate-900">
<PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} /> <PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} />
<PeFinalizeChip endsBeforeCeo={p.endsBeforeCeo} finalizeStepName={p.finalizeStepName} /> <PeFinalizeChip endsBeforeCeo={p.endsBeforeCeo} finalizeStepName={p.finalizeStepName} phase={p.phase} />
<span className="truncate">{p.tenGoiThau}</span> <span className="truncate">{p.tenGoiThau}</span>
</div> </div>
<div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500"> <div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500">

View File

@ -289,7 +289,7 @@ export function InboxPage() {
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex min-w-0 items-center gap-1 text-[13px] font-medium text-slate-900"> <div className="flex min-w-0 items-center gap-1 text-[13px] font-medium text-slate-900">
<PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} /> <PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} />
<PeFinalizeChip endsBeforeCeo={p.endsBeforeCeo} finalizeStepName={p.finalizeStepName} /> <PeFinalizeChip endsBeforeCeo={p.endsBeforeCeo} finalizeStepName={p.finalizeStepName} phase={p.phase} />
<span className="truncate">{p.tenGoiThau}</span> <span className="truncate">{p.tenGoiThau}</span>
</div> </div>
<div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500"> <div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500">

View File

@ -283,7 +283,7 @@ export function UserDashboardPage() {
<span className="truncate text-sm font-medium text-slate-900"> <span className="truncate text-sm font-medium text-slate-900">
{p.tenGoiThau} {p.tenGoiThau}
</span> </span>
<PeFinalizeChip endsBeforeCeo={p.endsBeforeCeo} finalizeStepName={p.finalizeStepName} /> <PeFinalizeChip endsBeforeCeo={p.endsBeforeCeo} finalizeStepName={p.finalizeStepName} phase={p.phase} />
<span <span
className={cn( className={cn(
'shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold ring-1 ring-inset ring-current/15', 'shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold ring-1 ring-inset ring-current/15',

View File

@ -489,7 +489,7 @@ export function PurchaseEvaluationsListPage() {
<div className="flex min-w-0 flex-1 items-center gap-1 truncate text-[13px] font-medium text-slate-900"> <div className="flex min-w-0 flex-1 items-center gap-1 truncate text-[13px] font-medium text-slate-900">
{/* S77 — cờ gấp PILL đồng bộ mọi danh sách: 🔴 GẤP (PRO) / 🟢 GẤP (CCM). */} {/* S77 — cờ gấp PILL đồng bộ mọi danh sách: 🔴 GẤP (PRO) / 🟢 GẤP (CCM). */}
<PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} /> <PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} />
<PeFinalizeChip endsBeforeCeo={p.endsBeforeCeo} finalizeStepName={p.finalizeStepName} /> <PeFinalizeChip endsBeforeCeo={p.endsBeforeCeo} finalizeStepName={p.finalizeStepName} phase={p.phase} />
<span className="truncate">{p.tenGoiThau}</span> <span className="truncate">{p.tenGoiThau}</span>
</div> </div>
<span <span