[CLAUDE] PE-Duyệt: ẩn dropdown trạng thái + filter cứng "Đã gửi duyệt"
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m12s

Leaf "Duyệt" (pendingMe=1) chỉ load phiếu trạng thái "Đã gửi duyệt".
Nháp / Trả lại / Đã duyệt / Từ chối lọc bỏ client-side.

- Replace <Select> trạng thái bằng hint amber "Lọc cố định: Đã gửi duyệt"
- allRows.filter qua getPeDisplayStatus === DaGuiDuyet
- Header count dùng rows.length khi pendingMe (inbox không paged)
- Mirror fe-admin + fe-user

Workaround BE /inbox loose UAT (trả phiếu Nháp). Phân quyền strict V2
pending Session 18+.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-08 17:07:23 +07:00
parent 8680f4c849
commit aaa1c6cba6
2 changed files with 66 additions and 38 deletions

View File

@ -107,7 +107,13 @@ export function PurchaseEvaluationsListPage() {
} }
} }
const rows = list.data?.items ?? [] const allRows = list.data?.items ?? []
// Duyệt (pendingMe) → filter cứng client-side chỉ "Đã gửi duyệt" (Nháp/Trả lại/
// Đã duyệt/Từ chối loại bỏ). BE /inbox hiện loose UAT có thể trả phiếu Nháp →
// FE filter để UX đúng kỳ vọng. Phân quyền strict V2 BE pending Session 18+.
const rows = pendingMe
? allRows.filter(p => getPeDisplayStatus(p.phase) === PeDisplayStatus.DaGuiDuyet)
: allRows
const headerTitle = typeFilter const headerTitle = typeFilter
? (pendingMe ? `${PurchaseEvaluationTypeLabel[typeFilter]} — Chờ duyệt` : PurchaseEvaluationTypeLabel[typeFilter]) ? (pendingMe ? `${PurchaseEvaluationTypeLabel[typeFilter]} — Chờ duyệt` : PurchaseEvaluationTypeLabel[typeFilter])
@ -120,7 +126,7 @@ export function PurchaseEvaluationsListPage() {
<ClipboardCheck className="h-5 w-5 text-slate-500" /> <ClipboardCheck className="h-5 w-5 text-slate-500" />
<h1 className="text-base font-semibold tracking-tight text-slate-900">{headerTitle}</h1> <h1 className="text-base font-semibold tracking-tight text-slate-900">{headerTitle}</h1>
<span className="ml-2 rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-600"> <span className="ml-2 rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-600">
{list.data?.total ?? 0} {pendingMe ? rows.length : (list.data?.total ?? 0)}
</span> </span>
</div> </div>
</header> </header>
@ -151,23 +157,31 @@ export function PurchaseEvaluationsListPage() {
))} ))}
</Select> </Select>
)} )}
<Select value={phase} onChange={e => setParam('phase', e.target.value)}> {/* Duyệt (pendingMe) → filter cứng "Đã gửi duyệt", ẩn dropdown trạng thái.
<option value="">Tất cả trạng thái</option> Danh sách (pendingMe=false) → giữ dropdown cho user filter mọi trạng thái. */}
{Object.values(PeDisplayStatus).map(s => { {pendingMe ? (
const phaseValue = s === PeDisplayStatus.Nhap <div className="rounded border border-amber-200 bg-amber-50 px-2 py-1.5 text-[11px] text-amber-700">
? String(PurchaseEvaluationPhase.DangSoanThao) Lọc cố đnh: <strong>Đã gửi duyệt</strong> (phiếu đang chờ duyệt)
: s === PeDisplayStatus.DaDuyet </div>
? String(PurchaseEvaluationPhase.DaDuyet) ) : (
: s === PeDisplayStatus.TraLai <Select value={phase} onChange={e => setParam('phase', e.target.value)}>
? String(PurchaseEvaluationPhase.TraLai) <option value="">Tất cả trạng thái</option>
: s === PeDisplayStatus.TuChoi {Object.values(PeDisplayStatus).map(s => {
? String(PurchaseEvaluationPhase.TuChoi) const phaseValue = s === PeDisplayStatus.Nhap
: '' // DaGuiDuyet — multi-phase, không filter exact (TODO BE) ? String(PurchaseEvaluationPhase.DangSoanThao)
return phaseValue ? ( : s === PeDisplayStatus.DaDuyet
<option key={s} value={phaseValue}>{PeDisplayStatusLabel[s]}</option> ? String(PurchaseEvaluationPhase.DaDuyet)
) : null : s === PeDisplayStatus.TraLai
})} ? String(PurchaseEvaluationPhase.TraLai)
</Select> : s === PeDisplayStatus.TuChoi
? String(PurchaseEvaluationPhase.TuChoi)
: '' // DaGuiDuyet — multi-phase, không filter exact (TODO BE)
return phaseValue ? (
<option key={s} value={phaseValue}>{PeDisplayStatusLabel[s]}</option>
) : null
})}
</Select>
)}
</div> </div>
<div className="flex-1 overflow-y-auto"> <div className="flex-1 overflow-y-auto">

