[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
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:
@ -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"
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user