[CLAUDE] FE-User: tab strip 4 giai doan tren phieu PE + menu 4-GD + danh so 01-07 nhom HD (UAT anh Kiet)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m44s

- PePipelineStrip moi (mirror x2 app md5-identical): Duyet NCC | Ke hoach ky ket HD | Duyet hop dong | Hop dong cung — chen dau PeDetailTabs (GD1 active)
- Menu 4-GD: rename 2 root (PurchaseEvaluations->Duyet NCC, Contracts->Duyet hop dong) + 2 root placeholder KeHoachKyKet/HopDongCung (ngoai MenuKeys.All, staticMap -> /dashboard) — prod da SQL truc tiep cung ngay, seeder+labelBackfill giu hoi tu (gotcha #11)
- Danh so 01.-07. nhom HD: prod prepend-only 0-VN-over-wire (renamed=7), seeder typeLabels + labelBackfill 7 entry
- Dong bo nhan 4-GD tren dashboard (3 title + 3 group aria)
- reviewKeys += 2 placeholder CanRead-only; test 562 PASS; build fe x2 PASS

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-29 12:06:19 +07:00
parent b2a86feab6
commit 1a47a61c21
8 changed files with 190 additions and 18 deletions

View File

@ -57,6 +57,11 @@ function resolvePath(key: string): string | null {
Dashboard: '/dashboard',
Contracts: '/my-contracts',
PurchaseEvaluations: '/purchase-evaluations',
// [S159 2026-07-29] 2 root placeholder giai đoạn 2/4 (owner: menu hiển thị đúng 4 GĐ
// như tab strip). Module chưa build → trỏ về Tổng quan pipeline (khung "Sắp triển khai").
// LESSON gotcha #50: key DB mới PHẢI có route ở đây, không thì sidebar drop SILENT.
KeHoachKyKet: '/dashboard',
HopDongCung: '/dashboard',
// [Plan CA Hotfix 1 S29 2026-05-22] 4 master + 4 catalog leaf moved từ
// fe-admin → fe-user. resolvePath PHẢI có route mapping nếu không
// MenuLeaf line 238 `if (!path) return null` → sidebar drop silent.

View File

@ -18,6 +18,7 @@ import { getErrorMessage } from '@/lib/apiError'
import { cn } from '@/lib/cn'
import { useAuth } from '@/contexts/AuthContext'
import { AttachmentPreviewDialog, isPreviewable } from './AttachmentPreviewDialog'
import { PePipelineStrip } from './PePipelineStrip'
import {
PeAttachmentPurpose,
PeAttachmentPurposeLabel,
@ -272,6 +273,8 @@ export function PeDetailTabs({
return (
<div className="rounded-lg border border-slate-200 bg-white shadow-sm">
{/* [S159] dải 4 giai đoạn toàn trình — owner vẽ tab lên ảnh phiếu (phiếu PE ⇒ GĐ1 active) */}
<PePipelineStrip current={1} />
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-slate-200 px-5 py-3">
<div>
<div className="flex items-center gap-2">

View File

@ -0,0 +1,65 @@
// PePipelineStrip — dải 4 giai đoạn toàn trình, đặt trên đầu detail phiếu Duyệt NCC.
// [S159 2026-07-29] Owner vẽ tab strip trực tiếp lên ảnh phiếu + chốt tên qua chat:
// "Duyệt NCC | Kế hoạch ký kết HĐ | Duyệt hợp đồng | Hợp đồng cứng"
// (2 chỉnh verbatim: "Trình ký Hợp đồng đổi thành Duyệt hợp đồng, ký cứng -> Hợp đồng cứng").
// MIRROR ×2 app BYTE-IDENTICAL (cùng họ PeDetailTabs md5-parity) — sửa file này
// thì sửa CẢ HAI, verify md5 trước commit.
// v1: phiếu PE ⇒ giai đoạn 1 active. GĐ2/GĐ4 chưa có module (badge "Sắp");
// GĐ3 có module HĐ nhưng CHƯA nối per-phiếu (cầu PE→KHKK→HĐ ship ở wave GĐ2)
// ⇒ mọi stage không-active đều KHÔNG click được — hiển thị lộ trình, không giả link.
import { Check } from 'lucide-react'
import { cn } from '@/lib/cn'
const STAGES = [
{ n: 1, label: 'Duyệt NCC', soon: false },
{ n: 2, label: 'Kế hoạch ký kết HĐ', soon: true },
{ n: 3, label: 'Duyệt hợp đồng', soon: false },
{ n: 4, label: 'Hợp đồng cứng', soon: true },
] as const
export function PePipelineStrip({ current = 1 }: { current?: number }) {
return (
<div
role="list"
aria-label="Toàn trình 4 giai đoạn"
className="flex items-stretch gap-1 overflow-x-auto rounded-t-lg border-b border-slate-200 bg-slate-50/70 px-2 py-1.5"
>
{STAGES.map((s, i) => {
const active = s.n === current
return (
<div key={s.n} role="listitem" className="flex min-w-0 flex-1 items-center gap-1">
<div
aria-current={active ? 'step' : undefined}
title={
active
? 'Giai đoạn hiện tại'
: s.soon
? 'Sắp triển khai'
: 'Sẽ nối khi giai đoạn trước hoàn tất'
}
className={cn(
'flex w-full min-w-0 items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-center text-[11px] font-medium leading-tight',
active
? 'bg-brand-600 text-white shadow-sm'
: 'border border-dashed border-slate-300 bg-white text-slate-500',
)}
>
{active && <Check aria-hidden className="h-3 w-3 shrink-0" />}
<span className="truncate">{s.label}</span>
{s.soon && !active && (
<span className="shrink-0 rounded bg-slate-100 px-1 text-[9px] font-semibold uppercase tracking-wide text-slate-400">
Sắp
</span>
)}
</div>
{i < STAGES.length - 1 && (
<span aria-hidden className="shrink-0 px-0.5 text-slate-300">
</span>
)}
</div>
)
})}
</div>
)
}

View File

@ -2,9 +2,9 @@
// [S159 2026-07-29 — owner chốt qua AskUser] Hero = dải pipeline 4 GIAI ĐOẠN xuyên
// suốt quy trình mua sắm → ký kết hợp đồng:
// GĐ1 Duyệt NCC (bước 1 → 6, CEO chốt) 🟢 LIVE — module PE
// GĐ2 Đề xuất ký kết hợp đồng (bước 7 → 12) ⏳ sắp triển khai
// GĐ3 Duyệt Hợp đồng (bước 13 → 18) 🟢 LIVE — module Contract
// GĐ4 Ký cứng & phát hành (bước 19 → 21) ⏳ sắp triển khai
// GĐ2 Kế hoạch ký kết HĐ (bước 7 → 12) ⏳ sắp triển khai
// GĐ3 Duyệt hợp đồng (bước 13 → 18) 🟢 LIVE — module Contract
// GĐ4 Hợp đồng cứng (bước 19 → 21) ⏳ sắp triển khai
// Dưới hero GIỮ nguyên KPI cá nhân + 2 danh sách gần đây (chỉ redesign cho hài hoà).
// fe-user ONLY — KHÔNG mirror sang fe-admin (trang này là UX riêng của user).
//
@ -473,7 +473,7 @@ export function UserDashboardPage() {
<StageArrow />
{/* GĐ2 — sắp triển khai */}
<StageCard index={2} title="Đề xuất ký kết hợp đồng" stepRange="Bước 7 → 12" icon={FileSignature}>
<StageCard index={2} title="Kế hoạch ký kết HĐ" stepRange="Bước 7 → 12" icon={FileSignature}>
<StageSoon
description="Phiếu nối tiếp từ phiếu Duyệt NCC đã duyệt — chốt giá trị ký kết."
hint={
@ -489,14 +489,14 @@ export function UserDashboardPage() {
{/* GĐ3 — LIVE */}
<StageCard
index={3}
title="Duyệt Hợp đồng"
title="Duyệt hợp đồng"
stepRange="Bước 13 → 18"
icon={FileText}
tone="teal"
>
<div className="space-y-0.5">
<StageStatRow
group="Duyệt Hợp đồng"
group="Duyệt hợp đồng"
label="Tổng hợp đồng"
value={recent.data?.total}
tone="teal"
@ -505,7 +505,7 @@ export function UserDashboardPage() {
onClick={() => navigate('/my-contracts')}
/>
<StageStatRow
group="Duyệt Hợp đồng"
group="Duyệt hợp đồng"
label="Chờ tôi duyệt"
value={s?.pendingMyApproval}
tone="teal"
@ -515,7 +515,7 @@ export function UserDashboardPage() {
onClick={() => navigate('/inbox')}
/>
<StageStatRow
group="Duyệt Hợp đồng"
group="Duyệt hợp đồng"
label="Đã quá hạn"
value={s?.overdue}
tone="teal"
@ -531,7 +531,7 @@ export function UserDashboardPage() {
<StageArrow />
{/* GĐ4 — sắp triển khai */}
<StageCard index={4} title="Ký cứng & phát hành" stepRange="Bước 19 → 21" icon={Stamp}>
<StageCard index={4} title="Hợp đồng cứng" stepRange="Bước 19 → 21" icon={Stamp}>
<StageSoon description="Tải lên bản hợp đồng đã ký cứng." />
</StageCard>
</div>