diff --git a/fe-admin/src/App.tsx b/fe-admin/src/App.tsx index 338bddd..9ac24cc 100644 --- a/fe-admin/src/App.tsx +++ b/fe-admin/src/App.tsx @@ -12,6 +12,7 @@ import { CatalogsPage } from '@/pages/master/CatalogsPage' import { PermissionsPage } from '@/pages/system/PermissionsPage' import { RolesPage } from '@/pages/system/RolesPage' import { WorkflowsPage } from '@/pages/system/WorkflowsPage' +import { PeWorkflowsPage } from '@/pages/system/PeWorkflowsPage' import { FormsPage } from '@/pages/forms/FormsPage' import { ContractsListPage } from '@/pages/contracts/ContractsListPage' import { ContractDetailPage } from '@/pages/contracts/ContractDetailPage' @@ -47,6 +48,8 @@ function App() { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/fe-admin/src/components/pe/PeDetailTabs.tsx b/fe-admin/src/components/pe/PeDetailTabs.tsx index aac435d..ec81f72 100644 --- a/fe-admin/src/components/pe/PeDetailTabs.tsx +++ b/fe-admin/src/components/pe/PeDetailTabs.tsx @@ -18,12 +18,15 @@ import { cn } from '@/lib/cn' import { PeAttachmentPurpose, PeAttachmentPurposeLabel, + PeDepartmentKind, + PeDepartmentKindLabel, PurchaseEvaluationPhase, PurchaseEvaluationPhaseColor, PurchaseEvaluationPhaseLabel, PurchaseEvaluationTypeLabel, type PeAttachment, type PeChangelog, + type PeDepartmentOpinion, type PeDetailBundle, type PeDetailRow, type PeQuote, @@ -108,6 +111,9 @@ export function PeDetailTabs({
+
+ +
) @@ -122,6 +128,131 @@ function Section({ title, children }: { title: string; children: React.ReactNode ) } +// ===== Section 5 — Ý kiến 4 phòng ban ===== +// Render 2x2 grid 4 box (Phê duyệt / CCM / MuaHàng / SM-PM). Mỗi box hiển +// thị Opinion text + chữ ký (UserName + SignedAt) nếu đã ký, hoặc form nhập +// + 2 button "Lưu" + "Lưu & Ký" khi chưa ký / readOnly=false. +function DepartmentOpinionsSection({ ev, readOnly }: { ev: PeDetailBundle; readOnly: boolean }) { + const KINDS: { kind: number; label: string }[] = [ + { kind: PeDepartmentKind.PheDuyet, label: PeDepartmentKindLabel[PeDepartmentKind.PheDuyet] }, + { kind: PeDepartmentKind.Ccm, label: PeDepartmentKindLabel[PeDepartmentKind.Ccm] }, + { kind: PeDepartmentKind.MuaHang, label: PeDepartmentKindLabel[PeDepartmentKind.MuaHang] }, + { kind: PeDepartmentKind.SmPm, label: PeDepartmentKindLabel[PeDepartmentKind.SmPm] }, + ] + return ( +
+ {KINDS.map(k => { + const existing = ev.departmentOpinions.find(o => o.kind === k.kind) ?? null + return ( + + ) + })} +
+ ) +} + +function OpinionBox({ + evaluationId, + kind, + kindLabel, + existing, + readOnly, +}: { + evaluationId: string + kind: number + kindLabel: string + existing: PeDepartmentOpinion | null + readOnly: boolean +}) { + const qc = useQueryClient() + const [text, setText] = useState(existing?.opinion ?? '') + const isSigned = !!existing?.signedAt + + const save = useMutation({ + mutationFn: async (sign: boolean) => + api.post(`/purchase-evaluations/${evaluationId}/opinions`, { + kind, + opinion: text || null, + sign, + }), + onSuccess: () => { + toast.success('Đã lưu ý kiến.') + qc.invalidateQueries({ queryKey: ['pe-detail', evaluationId] }) + }, + onError: e => toast.error(getErrorMessage(e)), + }) + + return ( +
+
+

{kindLabel}

+ {isSigned && ( + + Đã ký + + )} +
+ + {readOnly ? ( + <> +
+ {existing?.opinion ?? — chưa có ý kiến} +
+ {isSigned && ( +
+ Ký bởi {existing?.userName ?? '—'} · {new Date(existing!.signedAt!).toLocaleString('vi-VN')} +
+ )} + + ) : ( + <> +