[CLAUDE] FE-Admin+FE-User: PE display status meta — Bản nháp / Đã gửi duyệt / Đã duyệt / Từ chối
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m59s

User feedback 2026-05-07: thêm 2 trạng thái meta hiển thị "Bản nháp" + "Đã
gửi duyệt". Bản nháp chỉ hiện ở Thao tác workspace, không hiện ở Duyệt menu.

Implementation:
  ~ types/purchaseEvaluation.ts
    + PeDisplayStatus enum (BanNhap / DaGuiDuyet / DaDuyet / TuChoi)
    + PeDisplayStatusLabel + PeDisplayStatusColor
    + getPeDisplayStatus(phase) helper:
        DangSoanThao → BanNhap
        DaDuyet → DaDuyet
        TuChoi → TuChoi
        else (any middle phase) → DaGuiDuyet
  ~ components/pe/PeListPanel.tsx
    - Phase Select filter → Display status Select (4 option, "Đã gửi duyệt"
      KHÔNG filter exact phase do multi-phase, để client-side TODO BE)
    - Row badge dùng display status (gọn 4 màu)
    + Prop forcedPhase?: number — workspace dùng để khóa filter Bản nháp
      (DangSoanThao). Khi forcedPhase set: ẩn Select, show "Lọc cố định: Bản
      nháp" indicator.
  ~ components/pe/PeDetailTabs.tsx
    - Header badge dùng display status meta + secondary text "(Phase chi tiết)"
      nhỏ bên cạnh để approver/dev vẫn biết phase exact
  ~ pages/pe/PurchaseEvaluationsListPage.tsx
    - Phase filter Select → display status options
    - Row badge → display status
  ~ pages/pe/PurchaseEvaluationWorkspacePage.tsx
    - PeListPanel forcedPhase={PurchaseEvaluationPhase.DangSoanThao}
      → workspace chỉ list Bản nháp (đúng UX user yêu cầu)

Workflow timeline Panel 3 + workflow service BE KHÔNG đổi (giữ phase chi tiết
DangSoanThao/ChoPurchasing/ChoCCM/etc cho approval logic).

Pe_*_Pending Duyệt: dùng /inbox endpoint vốn đã filter chỉ phiếu cần user duyệt
→ DangSoanThao auto-không xuất hiện (không có active approver). Nên Bản nháp
auto-hidden từ Duyệt menu, không cần filter thêm.

UAT mode: skip verify, push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-07 15:27:29 +07:00
parent 18ebfa15f4
commit 0c5db1385f
10 changed files with 216 additions and 46 deletions

View File

