[CLAUDE] FE-Admin+FE-User: PE InfoTab auto re-edit on pencil click + active state visual feedback
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m7s

User feedback 2026-05-07: bấm pencil cho phiếu khác KHÔNG sáng + KHÔNG vào edit
mode (do useState init mount-time only, ev.id thay đổi không re-trigger).
Cũng cần visual feedback "sáng lên" để user biết đang edit phiếu nào.

Implementation:
  ~ PeDetailTabs.tsx (× 2 app)
    + import useEffect
    ~ InfoTab: thêm useEffect watch [autoEdit, canEdit, ev.id, ev.tenGoiThau,
      ev.diaDiem, ev.moTa, ev.paymentTerms]. Khi autoEdit && canEdit → setEditing(true)
      + sync values từ ev mới (tránh stale state khi switch giữa 2 phiếu khác id).
    Note: Dự án disabled đã có sẵn (line 458 `<Input value={ev.projectName}
    disabled className="bg-slate-100" />`) — verify hỏi user, KHÔNG thay đổi.
  ~ PeListPanel.tsx (× 2 app)
    + Prop `editingRowId?: string | null` — row đang edit (URL editHeader=1)
    ~ Pencil icon: thêm `isEditingThis = editable && editingRowId === p.id` state
      → bg-brand-100 + text-brand-700 + ring-brand-300 + shadow-sm khi active
      → tooltip đổi "✎ Đang sửa phiếu này — click để toggle / xem khác"
  ~ PurchaseEvaluationWorkspacePage.tsx (× 2 app)
    + Pass `editingRowId={autoEditHeader ? selectedId : null}` xuống PeListPanel

Verify: npm run build fe-admin + fe-user pass · 0 TS error · áp rule strict
verify khi add new prop chain + useEffect.

UAT mode: skip dotnet test (FE-only), push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-07 16:41:33 +07:00
parent 378c9939e6
commit e320027074
6 changed files with 60 additions and 8 deletions

View File

