)}
{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({
>
) : (
-
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({
>
) : (
-