View File

@ -107,7 +107,13 @@ export function PurchaseEvaluationsListPage() {
} }
} }
const rows = list.data?.items ?? [] const allRows = list.data?.items ?? []
// Duyệt (pendingMe) → filter cứng client-side chỉ "Đã gửi duyệt" (Nháp/Trả lại/
// Đã duyệt/Từ chối loại bỏ). BE /inbox hiện loose UAT có thể trả phiếu Nháp →
// FE filter để UX đúng kỳ vọng. Phân quyền strict V2 BE pending Session 18+.
const rows = pendingMe
? allRows.filter(p => getPeDisplayStatus(p.phase) === PeDisplayStatus.DaGuiDuyet)
: allRows
const headerTitle = typeFilter const headerTitle = typeFilter
? (pendingMe ? `${PurchaseEvaluationTypeLabel[typeFilter]} — Chờ duyệt` : PurchaseEvaluationTypeLabel[typeFilter]) ? (pendingMe ? `${PurchaseEvaluationTypeLabel[typeFilter]} — Chờ duyệt` : PurchaseEvaluationTypeLabel[typeFilter])
@ -120,7 +126,7 @@ export function PurchaseEvaluationsListPage() {
<ClipboardCheck className="h-5 w-5 text-slate-500" /> <ClipboardCheck className="h-5 w-5 text-slate-500" />
<h1 className="text-base font-semibold tracking-tight text-slate-900">{headerTitle}</h1> <h1 className="text-base font-semibold tracking-tight text-slate-900">{headerTitle}</h1>
<span className="ml-2 rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-600"> <span className="ml-2 rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-600">
{list.data?.total ?? 0} {pendingMe ? rows.length : (list.data?.total ?? 0)}
</span> </span>
</div> </div>
</header> </header>
@ -151,23 +157,31 @@ export function PurchaseEvaluationsListPage() {
))} ))}
</Select> </Select>
)} )}
<Select value={phase} onChange={e => setParam('phase', e.target.value)}> {/* Duyệt (pendingMe) → filter cứng "Đã gửi duyệt", ẩn dropdown trạng thái.
<option value="">Tất cả trạng thái</option> Danh sách (pendingMe=false) → giữ dropdown cho user filter mọi trạng thái. */}
{Object.values(PeDisplayStatus).map(s => { {pendingMe ? (
const phaseValue = s === PeDisplayStatus.Nhap <div className="rounded border border-amber-200 bg-amber-50 px-2 py-1.5 text-[11px] text-amber-700">
? String(PurchaseEvaluationPhase.DangSoanThao) Lọc cố đnh: <strong>Đã gửi duyệt</strong> (phiếu đang chờ duyệt)
: s === PeDisplayStatus.DaDuyet </div>
? String(PurchaseEvaluationPhase.DaDuyet) ) : (
: s === PeDisplayStatus.TraLai <Select value={phase} onChange={e => setParam('phase', e.target.value)}>
? String(PurchaseEvaluationPhase.TraLai) <option value="">Tất cả trạng thái</option>
: s === PeDisplayStatus.TuChoi {Object.values(PeDisplayStatus).map(s => {
? String(PurchaseEvaluationPhase.TuChoi) const phaseValue = s === PeDisplayStatus.Nhap
: '' // DaGuiDuyet — multi-phase, không filter exact (TODO BE) ? String(PurchaseEvaluationPhase.DangSoanThao)
return phaseValue ? ( : s === PeDisplayStatus.DaDuyet
<option key={s} value={phaseValue}>{PeDisplayStatusLabel[s]}</option> ? String(PurchaseEvaluationPhase.DaDuyet)
) : null : s === PeDisplayStatus.TraLai
})} ? String(PurchaseEvaluationPhase.TraLai)
</Select> : s === PeDisplayStatus.TuChoi
? String(PurchaseEvaluationPhase.TuChoi)
: '' // DaGuiDuyet — multi-phase, không filter exact (TODO BE)
return phaseValue ? (
<option key={s} value={phaseValue}>{PeDisplayStatusLabel[s]}</option>
) : null
})}
</Select>
)}
</div> </div>
<div className="flex-1 overflow-y-auto"> <div className="flex-1 overflow-y-auto">