[CLAUDE] FE-Admin+FE-User: PE workspace "new" — lock Loại quy trình + Select preset payment terms
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m36s

User feedback 2026-05-07 (annotation screenshot):
1. "Gán cứng Duyệt NCC hoặc Duyệt NCC và Giải pháp theo đúng Menu" — Loại quy
   trình lock theo URL ?type=N (user vào menu nào → loại đó, không chọn lại).
2. "Chỗ này vẫn hiểu code sửa lại thành select" — Điều khoản thanh toán đổi từ
   Textarea (JSON code-style placeholder) → Select preset options + "Khác".

Implementation:
  ~ PeWorkspaceCreateView.tsx (× 2 app)
    - Loại quy trình: <Select> editable → <Input disabled> hiển thị
      PurchaseEvaluationTypeLabel[type] với bg-slate-100. Label đổi sang
      "Loại quy trình (theo menu — khóa)" rõ ý đồ.
    - Điều khoản thanh toán: <Textarea> JSON → <Select> với 8 preset:
      "100% sau khi nghiệm thu" / "Tạm ứng 30% / 70%" / "Tạm ứng 50% / 50%" /
      "TGN-30 ngày" / "TGN-45" / "TGN-60" / "Tiến độ theo đợt" / "Bảo hành 5%"
      + last option "Khác (nhập tay)" → khi chọn show Input text custom.
    - Bỏ import Textarea (không dùng nữa).
    - paymentMode local state điều khiển select; form.paymentTerms vẫn save text.

UAT mode: skip verify, push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-07 15:16:05 +07:00
parent a1665ee9d0
commit 18ebfa15f4
2 changed files with 102 additions and 26 deletions

View File

