[CLAUDE] FE-Admin: S21 t5 Chunk B — Designer move 5 checkbox xuống per-Level slot
ApprovalWorkflowsV2Page.tsx refactor Designer modal theo Mig 29 per-NV: Types update: - `LevelDto` +5 Allow* (mirror BE AwLevelDto) - `DefinitionDto` REMOVE 6 workflow-level Allow* (no longer used) - `EditLevelEntry` +5 Allow* (form state per slot entry) - `makeDefaultLevelEntry(order, userId)` helper — 4 false + AllowReturnToDrafter true (S17 backward compat) - `copyFromDefinition` propagate 5 Allow* từ existing Levels Form state: - REMOVE 6 useState workflow-level (allowReturnOneLevel...allowApproverEditDetails) - POST body remove 6 workflow-level field - POST body levels[].* propagate 5 Allow* per slot UI refactor: - REMOVE entire section "Cấu hình nâng cao" workflow-level (amber bg 6 checkbox) - REPLACE với info banner violet ngắn "ⓘ Cấu hình quyền duyệt riêng cho từng NV ở mỗi Cấp dưới đây. F2 cấu hình ở User Management." - Mỗi Level entry (NV row) ADD inline panel amber-50/30 5 checkbox grid-cols-2: - Trả về 1 Cấp trước - Trả về 1 Bước trước - Trả về Người chỉ định - Trả về Drafter (mặc định checked) - Cho phép chỉnh sửa Section 2 (col-span-2, full row) - Header "Quyền duyệt NV #N" [10px] uppercase amber-700 - `updateField()` helper inline update per entry index F2 (AllowDrafterSkipToFinal) cần UX riêng ở User Management page (per-Drafter user global). Defer Chunk B Plus hoặc commit sau khi user UAT request. Verify: - npm run build fe-admin pass 498ms cached - 0 TS6 err, warning chunk size pre-existing Pending Chunk C: FE eOffice (PeWorkflowPanel + PeDetailTabs) read `evaluation.currentLevelOptions` + `evaluation.drafterAllowSkipToFinal` thay vì `workflowOptions`. Mirror 2 app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -41,6 +41,12 @@ type LevelDto = {
|
|||||||
approverUserId: string
|
approverUserId: string
|
||||||
approverUserName: string | null
|
approverUserName: string | null
|
||||||
approverEmail: string | null
|
approverEmail: string | null
|
||||||
|
// Mig 29 (S21 t5) — 5 Allow* options per slot Approver
|
||||||
|
allowReturnOneLevel: boolean
|
||||||
|
allowReturnOneStep: boolean
|
||||||
|
allowReturnToAssignee: boolean
|
||||||
|
allowReturnToDrafter: boolean
|
||||||
|
allowApproverEditDetails: boolean
|
||||||
}
|
}
|
||||||
type StepDto = {
|
type StepDto = {
|
||||||
id: string
|
id: string
|
||||||
@ -60,13 +66,9 @@ type DefinitionDto = {
|
|||||||
description: string | null
|
description: string | null
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
isUserSelectable: boolean // Mig 25 — admin toggle cho user pick
|
isUserSelectable: boolean // Mig 25 — admin toggle cho user pick
|
||||||
// Mig 28 (S21 t4) — 6 advanced options per workflow version
|
// Mig 29 (S21 t5) — 6 Allow* options MOVED:
|
||||||
allowReturnOneLevel: boolean
|
// - 5 flag F1+F3 xuống per slot Level (xem LevelDto)
|
||||||
allowReturnOneStep: boolean
|
// - 1 flag F2 AllowDrafterSkipToFinal xuống per User (User Management)
|
||||||
allowReturnToAssignee: boolean
|
|
||||||
allowReturnToDrafter: boolean // default true backward compat S17
|
|
||||||
allowDrafterSkipToFinal: boolean
|
|
||||||
allowApproverEditDetails: boolean
|
|
||||||
activatedAt: string | null
|
activatedAt: string | null
|
||||||
createdAt: string
|
createdAt: string
|
||||||
steps: StepDto[]
|
steps: StepDto[]
|
||||||
@ -79,7 +81,17 @@ type TypeSummaryDto = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LevelOrder = 1 | 2 | 3
|
type LevelOrder = 1 | 2 | 3
|
||||||
type EditLevelEntry = { order: LevelOrder; approverUserId: string }
|
type EditLevelEntry = {
|
||||||
|
order: LevelOrder
|
||||||
|
approverUserId: string
|
||||||
|
// Mig 29 (S21 t5) — 5 Allow* per slot (default backward compat S17: chỉ
|
||||||
|
// AllowReturnToDrafter=true, 4 còn lại false).
|
||||||
|
allowReturnOneLevel: boolean
|
||||||
|
allowReturnOneStep: boolean
|
||||||
|
allowReturnToAssignee: boolean
|
||||||
|
allowReturnToDrafter: boolean
|
||||||
|
allowApproverEditDetails: boolean
|
||||||
|
}
|
||||||
type EditStep = { name: string; departmentId: string | null; levelEntries: EditLevelEntry[] }
|
type EditStep = { name: string; departmentId: string | null; levelEntries: EditLevelEntry[] }
|
||||||
|
|
||||||
type ApproverUser = { id: string; fullName: string; email: string; departmentId: string | null }
|
type ApproverUser = { id: string; fullName: string; email: string; departmentId: string | null }
|
||||||
@ -110,16 +122,39 @@ function makeEmptyStep(stepNo: number, deptId: string | null = null): EditStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clone existing definition: filter Order ∈ {1,2,3}, drop entries vượt giới hạn.
|
// Clone existing definition: filter Order ∈ {1,2,3}, drop entries vượt giới hạn.
|
||||||
|
// Mig 29 (S21 t5) — clone 5 Allow* per slot từ existing Level.
|
||||||
function copyFromDefinition(d: DefinitionDto): EditStep[] {
|
function copyFromDefinition(d: DefinitionDto): EditStep[] {
|
||||||
return d.steps.map(s => ({
|
return d.steps.map(s => ({
|
||||||
name: s.name,
|
name: s.name,
|
||||||
departmentId: s.departmentId,
|
departmentId: s.departmentId,
|
||||||
levelEntries: s.levels
|
levelEntries: s.levels
|
||||||
.filter(l => l.order >= 1 && l.order <= MAX_LEVELS_PER_STEP)
|
.filter(l => l.order >= 1 && l.order <= MAX_LEVELS_PER_STEP)
|
||||||
.map(l => ({ order: l.order as LevelOrder, approverUserId: l.approverUserId })),
|
.map(l => ({
|
||||||
|
order: l.order as LevelOrder,
|
||||||
|
approverUserId: l.approverUserId,
|
||||||
|
allowReturnOneLevel: l.allowReturnOneLevel ?? false,
|
||||||
|
allowReturnOneStep: l.allowReturnOneStep ?? false,
|
||||||
|
allowReturnToAssignee: l.allowReturnToAssignee ?? false,
|
||||||
|
allowReturnToDrafter: l.allowReturnToDrafter ?? true,
|
||||||
|
allowApproverEditDetails: l.allowApproverEditDetails ?? false,
|
||||||
|
})),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mig 29 — Factory default cho entry mới (admin click "+ Thêm NV"). 5 flag
|
||||||
|
// default backward compat S17: chỉ AllowReturnToDrafter=true.
|
||||||
|
function makeDefaultLevelEntry(order: LevelOrder, approverUserId: string): EditLevelEntry {
|
||||||
|
return {
|
||||||
|
order,
|
||||||
|
approverUserId,
|
||||||
|
allowReturnOneLevel: false,
|
||||||
|
allowReturnOneStep: false,
|
||||||
|
allowReturnToAssignee: false,
|
||||||
|
allowReturnToDrafter: true,
|
||||||
|
allowApproverEditDetails: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Filter NV theo Phòng. Nếu Phòng = null → fallback all (chưa chọn phòng).
|
// Filter NV theo Phòng. Nếu Phòng = null → fallback all (chưa chọn phòng).
|
||||||
function usersForDept(all: ApproverUser[] | undefined, deptId: string | null): ApproverUser[] {
|
function usersForDept(all: ApproverUser[] | undefined, deptId: string | null): ApproverUser[] {
|
||||||
if (!all) return []
|
if (!all) return []
|
||||||
@ -452,14 +487,9 @@ function Designer({
|
|||||||
const [description, setDescription] = useState(cloneFrom?.description ?? '')
|
const [description, setDescription] = useState(cloneFrom?.description ?? '')
|
||||||
const [steps, setSteps] = useState<EditStep[]>(initialSteps)
|
const [steps, setSteps] = useState<EditStep[]>(initialSteps)
|
||||||
|
|
||||||
// Mig 28 (S21 t4) — 6 advanced options. Default clone từ cloneFrom (giữ
|
// Mig 29 (S21 t5) — 6 Allow* options MOVED:
|
||||||
// config version trước) hoặc backward compat S17 (chỉ Drafter mode).
|
// - 5 flag F1+F3 xuống per Level slot (xem EditLevelEntry, render mỗi Level row)
|
||||||
const [allowReturnOneLevel, setAllowReturnOneLevel] = useState(cloneFrom?.allowReturnOneLevel ?? false)
|
// - 1 flag F2 AllowDrafterSkipToFinal xuống per User (User Management page)
|
||||||
const [allowReturnOneStep, setAllowReturnOneStep] = useState(cloneFrom?.allowReturnOneStep ?? false)
|
|
||||||
const [allowReturnToAssignee, setAllowReturnToAssignee] = useState(cloneFrom?.allowReturnToAssignee ?? false)
|
|
||||||
const [allowReturnToDrafter, setAllowReturnToDrafter] = useState(cloneFrom?.allowReturnToDrafter ?? true)
|
|
||||||
const [allowDrafterSkipToFinal, setAllowDrafterSkipToFinal] = useState(cloneFrom?.allowDrafterSkipToFinal ?? false)
|
|
||||||
const [allowApproverEditDetails, setAllowApproverEditDetails] = useState(cloneFrom?.allowApproverEditDetails ?? false)
|
|
||||||
|
|
||||||
const usersList = useQuery({
|
const usersList = useQuery({
|
||||||
queryKey: ['users-for-approver-v2'],
|
queryKey: ['users-for-approver-v2'],
|
||||||
@ -513,19 +543,18 @@ function Designer({
|
|||||||
departmentId: s.departmentId,
|
departmentId: s.departmentId,
|
||||||
// Mỗi entry → 1 Level row. Multiple rows cùng Order = same Cấp với
|
// Mỗi entry → 1 Level row. Multiple rows cùng Order = same Cấp với
|
||||||
// N approvers (BE iterate group by Order).
|
// N approvers (BE iterate group by Order).
|
||||||
|
// Mig 29 (S21 t5) — 5 Allow* options per slot Approver.
|
||||||
levels: s.levelEntries.map(e => ({
|
levels: s.levelEntries.map(e => ({
|
||||||
order: e.order,
|
order: e.order,
|
||||||
name: `Cấp ${e.order}`,
|
name: `Cấp ${e.order}`,
|
||||||
approverUserId: e.approverUserId,
|
approverUserId: e.approverUserId,
|
||||||
|
allowReturnOneLevel: e.allowReturnOneLevel,
|
||||||
|
allowReturnOneStep: e.allowReturnOneStep,
|
||||||
|
allowReturnToAssignee: e.allowReturnToAssignee,
|
||||||
|
allowReturnToDrafter: e.allowReturnToDrafter,
|
||||||
|
allowApproverEditDetails: e.allowApproverEditDetails,
|
||||||
})),
|
})),
|
||||||
})),
|
})),
|
||||||
// Mig 28 (S21 t4) — 6 advanced options
|
|
||||||
allowReturnOneLevel,
|
|
||||||
allowReturnOneStep,
|
|
||||||
allowReturnToAssignee,
|
|
||||||
allowReturnToDrafter,
|
|
||||||
allowDrafterSkipToFinal,
|
|
||||||
allowApproverEditDetails,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@ -584,116 +613,14 @@ function Designer({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mig 28 (S21 t4) — Section Cấu hình nâng cao (F1+F2+F3 advanced options).
|
{/* Mig 29 (S21 t5) — 6 Allow* options MOVED per-NV:
|
||||||
6 checkbox per workflow: 4 mode Trả lại + 1 Skip CEO + 1 Approver edit. */}
|
- 5 flag F1+F3 xuống mỗi Level row (xem level entry inline below).
|
||||||
<div className="space-y-2 rounded-lg border border-amber-200 bg-amber-50/30 p-3">
|
- 1 flag F2 AllowDrafterSkipToFinal xuống Users page (System → Users).
|
||||||
<Label className="text-amber-900">
|
Section "Cấu hình nâng cao" workflow-level cũ Mig 28 đã DROP. */}
|
||||||
Cấu hình nâng cao — quyền duyệt mở rộng
|
<div className="rounded-lg border border-violet-200 bg-violet-50/30 px-3 py-2 text-[11px] leading-relaxed text-violet-800">
|
||||||
</Label>
|
ⓘ Cấu hình quyền duyệt (Trả lại modes + Edit Section 2) đặt RIÊNG cho từng NV ở mỗi
|
||||||
<p className="text-[11px] leading-relaxed text-slate-600">
|
Cấp dưới đây. F2 "Gửi thẳng Cấp cuối" (Drafter) cấu hình ở
|
||||||
Bật/tắt mode duyệt mở rộng cho workflow này. Mặc định chỉ "Trả về Người soạn thảo" enabled
|
<span className="font-medium"> User Management</span> (mỗi NV global).
|
||||||
(tương thích quy trình cũ). Các mode khác opt-in để audit nghiêm.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="mt-2 space-y-3">
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 text-[11px] font-semibold uppercase text-slate-500">
|
|
||||||
Mode Trả lại (Approver chọn khi nhấn ← Trả lại)
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-1.5">
|
|
||||||
<label className="flex items-start gap-2 rounded border border-slate-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="mt-0.5 h-3.5 w-3.5"
|
|
||||||
checked={allowReturnOneLevel}
|
|
||||||
onChange={e => setAllowReturnOneLevel(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
<span className="font-medium">Trả về 1 Cấp trước</span>
|
|
||||||
<span className="block text-[10px] text-slate-500">Lùi 1 Cấp trong cùng Bước, peer review chain</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<label className="flex items-start gap-2 rounded border border-slate-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="mt-0.5 h-3.5 w-3.5"
|
|
||||||
checked={allowReturnOneStep}
|
|
||||||
onChange={e => setAllowReturnOneStep(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
<span className="font-medium">Trả về 1 Bước trước</span>
|
|
||||||
<span className="block text-[10px] text-slate-500">Lùi sang Bước trước, Cấp cuối nhận lại</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<label className="flex items-start gap-2 rounded border border-slate-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="mt-0.5 h-3.5 w-3.5"
|
|
||||||
checked={allowReturnToAssignee}
|
|
||||||
onChange={e => setAllowReturnToAssignee(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
<span className="font-medium">Trả về Người chỉ định</span>
|
|
||||||
<span className="block text-[10px] text-slate-500">Pick runtime từ list NV đã duyệt</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<label className="flex items-start gap-2 rounded border border-slate-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="mt-0.5 h-3.5 w-3.5"
|
|
||||||
checked={allowReturnToDrafter}
|
|
||||||
onChange={e => setAllowReturnToDrafter(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
<span className="font-medium">Trả về Người soạn thảo</span>
|
|
||||||
<span className="block text-[10px] text-slate-500">Phase=TraLai, Drafter sửa rồi gửi lại (mặc định)</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 text-[11px] font-semibold uppercase text-slate-500">
|
|
||||||
Drafter gửi duyệt (Workspace "Lưu & Gửi Duyệt")
|
|
||||||
</div>
|
|
||||||
<label className="flex items-start gap-2 rounded border border-slate-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="mt-0.5 h-3.5 w-3.5"
|
|
||||||
checked={allowDrafterSkipToFinal}
|
|
||||||
onChange={e => setAllowDrafterSkipToFinal(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
<span className="font-medium">Cho phép Drafter gửi thẳng Cấp cuối</span>
|
|
||||||
<span className="block text-[10px] text-slate-500">
|
|
||||||
Skip mọi Bước/Cấp trung gian → đi thẳng NV Cấp cuối (vd CEO).
|
|
||||||
Workspace hiện dropdown 2 option "Gửi tuần tự" vs "Gửi thẳng Cấp cuối".
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 text-[11px] font-semibold uppercase text-slate-500">
|
|
||||||
Approver chỉnh sửa phiếu
|
|
||||||
</div>
|
|
||||||
<label className="flex items-start gap-2 rounded border border-slate-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="mt-0.5 h-3.5 w-3.5"
|
|
||||||
checked={allowApproverEditDetails}
|
|
||||||
onChange={e => setAllowApproverEditDetails(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
<span className="font-medium">Cho phép Approver chỉnh sửa Section 2 (Hạng mục + NCC + Báo giá)</span>
|
|
||||||
<span className="block text-[10px] text-slate-500">
|
|
||||||
NV Cấp đang duyệt được edit chi tiết phiếu (không reset workflow,
|
|
||||||
giữ Cấp hiện tại). Mọi thay đổi log vào Lịch sử chỉnh sửa.
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 rounded-lg border border-slate-200 p-3">
|
<div className="space-y-2 rounded-lg border border-slate-200 p-3">
|
||||||
@ -830,7 +757,7 @@ function Designer({
|
|||||||
const firstUser = availableUsers[0]
|
const firstUser = availableUsers[0]
|
||||||
setSteps(steps.map((x, i) =>
|
setSteps(steps.map((x, i) =>
|
||||||
i === idx
|
i === idx
|
||||||
? { ...x, levelEntries: [...x.levelEntries, { order, approverUserId: firstUser.id }] }
|
? { ...x, levelEntries: [...x.levelEntries, makeDefaultLevelEntry(order, firstUser.id)] }
|
||||||
: x,
|
: x,
|
||||||
))
|
))
|
||||||
}}
|
}}
|
||||||
@ -913,6 +840,81 @@ function Designer({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
{/* Mig 29 (S21 t5) — 5 Allow* checkbox inline cho mỗi
|
||||||
|
NV entry. Mặc định AllowReturnToDrafter=true (S17
|
||||||
|
backward compat). Admin tick mở mode khác per slot. */}
|
||||||
|
{entries.map((entry, ei) => {
|
||||||
|
const globalIdx = s.levelEntries.findIndex(x => x === entry)
|
||||||
|
const updateField = (field: keyof EditLevelEntry, value: boolean) => {
|
||||||
|
setSteps(steps.map((x, i) =>
|
||||||
|
i === idx
|
||||||
|
? {
|
||||||
|
...x,
|
||||||
|
levelEntries: x.levelEntries.map((y, j) =>
|
||||||
|
j === globalIdx ? { ...y, [field]: value } : y,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: x,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`opts-${ei}`}
|
||||||
|
className="ml-4 mt-1 rounded border border-amber-100 bg-amber-50/30 px-2 py-1.5"
|
||||||
|
>
|
||||||
|
<div className="mb-1 text-[10px] font-medium uppercase text-amber-700">
|
||||||
|
Quyền duyệt NV #{ei + 1}
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-1">
|
||||||
|
<label className="flex items-center gap-1 text-[11px] text-slate-700">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-3 w-3"
|
||||||
|
checked={entry.allowReturnOneLevel}
|
||||||
|
onChange={e => updateField('allowReturnOneLevel', e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span>Trả về 1 Cấp trước</span>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-1 text-[11px] text-slate-700">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-3 w-3"
|
||||||
|
checked={entry.allowReturnOneStep}
|
||||||
|
onChange={e => updateField('allowReturnOneStep', e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span>Trả về 1 Bước trước</span>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-1 text-[11px] text-slate-700">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-3 w-3"
|
||||||
|
checked={entry.allowReturnToAssignee}
|
||||||
|
onChange={e => updateField('allowReturnToAssignee', e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span>Trả về Người chỉ định</span>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-1 text-[11px] text-slate-700">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-3 w-3"
|
||||||
|
checked={entry.allowReturnToDrafter}
|
||||||
|
onChange={e => updateField('allowReturnToDrafter', e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span>Trả về Drafter (mặc định)</span>
|
||||||
|
</label>
|
||||||
|
<label className="col-span-2 flex items-center gap-1 text-[11px] text-slate-700">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-3 w-3"
|
||||||
|
checked={entry.allowApproverEditDetails}
|
||||||
|
onChange={e => updateField('allowApproverEditDetails', e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span>Cho phép chỉnh sửa Section 2 (Hạng mục/NCC/Báo giá) lúc đang duyệt</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user