diff --git a/fe-admin/src/components/pe/PeDetailTabs.tsx b/fe-admin/src/components/pe/PeDetailTabs.tsx index 5096a2d..c7ad1f6 100644 --- a/fe-admin/src/components/pe/PeDetailTabs.tsx +++ b/fe-admin/src/components/pe/PeDetailTabs.tsx @@ -766,11 +766,13 @@ function BudgetFieldRow({ ev, readOnly }: { ev: PeDetailBundle; readOnly: boolea const canEdit = !readOnly && isEditablePhase(ev.phase) const qc = useQueryClient() - // Detect mode khi mount/refresh: prefer manual mode nếu đã có data manual + ko link + // Detect mode khi mount/refresh: prefer manual mode nếu đã có data manual + ko link. + // Session 20 turn 6: user yêu cầu manual mode chỉ nhập số tiền — bỏ Tên field + // khỏi UI. State manualName drop, BE save luôn null cho field này. Data cũ với + // tên vẫn hiển thị OK ở read-only display (ev.budgetManualName). const initialManual = (ev.budgetManualName !== null || ev.budgetManualAmount !== null) && !ev.budgetId const [manualMode, setManualMode] = useState(initialManual) const [budgetId, setBudgetId] = useState(ev.budgetId ?? '') - const [manualName, setManualName] = useState(ev.budgetManualName ?? '') const [manualAmount, setManualAmount] = useState(ev.budgetManualAmount ?? 0) // Eligible budgets — chỉ fetch khi user có khả năng edit @@ -784,13 +786,13 @@ function BudgetFieldRow({ ev, readOnly }: { ev: PeDetailBundle; readOnly: boolea // Dirty detect — compare current state vs ev original const dirty = manualMode !== initialManual - || (manualMode && (manualName !== (ev.budgetManualName ?? '') || manualAmount !== (ev.budgetManualAmount ?? 0))) + || (manualMode && manualAmount !== (ev.budgetManualAmount ?? 0)) || (!manualMode && budgetId !== (ev.budgetId ?? '')) const save = useMutation({ mutationFn: async () => { const payload = manualMode - ? { budgetId: null, budgetManualName: manualName || null, budgetManualAmount: manualAmount > 0 ? manualAmount : null } + ? { budgetId: null, budgetManualName: null, budgetManualAmount: manualAmount > 0 ? manualAmount : null } : { budgetId: budgetId || null, budgetManualName: null, budgetManualAmount: null } await api.put(`/purchase-evaluations/${ev.id}`, { id: ev.id, @@ -862,22 +864,16 @@ function BudgetFieldRow({ ev, readOnly }: { ev: PeDetailBundle; readOnly: boolea ))} ) : ( -
+
setManualName(e.target.value)} - placeholder="Tên ngân sách (vd Tạm tính T11/2025)" - maxLength={200} - className="text-sm" - /> - setManualAmount(Number(e.target.value))} - placeholder="Số tiền (đ)" - className="text-sm" + type="text" + inputMode="numeric" + value={formatVndInput(manualAmount)} + onChange={e => setManualAmount(parseVnd(e.target.value))} + placeholder="0" + className="pr-10 font-mono text-right text-sm" /> + đ
)} {dirty && ( @@ -893,7 +889,6 @@ function BudgetFieldRow({ ev, readOnly }: { ev: PeDetailBundle; readOnly: boolea onClick={() => { setManualMode(initialManual) setBudgetId(ev.budgetId ?? '') - setManualName(ev.budgetManualName ?? '') setManualAmount(ev.budgetManualAmount ?? 0) }} className="text-[11px] text-slate-500 hover:text-slate-700" diff --git a/fe-admin/src/components/pe/PeHeaderForm.tsx b/fe-admin/src/components/pe/PeHeaderForm.tsx index 75f84f1..bcc30dd 100644 --- a/fe-admin/src/components/pe/PeHeaderForm.tsx +++ b/fe-admin/src/components/pe/PeHeaderForm.tsx @@ -20,6 +20,10 @@ import { import { BudgetPhase, type BudgetListItem } from '@/types/budget' import type { Paged, Project } from '@/types/master' +// VND format helpers (mirror PeDetailTabs.tsx — session 20) +const parseVnd = (s: string): number => Number(s.replace(/[^\d]/g, '')) || 0 +const formatVndInput = (n: number): string => (n > 0 ? n.toLocaleString('vi-VN') : '') + export function PeHeaderForm({ editId, defaultType, @@ -220,31 +224,20 @@ export function PeHeaderForm({

) : ( -
-
- +
+ +
setForm({ ...form, budgetManualName: e.target.value })} - placeholder="vd Tạm tính dự toán T11/2025" - maxLength={200} + type="text" + inputMode="numeric" + value={formatVndInput(form.budgetManualAmount)} + onChange={e => setForm({ ...form, budgetManualAmount: parseVnd(e.target.value) })} + placeholder="0" + className="pr-10 font-mono text-right" /> + đ
-
- - setForm({ ...form, budgetManualAmount: Number(e.target.value) })} - placeholder="1000000000" - /> - {form.budgetManualAmount > 0 && ( -

- ≈ {form.budgetManualAmount.toLocaleString('vi-VN')} đ -

- )} -
+

VND — nhập số, tự format dấu chấm ngàn (vd 1.000.000)

)}
diff --git a/fe-admin/src/components/pe/PeWorkspaceCreateView.tsx b/fe-admin/src/components/pe/PeWorkspaceCreateView.tsx index c580d75..1c742d8 100644 --- a/fe-admin/src/components/pe/PeWorkspaceCreateView.tsx +++ b/fe-admin/src/components/pe/PeWorkspaceCreateView.tsx @@ -20,6 +20,10 @@ import { PurchaseEvaluationTypeLabel } from '@/types/purchaseEvaluation' import { BudgetPhase, type BudgetListItem } from '@/types/budget' import type { Paged, Project } from '@/types/master' +// VND format helpers (mirror PeDetailTabs.tsx — session 20) +const parseVnd = (s: string): number => Number(s.replace(/[^\d]/g, '')) || 0 +const formatVndInput = (n: number): string => (n > 0 ? n.toLocaleString('vi-VN') : '') + // Preset điều khoản thanh toán phổ biến — user chọn 1 trong list, hoặc "Khác" // để nhập tay. Save as plain text (không JSON như cũ — code-style không phù // hợp UI cho end-user). User 2026-05-07 chỉnh. @@ -281,22 +285,16 @@ export function PeWorkspaceCreateView({

) : ( -
+
setForm({ ...form, budgetManualName: e.target.value })} - placeholder="Tên (vd Tạm tính T11/2025)" - maxLength={200} - className="text-sm" - /> - setForm({ ...form, budgetManualAmount: Number(e.target.value) })} - placeholder="Số tiền (đ)" - className="text-sm" + type="text" + inputMode="numeric" + value={formatVndInput(form.budgetManualAmount)} + onChange={e => setForm({ ...form, budgetManualAmount: parseVnd(e.target.value) })} + placeholder="0" + className="pr-10 font-mono text-right text-sm" /> + đ
)}
diff --git a/fe-user/src/components/pe/PeDetailTabs.tsx b/fe-user/src/components/pe/PeDetailTabs.tsx index 5096a2d..c7ad1f6 100644 --- a/fe-user/src/components/pe/PeDetailTabs.tsx +++ b/fe-user/src/components/pe/PeDetailTabs.tsx @@ -766,11 +766,13 @@ function BudgetFieldRow({ ev, readOnly }: { ev: PeDetailBundle; readOnly: boolea const canEdit = !readOnly && isEditablePhase(ev.phase) const qc = useQueryClient() - // Detect mode khi mount/refresh: prefer manual mode nếu đã có data manual + ko link + // Detect mode khi mount/refresh: prefer manual mode nếu đã có data manual + ko link. + // Session 20 turn 6: user yêu cầu manual mode chỉ nhập số tiền — bỏ Tên field + // khỏi UI. State manualName drop, BE save luôn null cho field này. Data cũ với + // tên vẫn hiển thị OK ở read-only display (ev.budgetManualName). const initialManual = (ev.budgetManualName !== null || ev.budgetManualAmount !== null) && !ev.budgetId const [manualMode, setManualMode] = useState(initialManual) const [budgetId, setBudgetId] = useState(ev.budgetId ?? '') - const [manualName, setManualName] = useState(ev.budgetManualName ?? '') const [manualAmount, setManualAmount] = useState(ev.budgetManualAmount ?? 0) // Eligible budgets — chỉ fetch khi user có khả năng edit @@ -784,13 +786,13 @@ function BudgetFieldRow({ ev, readOnly }: { ev: PeDetailBundle; readOnly: boolea // Dirty detect — compare current state vs ev original const dirty = manualMode !== initialManual - || (manualMode && (manualName !== (ev.budgetManualName ?? '') || manualAmount !== (ev.budgetManualAmount ?? 0))) + || (manualMode && manualAmount !== (ev.budgetManualAmount ?? 0)) || (!manualMode && budgetId !== (ev.budgetId ?? '')) const save = useMutation({ mutationFn: async () => { const payload = manualMode - ? { budgetId: null, budgetManualName: manualName || null, budgetManualAmount: manualAmount > 0 ? manualAmount : null } + ? { budgetId: null, budgetManualName: null, budgetManualAmount: manualAmount > 0 ? manualAmount : null } : { budgetId: budgetId || null, budgetManualName: null, budgetManualAmount: null } await api.put(`/purchase-evaluations/${ev.id}`, { id: ev.id, @@ -862,22 +864,16 @@ function BudgetFieldRow({ ev, readOnly }: { ev: PeDetailBundle; readOnly: boolea ))} ) : ( -
+
setManualName(e.target.value)} - placeholder="Tên ngân sách (vd Tạm tính T11/2025)" - maxLength={200} - className="text-sm" - /> - setManualAmount(Number(e.target.value))} - placeholder="Số tiền (đ)" - className="text-sm" + type="text" + inputMode="numeric" + value={formatVndInput(manualAmount)} + onChange={e => setManualAmount(parseVnd(e.target.value))} + placeholder="0" + className="pr-10 font-mono text-right text-sm" /> + đ
)} {dirty && ( @@ -893,7 +889,6 @@ function BudgetFieldRow({ ev, readOnly }: { ev: PeDetailBundle; readOnly: boolea onClick={() => { setManualMode(initialManual) setBudgetId(ev.budgetId ?? '') - setManualName(ev.budgetManualName ?? '') setManualAmount(ev.budgetManualAmount ?? 0) }} className="text-[11px] text-slate-500 hover:text-slate-700" diff --git a/fe-user/src/components/pe/PeHeaderForm.tsx b/fe-user/src/components/pe/PeHeaderForm.tsx index 75f84f1..bcc30dd 100644 --- a/fe-user/src/components/pe/PeHeaderForm.tsx +++ b/fe-user/src/components/pe/PeHeaderForm.tsx @@ -20,6 +20,10 @@ import { import { BudgetPhase, type BudgetListItem } from '@/types/budget' import type { Paged, Project } from '@/types/master' +// VND format helpers (mirror PeDetailTabs.tsx — session 20) +const parseVnd = (s: string): number => Number(s.replace(/[^\d]/g, '')) || 0 +const formatVndInput = (n: number): string => (n > 0 ? n.toLocaleString('vi-VN') : '') + export function PeHeaderForm({ editId, defaultType, @@ -220,31 +224,20 @@ export function PeHeaderForm({

) : ( -
-
- +
+ +
setForm({ ...form, budgetManualName: e.target.value })} - placeholder="vd Tạm tính dự toán T11/2025" - maxLength={200} + type="text" + inputMode="numeric" + value={formatVndInput(form.budgetManualAmount)} + onChange={e => setForm({ ...form, budgetManualAmount: parseVnd(e.target.value) })} + placeholder="0" + className="pr-10 font-mono text-right" /> + đ
-
- - setForm({ ...form, budgetManualAmount: Number(e.target.value) })} - placeholder="1000000000" - /> - {form.budgetManualAmount > 0 && ( -

- ≈ {form.budgetManualAmount.toLocaleString('vi-VN')} đ -

- )} -
+

VND — nhập số, tự format dấu chấm ngàn (vd 1.000.000)

)}
diff --git a/fe-user/src/components/pe/PeWorkspaceCreateView.tsx b/fe-user/src/components/pe/PeWorkspaceCreateView.tsx index 1896b82..8c99897 100644 --- a/fe-user/src/components/pe/PeWorkspaceCreateView.tsx +++ b/fe-user/src/components/pe/PeWorkspaceCreateView.tsx @@ -20,6 +20,10 @@ import { PurchaseEvaluationTypeLabel } from '@/types/purchaseEvaluation' import { BudgetPhase, type BudgetListItem } from '@/types/budget' import type { Paged, Project } from '@/types/master' +// VND format helpers (mirror PeDetailTabs.tsx — session 20) +const parseVnd = (s: string): number => Number(s.replace(/[^\d]/g, '')) || 0 +const formatVndInput = (n: number): string => (n > 0 ? n.toLocaleString('vi-VN') : '') + // Preset điều khoản thanh toán phổ biến — user chọn 1 trong list, hoặc "Khác" // để nhập tay. Save as plain text (không JSON như cũ — code-style không phù // hợp UI cho end-user). User 2026-05-07 chỉnh. @@ -279,22 +283,16 @@ export function PeWorkspaceCreateView({

) : ( -
+
setForm({ ...form, budgetManualName: e.target.value })} - placeholder="Tên (vd Tạm tính T11/2025)" - maxLength={200} - className="text-sm" - /> - setForm({ ...form, budgetManualAmount: Number(e.target.value) })} - placeholder="Số tiền (đ)" - className="text-sm" + type="text" + inputMode="numeric" + value={formatVndInput(form.budgetManualAmount)} + onChange={e => setForm({ ...form, budgetManualAmount: parseVnd(e.target.value) })} + placeholder="0" + className="pr-10 font-mono text-right text-sm" /> + đ
)}