@ -14,7 +14,6 @@ import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input' import { Input } from '@/components/ui/Input'
import { Label } from '@/components/ui/Label' import { Label } from '@/components/ui/Label'
import { Select } from '@/components/ui/Select' import { Select } from '@/components/ui/Select'
import { Textarea } from '@/components/ui/Textarea'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { getErrorMessage } from '@/lib/apiError' import { getErrorMessage } from '@/lib/apiError'
import { import {
@ -24,6 +23,22 @@ import {
import { BudgetPhase, type BudgetListItem } from '@/types/budget' import { BudgetPhase, type BudgetListItem } from '@/types/budget'
import type { Paged, Project } from '@/types/master' import type { Paged, Project } from '@/types/master'
// 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.
const PAYMENT_PRESETS = [
'100% sau khi nghiệm thu',
'Tạm ứng 30% / Thanh toán 70% sau nghiệm thu',
'Tạm ứng 50% / Thanh toán 50% sau nghiệm thu',
'TGN-30 ngày (Thanh toán giao nhận 30 ngày)',
'TGN-45 ngày',
'TGN-60 ngày',
'Tiến độ theo từng đợt',
'Bảo hành 5% trong 12 tháng',
] as const
const PAYMENT_CUSTOM = '__custom__'
export function PeWorkspaceCreateView({ export function PeWorkspaceCreateView({
defaultType, defaultType,
onSaved, onSaved,
@ -48,6 +63,9 @@ export function PeWorkspaceCreateView({
budgetManualName: '', budgetManualName: '',
budgetManualAmount: 0, budgetManualAmount: 0,
}) })
// Payment terms: select preset OR "Khác" → text input
const [paymentMode, setPaymentMode] = useState<string>('') // '' / preset / __custom__
const isPaymentCustom = paymentMode === PAYMENT_CUSTOM
const projects = useQuery({ const projects = useQuery({
queryKey: ['all-projects'], queryKey: ['all-projects'],
@ -112,12 +130,12 @@ export function PeWorkspaceCreateView({
<Section title="1. Thông tin gói thầu"> <Section title="1. Thông tin gói thầu">
<div className="grid gap-3 md:grid-cols-2"> <div className="grid gap-3 md:grid-cols-2">
<div> <div>
<Label className="text-[11px]">Loại quy trình *</Label> <Label className="text-[11px]">Loại quy trình (theo menu khóa)</Label>
<Select value={form.type} onChange={e => setForm({ ...form, type: Number(e.target.value) })}> <Input
{Object.values(PurchaseEvaluationType).map(t => ( value={PurchaseEvaluationTypeLabel[form.type]}
<option key={t} value={t}>{PurchaseEvaluationTypeLabel[t]}</option> disabled
))} className="bg-slate-100 font-medium"
</Select> />
</div> </div>
<div className="md:col-span-1"> <div className="md:col-span-1">
<Label className="text-[11px]">a. Tên gói thầu *</Label> <Label className="text-[11px]">a. Tên gói thầu *</Label>
@ -157,12 +175,32 @@ export function PeWorkspaceCreateView({
</div> </div>
<div className="md:col-span-2"> <div className="md:col-span-2">
<Label className="text-[11px]">Điều khoản thanh toán</Label> <Label className="text-[11px]">Điều khoản thanh toán</Label>
<Textarea <Select
rows={2} value={paymentMode}
onChange={e => {
const v = e.target.value
setPaymentMode(v)
if (v === '' || v === PAYMENT_CUSTOM) {
setForm(f => ({ ...f, paymentTerms: '' }))
} else {
setForm(f => ({ ...f, paymentTerms: v }))
}
}}
>
<option value=""> Chọn điều khoản </option>
{PAYMENT_PRESETS.map(p => (
<option key={p} value={p}>{p}</option>
))}
<option value={PAYMENT_CUSTOM}>Khác (nhập tay)</option>
</Select>
{isPaymentCustom && (
<Input
value={form.paymentTerms} value={form.paymentTerms}
onChange={e => setForm({ ...form, paymentTerms: e.target.value })} onChange={e => setForm({ ...form, paymentTerms: e.target.value })}
placeholder='{"tamUng":"10%","thanhToanTam":"100% W.done","quyetToan":"Final Account","baoHanh":"5%"}' placeholder="Nhập điều khoản tùy chỉnh"
className="mt-2"
/> />
)}
</div> </div>
</div> </div>
</Section> </Section>

View File

@ -14,7 +14,6 @@ import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input' import { Input } from '@/components/ui/Input'
import { Label } from '@/components/ui/Label' import { Label } from '@/components/ui/Label'
import { Select } from '@/components/ui/Select' import { Select } from '@/components/ui/Select'
import { Textarea } from '@/components/ui/Textarea'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { getErrorMessage } from '@/lib/apiError' import { getErrorMessage } from '@/lib/apiError'
import { import {
@ -24,6 +23,22 @@ import {
import { BudgetPhase, type BudgetListItem } from '@/types/budget' import { BudgetPhase, type BudgetListItem } from '@/types/budget'
import type { Paged, Project } from '@/types/master' import type { Paged, Project } from '@/types/master'
// 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.
const PAYMENT_PRESETS = [
'100% sau khi nghiệm thu',
'Tạm ứng 30% / Thanh toán 70% sau nghiệm thu',
'Tạm ứng 50% / Thanh toán 50% sau nghiệm thu',
'TGN-30 ngày (Thanh toán giao nhận 30 ngày)',
'TGN-45 ngày',
'TGN-60 ngày',
'Tiến độ theo từng đợt',
'Bảo hành 5% trong 12 tháng',
] as const
const PAYMENT_CUSTOM = '__custom__'
export function PeWorkspaceCreateView({ export function PeWorkspaceCreateView({
defaultType, defaultType,
onSaved, onSaved,
@ -48,6 +63,9 @@ export function PeWorkspaceCreateView({
budgetManualName: '', budgetManualName: '',
budgetManualAmount: 0, budgetManualAmount: 0,
}) })
// Payment terms: select preset OR "Khác" → text input
const [paymentMode, setPaymentMode] = useState<string>('') // '' / preset / __custom__
const isPaymentCustom = paymentMode === PAYMENT_CUSTOM
const projects = useQuery({ const projects = useQuery({
queryKey: ['all-projects'], queryKey: ['all-projects'],
@ -112,12 +130,12 @@ export function PeWorkspaceCreateView({
<Section title="1. Thông tin gói thầu"> <Section title="1. Thông tin gói thầu">
<div className="grid gap-3 md:grid-cols-2"> <div className="grid gap-3 md:grid-cols-2">
<div> <div>
<Label className="text-[11px]">Loại quy trình *</Label> <Label className="text-[11px]">Loại quy trình (theo menu khóa)</Label>
<Select value={form.type} onChange={e => setForm({ ...form, type: Number(e.target.value) })}> <Input
{Object.values(PurchaseEvaluationType).map(t => ( value={PurchaseEvaluationTypeLabel[form.type]}
<option key={t} value={t}>{PurchaseEvaluationTypeLabel[t]}</option> disabled
))} className="bg-slate-100 font-medium"
</Select> />
</div> </div>
<div className="md:col-span-1"> <div className="md:col-span-1">
<Label className="text-[11px]">a. Tên gói thầu *</Label> <Label className="text-[11px]">a. Tên gói thầu *</Label>
@ -157,12 +175,32 @@ export function PeWorkspaceCreateView({
</div> </div>
<div className="md:col-span-2"> <div className="md:col-span-2">
<Label className="text-[11px]">Điều khoản thanh toán</Label> <Label className="text-[11px]">Điều khoản thanh toán</Label>
<Textarea <Select
rows={2} value={paymentMode}
onChange={e => {
const v = e.target.value
setPaymentMode(v)
if (v === '' || v === PAYMENT_CUSTOM) {
setForm(f => ({ ...f, paymentTerms: '' }))
} else {
setForm(f => ({ ...f, paymentTerms: v }))
}
}}
>
<option value=""> Chọn điều khoản </option>
{PAYMENT_PRESETS.map(p => (
<option key={p} value={p}>{p}</option>
))}
<option value={PAYMENT_CUSTOM}>Khác (nhập tay)</option>
</Select>
{isPaymentCustom && (
<Input
value={form.paymentTerms} value={form.paymentTerms}
onChange={e => setForm({ ...form, paymentTerms: e.target.value })} onChange={e => setForm({ ...form, paymentTerms: e.target.value })}
placeholder='{"tamUng":"10%","thanhToanTam":"100% W.done","quyetToan":"Final Account","baoHanh":"5%"}' placeholder="Nhập điều khoản tùy chỉnh"
className="mt-2"
/> />
)}
</div> </div>
</div> </div>
</Section> </Section>