[CLAUDE] FE-Admin: PE InfoTab inline edit Section 1 + PeListPanel pencil edit hover

User feedback 2026-05-07: muốn thêm nút edit kế bên row trong Panel 1, click →
Panel 2 sáng nội dung Section 1 lên cho user sửa header inline (KHÔNG cần đi
"Sửa header" page). Cũng muốn create new interface gần giống detail view
sectioned (defer cho chunk sau, hoặc keep PeHeaderForm nếu user OK).

Implementation:
  ~ PeDetailTabs.tsx
    - InfoTab thêm prop `readOnly` + `autoEdit` (trigger edit mode tự động khi
      mount nếu URL ?editHeader=1)
    - canEdit = !readOnly && isDraft (DangSoanThao):
      → display mode: hiển thị FormRow + button "✎ Sửa" góc trên phải Section 1
      → editing mode (click Sửa hoặc autoEdit): card border brand-200 + 4 input
        (Tên * / Dự án disabled / Địa điểm / Mô tả / Payment) + nút Lưu/Hủy
    - Save: PUT /pe/:id full payload (current ev values + new editable fields).
      onSuccess: invalidate ['pe-detail', 'pe-list'] + setEditing(false)
    - PeDetailTabs prop `autoEditHeader` mới — pass-through xuống InfoTab
  ~ PeListPanel.tsx
    - Thêm prop `onEditClick?: (id) => void`
    - Pencil icon (lucide) absolute right-2 top-2 trong mỗi <li>, opacity-0
      group-hover:opacity-100 — chỉ hiện khi user hover row + onEditClick set
    - Click → trigger onEditClick(id) callback (different from row click)
  ~ PurchaseEvaluationWorkspacePage.tsx
    - Đọc URL ?editHeader=1 → pass autoEditHeader xuống PeDetailTabs
    - PeListPanel onEditClick → setParams({ id, mode: null, editHeader: '1' })
    - onSelect (click row thường) → editHeader: null (clear flag)
    - onBack → clear editHeader

Verify: npm run build fe-admin pass · 0 TS error.

Pending: workspace "new" mode wrap PeHeaderForm trong sectioned layout giống
detail view (defer — user có thể chấp nhận PeHeaderForm hiện tại nếu OK).

Next: Chunk 2 fe-user mirror.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-07 14:55:49 +07:00
parent 7f38c02ab3
commit 5a89dd2188
3 changed files with 160 additions and 19 deletions

View File

@ -34,6 +34,7 @@ export function PurchaseEvaluationWorkspacePage() {
const phase = sp.get('phase') ?? ''
const selectedId = sp.get('id')
const mode = sp.get('mode') // 'new' | null
const autoEditHeader = sp.get('editHeader') === '1'
const detail = useQuery({
queryKey: ['pe-detail', selectedId],
@ -77,17 +78,18 @@ export function PurchaseEvaluationWorkspacePage() {
</header>
<div className="grid flex-1 grid-cols-1 overflow-hidden lg:grid-cols-[320px_1fr]">
{/* Panel 1: List pure picker + sticky create */}
{/* Panel 1: List pure picker + sticky create + pencil edit hover */}
<PeListPanel
typeFilter={typeFilter}
selectedId={selectedId}
search={search}
phase={phase}
onSelect={id => setParams({ id, mode: null })}
onSelect={id => setParams({ id, mode: null, editHeader: null })}
onSearchChange={q => setParams({ q })}
onPhaseChange={p => setParams({ phase: p })}
showCreateButton
onCreate={() => setParams({ mode: 'new', id: null })}
onCreate={() => setParams({ mode: 'new', id: null, editHeader: null })}
onEditClick={id => setParams({ id, mode: null, editHeader: '1' })}
/>
{/* Panel 2: Empty | Header form | Detail tabs (workspace mode) */}
@ -117,9 +119,10 @@ export function PurchaseEvaluationWorkspacePage() {
{selectedId && detail.data && (
<PeDetailTabs
evaluation={detail.data}
onBack={() => setParams({ id: null })}
onBack={() => setParams({ id: null, editHeader: null })}
onDelete={() => del.mutate(detail.data!.id)}
mode="workspace"
autoEditHeader={autoEditHeader}
/>
)}
</main>