[CLAUDE] Domain+FE: PE thêm phase TraLai + pencil always visible + edit gating
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 2m0s
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 2m0s
User feedback 2026-05-07:
1. Pencil edit icon LUÔN hiện (không chỉ hover) trong workspace Panel 1
2. Pencil sáng (active brand-color) khi phase editable, xám/disabled khi không
3. Click pencil khi sáng → row sáng + auto-open edit toàn bộ (header + detail)
4. Thêm phase mới "Trả lại" — approver gửi về Drafter sửa (vs Từ chối terminal)
5. Edit chỉ cho 2 trạng thái: Đang soạn thảo + Trả lại
(Từ chối + Đã gửi duyệt + Đã duyệt → không edit/thao tác gì)
Implementation:
~ Domain/PurchaseEvaluations/PurchaseEvaluationPhase.cs
+ TraLai = 98 (giữa DaDuyet=7 và TuChoi=99)
Comment ghi rõ "approver trả về Drafter sửa, vẫn cho edit, khác TuChoi"
~ types/purchaseEvaluation.ts (× 2 app)
+ PurchaseEvaluationPhase enum: TraLai = 98
+ PurchaseEvaluationPhaseLabel/Color cho TraLai (yellow)
+ isEditablePhase(phase) helper: true cho DangSoanThao + TraLai
+ PeDisplayStatus thêm "TraLai" (separate, không gộp DaGuiDuyet)
+ getPeDisplayStatus map TraLai → "Trả lại" badge yellow
~ components/pe/PeListPanel.tsx (× 2 app)
- Pencil icon: bỏ opacity-0 hover-only → LUÔN visible
- editable=isEditablePhase(p.phase): bright text-brand-600 + cursor-pointer
- !editable: text-slate-300 + cursor-not-allowed + onClick guard ignored
- title tooltip rõ ràng "đã gửi duyệt / đã duyệt / từ chối — không sửa được"
- Bỏ forcedPhase prop → editableOnly prop (filter client-side cả 2 phase
DangSoanThao + TraLai vì BE chưa support multi-phase param)
- Khi editableOnly: hiển thị "Lọc cố định: Bản nháp + Trả lại" indicator
~ components/pe/PeDetailTabs.tsx (× 2 app)
- Header bar: isDraft → canEditPhase = isEditablePhase(phase)
- InfoTab: canEdit = !readOnly && isEditablePhase
- BudgetFieldRow: canEdit = !readOnly && isEditablePhase
→ Đồng nghĩa Drafter sửa được phiếu Trả lại sau approver send back
~ pages/pe/PurchaseEvaluationWorkspacePage.tsx (× 2 app)
- PeListPanel forcedPhase=DangSoanThao → editableOnly
- Bỏ import PurchaseEvaluationPhase
Workflow service BE chưa wire transition → TraLai (defer — user sẽ thêm button
"Trả lại" trong PeWorkflowPanel duyệt sau, hoặc dùng API PATCH manual). Phase
TraLai chỉ là enum value sẵn sàng FE hiển thị + BE ánh xạ HasConversion<int>.
UAT mode: skip verify, push ngay.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -22,6 +22,7 @@ import {
|
||||
PurchaseEvaluationPhase,
|
||||
PurchaseEvaluationTypeLabel,
|
||||
getPeDisplayStatus,
|
||||
isEditablePhase,
|
||||
type PeListItem,
|
||||
} from '@/types/purchaseEvaluation'
|
||||
|
||||
@ -51,13 +52,12 @@ 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
|
||||
/** Workspace mode: chỉ list phiếu editable (DangSoanThao + TraLai). Filter
|
||||
* client-side sau khi fetch — BE chưa hỗ trợ multi-phase param. */
|
||||
editableOnly?: boolean
|
||||
}) {
|
||||
const effectivePhase = forcedPhase !== undefined ? String(forcedPhase) : phase
|
||||
const list = useQuery({
|
||||
queryKey: ['pe-list', { typeFilter, pendingMe, search, phase: effectivePhase }],
|
||||
queryKey: ['pe-list', { typeFilter, pendingMe, search, phase }],
|
||||
queryFn: async () => {
|
||||
if (pendingMe) {
|
||||
const res = await api.get<PeListItem[]>('/purchase-evaluations/inbox', {
|
||||
@ -70,14 +70,17 @@ export function PeListPanel({
|
||||
pageSize: 50,
|
||||
search: search || undefined,
|
||||
type: typeFilter ?? undefined,
|
||||
phase: effectivePhase || undefined,
|
||||
phase: phase || undefined,
|
||||
},
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
})
|
||||
|
||||
const rows = list.data?.items ?? []
|
||||
const allRows = list.data?.items ?? []
|
||||
const rows = editableOnly
|
||||
? allRows.filter(p => isEditablePhase(p.phase))
|
||||
: allRows
|
||||
|
||||
return (
|
||||
<aside className="flex flex-col overflow-hidden border-r border-slate-200 bg-white">
|
||||
@ -88,7 +91,7 @@ export function PeListPanel({
|
||||
Danh sách phiếu
|
||||
</div>
|
||||
<span className="rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-600">
|
||||
{list.data?.total ?? 0}
|
||||
{rows.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
@ -100,17 +103,17 @@ export function PeListPanel({
|
||||
className="pl-8"
|
||||
/>
|
||||
</div>
|
||||
{forcedPhase === undefined ? (
|
||||
{editableOnly ? (
|
||||
<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>Bản nháp + Trả lại</strong> (chỉ phiếu sửa được)
|
||||
</div>
|
||||
) : (
|
||||
<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>
|
||||
|
||||
@ -179,16 +182,30 @@ export function PeListPanel({
|
||||
<div className="mt-1 text-[10px] text-brand-600">✓ Đã tạo HĐ</div>
|
||||
)}
|
||||
</button>
|
||||
{/* Edit pencil — visible on hover (chỉ khi onEditClick được truyền) */}
|
||||
{onEditClick && (
|
||||
<button
|
||||
onClick={() => onEditClick(p.id)}
|
||||
className="absolute right-2 top-2 rounded p-1.5 text-slate-400 opacity-0 transition group-hover:opacity-100 hover:bg-white hover:text-brand-600 hover:shadow-sm"
|
||||
title="Sửa thông tin gói thầu"
|
||||
>
|
||||
<Pencil className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
)}
|
||||
{/* Edit pencil — LUÔN visible (user 2026-05-07).
|
||||
Bright/active khi phase editable (DangSoanThao + TraLai).
|
||||
Dim/disabled khi phase không edit được (Đã gửi duyệt / Đã duyệt
|
||||
/ Từ chối) — click không có tác dụng. */}
|
||||
{onEditClick && (() => {
|
||||
const editable = isEditablePhase(p.phase)
|
||||
return (
|
||||
<button
|
||||
onClick={() => editable && onEditClick(p.id)}
|
||||
disabled={!editable}
|
||||
className={cn(
|
||||
'absolute right-2 top-2 rounded p-1.5 transition',
|
||||
editable
|
||||
? 'text-brand-600 hover:bg-brand-50 hover:shadow-sm cursor-pointer'
|
||||
: 'text-slate-300 cursor-not-allowed',
|
||||
)}
|
||||
title={editable
|
||||
? 'Sửa phiếu (header + chi tiết)'
|
||||
: 'Phiếu đã gửi duyệt / đã duyệt / từ chối — không sửa được'}
|
||||
>
|
||||
<Pencil className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
)
|
||||
})()}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user