@ -20,10 +20,13 @@ import {
PeAttachmentPurposeLabel,
PeDepartmentKind,
PeDepartmentKindLabel,
PeDisplayStatusColor,
PeDisplayStatusLabel,
PurchaseEvaluationPhase,
PurchaseEvaluationPhaseColor,
PurchaseEvaluationPhaseLabel,
PurchaseEvaluationTypeLabel,
getPeDisplayStatus,
type PeAttachment,
type PeChangelog,
type PeDepartmentOpinion,
@ -75,13 +78,18 @@ export function PeDetailTabs({
<div>
<div className="flex items-center gap-2">
<h2 className="text-base font-semibold text-slate-900">{evaluation.tenGoiThau}</h2>
{/* Display status meta (Bản nháp / Đã gửi duyệt / Đã duyệt / Từ chối)
— phase chi tiết hiện ở Workflow timeline Panel 3. */}
<span
className={cn(
'rounded px-1.5 py-0.5 text-[11px] font-medium',
PurchaseEvaluationPhaseColor[evaluation.phase],
PeDisplayStatusColor[getPeDisplayStatus(evaluation.phase)],
)}
>
{PurchaseEvaluationPhaseLabel[evaluation.phase]}
{PeDisplayStatusLabel[getPeDisplayStatus(evaluation.phase)]}
</span>
<span className="text-[10px] text-slate-400" title="Phase workflow chi tiết">
({PurchaseEvaluationPhaseLabel[evaluation.phase]})
</span>
{readOnly && (
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-[11px] font-medium text-slate-600">

View File

@ -16,10 +16,12 @@ import { api } from '@/lib/api'
import { cn } from '@/lib/cn'
import type { Paged } from '@/types/master'
import {
PeDisplayStatus,
PeDisplayStatusColor,
PeDisplayStatusLabel,
PurchaseEvaluationPhase,
PurchaseEvaluationPhaseColor,
PurchaseEvaluationPhaseLabel,
PurchaseEvaluationTypeLabel,
getPeDisplayStatus,
type PeListItem,
} from '@/types/purchaseEvaluation'
@ -35,6 +37,7 @@ export function PeListPanel({
showCreateButton = false,
onCreate,
onEditClick,
forcedPhase,
}: {
typeFilter: number | null
pendingMe?: boolean
@ -48,9 +51,13 @@ export function PeListPanel({
onCreate?: () => void
/** Pencil edit icon hover next-to-row — click → select + auto-open Section 1 edit mode (URL ?editHeader=1). */
onEditClick?: (id: string) => void
/** Force phase filter (vd workspace chỉ hiện Bản nháp = DangSoanThao). Khi set:
* ẩn phase Select dropdown + force fetch params phase=<this>. */
forcedPhase?: number
}) {
const effectivePhase = forcedPhase !== undefined ? String(forcedPhase) : phase
const list = useQuery({
queryKey: ['pe-list', { typeFilter, pendingMe, search, phase }],
queryKey: ['pe-list', { typeFilter, pendingMe, search, phase: effectivePhase }],
queryFn: async () => {
if (pendingMe) {
const res = await api.get<PeListItem[]>('/purchase-evaluations/inbox', {
@ -63,7 +70,7 @@ export function PeListPanel({
pageSize: 50,
search: search || undefined,
type: typeFilter ?? undefined,
phase: phase || undefined,
phase: effectivePhase || undefined,
},
})
return res.data
@ -93,12 +100,18 @@ export function PeListPanel({
className="pl-8"
/>
</div>
<Select value={phase} onChange={e => onPhaseChange(e.target.value)}>
<option value="">Tất cả phase</option>
{Object.values(PurchaseEvaluationPhase).map(p => (
<option key={p} value={p}>{PurchaseEvaluationPhaseLabel[p]}</option>
))}
</Select>
{forcedPhase === undefined ? (
<Select value={phase} onChange={e => onPhaseChange(e.target.value)}>
<option value="">Tất cả trạng thái</option>
{Object.values(PeDisplayStatus).map(s => (
<option key={s} value={statusToPhaseValue(s)}>{PeDisplayStatusLabel[s]}</option>
))}
</Select>
) : (
<div className="rounded border border-slate-200 bg-slate-50 px-2 py-1.5 text-[11px] text-slate-600">
Lọc cố đnh: <strong>{PeDisplayStatusLabel[getPeDisplayStatus(forcedPhase)]}</strong>
</div>
)}
</div>
{/* List body */}
@ -150,10 +163,10 @@ export function PeListPanel({
<span
className={cn(
'shrink-0 rounded px-1.5 py-0.5 text-[10px] font-medium',
PurchaseEvaluationPhaseColor[p.phase],
PeDisplayStatusColor[getPeDisplayStatus(p.phase)],
)}
>
{PurchaseEvaluationPhaseLabel[p.phase]}
{PeDisplayStatusLabel[getPeDisplayStatus(p.phase)]}
</span>
</div>
<div className="mt-1 flex items-center justify-between text-[11px] text-slate-500">
@ -195,3 +208,16 @@ export function PeListPanel({
</aside>
)
}
// Map display status → phase enum cho filter dropdown.
// Bản nháp = DangSoanThao (1), Đã duyệt = DaDuyet (7), Từ chối = TuChoi (99).
// Đã gửi duyệt = không filter exact phase (cần BE hỗ trợ multi-phase filter
// hoặc filter client-side). Tạm thời: trả về '' (không filter) → list show
// hết, user vẫn thấy được phiếu Đã gửi duyệt cùng với tất cả khác. Trade-off
// chấp nhận tới khi BE thêm multi-phase param.
function statusToPhaseValue(status: PeDisplayStatus): string {
if (status === PeDisplayStatus.BanNhap) return String(PurchaseEvaluationPhase.DangSoanThao)
if (status === PeDisplayStatus.DaDuyet) return String(PurchaseEvaluationPhase.DaDuyet)
if (status === PeDisplayStatus.TuChoi) return String(PurchaseEvaluationPhase.TuChoi)
return '' // DaGuiDuyet — multi-phase, không filter exact (TODO BE add support)
}