@ -2,7 +2,7 @@
// NCC + Hạng mục + Báo giá stack vertically trong 1 màn hình.
// Duyệt history + Lịch sử thay đổi → moved to Panel 3 (xem PeWorkflowPanel
// → PeApprovalsSection + PeHistorySection).
import { useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useNavigate } from 'react-router-dom'
import { toast } from 'sonner'
@ -399,6 +399,20 @@ function InfoTab({ ev, readOnly, autoEdit }: { ev: PeDetailBundle; readOnly: boo
const [moTa, setMoTa] = useState(ev.moTa ?? '')
const [paymentTerms, setPaymentTerms] = useState(ev.paymentTerms ?? '')
// User 2026-05-07: re-trigger editing mode khi click pencil ở Panel 1 cho
// PHIẾU KHÁC (ev.id thay đổi) hoặc autoEdit prop change. useState init chỉ
// chạy mount-time → cần useEffect sync khi parent re-render với props mới.
useEffect(() => {
if (autoEdit && canEdit) {
setEditing(true)
// Sync values từ ev mới (tránh stale state khi switch giữa 2 phiếu)
setTenGoiThau(ev.tenGoiThau)
setDiaDiem(ev.diaDiem ?? '')
setMoTa(ev.moTa ?? '')
setPaymentTerms(ev.paymentTerms ?? '')
}
}, [autoEdit, canEdit, ev.id, ev.tenGoiThau, ev.diaDiem, ev.moTa, ev.paymentTerms])
const dirty = tenGoiThau !== ev.tenGoiThau
|| diaDiem !== (ev.diaDiem ?? '')
|| moTa !== (ev.moTa ?? '')

View File

@ -39,6 +39,7 @@ export function PeListPanel({
onCreate,
onEditClick,
editableOnly = false,
editingRowId = null,
}: {
typeFilter: number | null
pendingMe?: boolean
@ -55,6 +56,9 @@ export function PeListPanel({
/** 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
/** Row đang được edit (URL editHeader=1) — pencil icon "sáng lên" active state.
* User 2026-05-07: visual feedback khi click pencil. */
editingRowId?: string | null
}) {
const list = useQuery({
queryKey: ['pe-list', { typeFilter, pendingMe, search, phase }],
@ -185,20 +189,27 @@ export function PeListPanel({
{/* 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. */}
/ Từ chối) — click không có tác dụng.
"Sáng lên" active state khi row.id === editingRowId (user
vừa click pencil + đang edit) — bg-brand-100 + ring. */}
{onEditClick && (() => {
const editable = isEditablePhase(p.phase)
const isEditingThis = editable && editingRowId === p.id
return (
<button
onClick={() => editable && onEditClick(p.id)}
disabled={!editable}
className={cn(
'absolute right-2 top-2 rounded p-1.5 transition',
editable
isEditingThis
? 'bg-brand-100 text-brand-700 shadow-sm ring-1 ring-brand-300 cursor-pointer'
: editable
? 'text-brand-600 hover:bg-brand-50 hover:shadow-sm cursor-pointer'
: 'text-slate-300 cursor-not-allowed',
)}
title={editable
title={isEditingThis
? '✎ Đang sửa phiếu này — click để toggle / xem khác'
: 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'}
>

View File

@ -93,6 +93,7 @@ export function PurchaseEvaluationWorkspacePage() {
onCreate={() => setParams({ mode: 'new', id: null, editHeader: null })}
onEditClick={id => setParams({ id, mode: null, editHeader: '1' })}
editableOnly
editingRowId={autoEditHeader ? selectedId : null}
/>
{/* Panel 2: Empty | Header form | Detail tabs (workspace mode) */}

View File

@ -2,7 +2,7 @@
// NCC + Hạng mục + Báo giá stack vertically trong 1 màn hình.
// Duyệt history + Lịch sử thay đổi → moved to Panel 3 (xem PeWorkflowPanel
// → PeApprovalsSection + PeHistorySection).
import { useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useNavigate } from 'react-router-dom'
import { toast } from 'sonner'
@ -399,6 +399,20 @@ function InfoTab({ ev, readOnly, autoEdit }: { ev: PeDetailBundle; readOnly: boo
const [moTa, setMoTa] = useState(ev.moTa ?? '')
const [paymentTerms, setPaymentTerms] = useState(ev.paymentTerms ?? '')
// User 2026-05-07: re-trigger editing mode khi click pencil ở Panel 1 cho
// PHIẾU KHÁC (ev.id thay đổi) hoặc autoEdit prop change. useState init chỉ
// chạy mount-time → cần useEffect sync khi parent re-render với props mới.
useEffect(() => {
if (autoEdit && canEdit) {
setEditing(true)
// Sync values từ ev mới (tránh stale state khi switch giữa 2 phiếu)
setTenGoiThau(ev.tenGoiThau)
setDiaDiem(ev.diaDiem ?? '')
setMoTa(ev.moTa ?? '')
setPaymentTerms(ev.paymentTerms ?? '')
}
}, [autoEdit, canEdit, ev.id, ev.tenGoiThau, ev.diaDiem, ev.moTa, ev.paymentTerms])
const dirty = tenGoiThau !== ev.tenGoiThau
|| diaDiem !== (ev.diaDiem ?? '')
|| moTa !== (ev.moTa ?? '')

View File

@ -39,6 +39,7 @@ export function PeListPanel({
onCreate,
onEditClick,
editableOnly = false,
editingRowId = null,
}: {
typeFilter: number | null
pendingMe?: boolean
@ -55,6 +56,9 @@ export function PeListPanel({
/** 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
/** Row đang được edit (URL editHeader=1) — pencil icon "sáng lên" active state.
* User 2026-05-07: visual feedback khi click pencil. */
editingRowId?: string | null
}) {
const list = useQuery({
queryKey: ['pe-list', { typeFilter, pendingMe, search, phase }],
@ -185,20 +189,27 @@ export function PeListPanel({
{/* 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. */}
/ Từ chối) — click không có tác dụng.
"Sáng lên" active state khi row.id === editingRowId (user
vừa click pencil + đang edit) — bg-brand-100 + ring. */}
{onEditClick && (() => {
const editable = isEditablePhase(p.phase)
const isEditingThis = editable && editingRowId === p.id
return (
<button
onClick={() => editable && onEditClick(p.id)}
disabled={!editable}
className={cn(
'absolute right-2 top-2 rounded p-1.5 transition',
editable
isEditingThis
? 'bg-brand-100 text-brand-700 shadow-sm ring-1 ring-brand-300 cursor-pointer'
: editable
? 'text-brand-600 hover:bg-brand-50 hover:shadow-sm cursor-pointer'
: 'text-slate-300 cursor-not-allowed',
)}
title={editable
title={isEditingThis
? '✎ Đang sửa phiếu này — click để toggle / xem khác'
: 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'}
>

View File

@ -93,6 +93,7 @@ export function PurchaseEvaluationWorkspacePage() {
onCreate={() => setParams({ mode: 'new', id: null, editHeader: null })}
onEditClick={id => setParams({ id, mode: null, editHeader: '1' })}
editableOnly
editingRowId={autoEditHeader ? selectedId : null}
/>
{/* Panel 2: Empty | Header form | Detail tabs (workspace mode) */}