diff --git a/fe-admin/src/components/khkk/KhkkDetailContent.tsx b/fe-admin/src/components/khkk/KhkkDetailContent.tsx index d8dd535..6f9441e 100644 --- a/fe-admin/src/components/khkk/KhkkDetailContent.tsx +++ b/fe-admin/src/components/khkk/KhkkDetailContent.tsx @@ -359,12 +359,16 @@ export function KhkkDetailContent({ // `plan.workflowSteps` VẪN có trong DTO (panel 3 dùng) — chỉ bỏ chỗ VẼ ở đây. return ( - // [S175 — anh: "Viền xanh này trong thao tác → Chọn kế hoạch"] GỠ `card-accent`. - // Class này vẽ DẢI ACCENT DỌC 3px bên trái (`index.css:112`); ở màn Thao tác nó nằm sát - // mép panel 2 nên đọc ra thành một vệt xanh đậm chạy dọc màn hình, không ra "thẻ". - // 🔴 GỠ HẲN CLASS, KHÔNG đè `border-0`: `.card-accent` nằm NGOÀI `@layer` nên thắng - // utility Tailwind v4 — đè là vô hiệu (gotcha #66, đã cắn thật ở đợt B với panel duyệt). -
- File căn cứ hồ sơ / shopdrawing của kế hoạch. Gắn vào 1 căn cứ cụ thể bằng nút ⬆ ở dòng căn cứ đó. -
- )} + {/* 🧊 [S175 — anh: "Bỏ hết mấy chữ thừa này đi"] Dòng mô-tả "File căn cứ hồ sơ / + shopdrawing của kế hoạch…" ĐÃ GỠ. Nút ⬆ ở mỗi dòng căn cứ đã tự nói nó làm gì + (có `title`), không cần một câu hướng-dẫn nằm chình ình trên đầu khối. */} {mergedRows.length === 0 ? (- Đã ẩn kế hoạch ở trạng thái “Đã duyệt” — xem đủ ở menu Kế hoạch ký kết HĐ. -
- )} + {/* 🧊 [S175 — anh: "Bỏ hết mấy chữ thừa này đi"] Dòng "Đã ẩn kế hoạch ở trạng + thái Đã duyệt…" ĐÃ GỠ. Nó giải-thích CƠ-CHẾ lọc bên trong; người dùng chỉ cần + thấy đúng phiếu, không cần biết cái gì bị ẩn vì sao. */} > )} diff --git a/fe-admin/src/components/pipeline/PipelineTreePanel.tsx b/fe-admin/src/components/pipeline/PipelineTreePanel.tsx index bc0387b..e7176ec 100644 --- a/fe-admin/src/components/pipeline/PipelineTreePanel.tsx +++ b/fe-admin/src/components/pipeline/PipelineTreePanel.tsx @@ -16,7 +16,7 @@ import { cn } from '@/lib/cn' import { usePipelineStages, type BuildStagesOptions } from '@/hooks/usePipelineStages' import { PipelineStageFolders, type PipelineStageNo } from '@/components/pipeline/PipelineStageFolders' import type { Paged } from '@/types/master' -import type { PeListItem } from '@/types/purchaseEvaluation' +import { PurchaseEvaluationPhase, type PeListItem } from '@/types/purchaseEvaluation' const STORAGE_KEY = 'pipeline_tree_expanded_v1' const PE_INDEX_PAGE_SIZE = 200 @@ -90,6 +90,8 @@ export function PipelineTreePanel({ stage2Options, hideHeader, hideSearch, + approvedPeOnly, + stagesOnly, }: { currentStage: PipelineStageNo className?: string @@ -116,6 +118,14 @@ export function PipelineTreePanel({ // dùng tới — người dùng gõ phát nào là cây biến mất phát đó. // 6 host còn lại KHÔNG truyền ⇒ giữ nguyên ô lọc cây như cũ. hideSearch?: boolean + // [S175 — anh: "chỗ này chỉ cần NCC đã duyệt là đc rồi nhé, còn lại bỏ hết đi"] + // Chỉ giữ phiếu Duyệt NCC ở phase DaDuyet. Màn Thao tác dùng cây này để CHỌN PE mà lập + // kế hoạch — phiếu chưa duyệt xong thì lập không được, hiện ra chỉ tổ mời bấm nhầm. + // 🔴 OPT-IN: 5 host còn lại không truyền ⇒ cây giữ NGUYÊN mọi phase như cũ. + approvedPeOnly?: boolean + // [S175] Chỉ vẽ các folder giai-đoạn trong danh sách này (vd `[1]` = chỉ Duyệt NCC). + // Không truyền ⇒ vẽ đủ 4 giai-đoạn như cũ. + stagesOnly?: PipelineStageNo[] }) { const { canPe, buildStages } = usePipelineStages() const [search, setSearch] = useState('') @@ -154,13 +164,18 @@ export function PipelineTreePanel({ const term = search.trim().toLowerCase() const rows = useMemo(() => { - const all = peIndex.data?.items ?? [] + // [S175] Lọc phase TRƯỚC khi dựng cây ⇒ số đếm ở mọi tầng (Năm/Dự án/Hạng mục) tự khớp + // tập đã lọc. Lọc SAU khi dựng thì badge đếm sẽ nói dối. + const base = peIndex.data?.items ?? [] + const all = approvedPeOnly + ? base.filter(p => p.phase === PurchaseEvaluationPhase.DaDuyet) + : base if (!term) return all return all.filter(p => [p.projectCode, p.projectName, p.workItemName, p.maPhieu, p.tenGoiThau] .some(v => (v ?? '').toLowerCase().includes(term)), ) - }, [peIndex.data, term]) + }, [peIndex.data, term, approvedPeOnly]) const years = useMemo(() => buildTree(rows), [rows]) const packageCount = years.reduce((n, y) => n + y.projects.reduce((m, p) => m + p.workItems.length, 0), 0) @@ -342,7 +357,12 @@ export function PipelineTreePanel({ className="ml-3 border-l border-slate-200" currentStage={currentStage} hideEmpty={hideEmpty} - stages={buildStages(wg.items, stage2Options ?? {})} + stages={(() => { + const built = buildStages(wg.items, stage2Options ?? {}) + // [S175] `stagesOnly` = lọc HIỂN THỊ, KHÔNG đụng bộ dựng — + // 5 host kia không truyền thì `built` đi thẳng như cũ. + return stagesOnly ? built.filter(st => stagesOnly.includes(st.n)) : built + })()} /> ) diff --git a/fe-user/src/components/khkk/KhkkDetailContent.tsx b/fe-user/src/components/khkk/KhkkDetailContent.tsx index d8dd535..6f9441e 100644 --- a/fe-user/src/components/khkk/KhkkDetailContent.tsx +++ b/fe-user/src/components/khkk/KhkkDetailContent.tsx @@ -359,12 +359,16 @@ export function KhkkDetailContent({ // `plan.workflowSteps` VẪN có trong DTO (panel 3 dùng) — chỉ bỏ chỗ VẼ ở đây. return ( - // [S175 — anh: "Viền xanh này trong thao tác → Chọn kế hoạch"] GỠ `card-accent`. - // Class này vẽ DẢI ACCENT DỌC 3px bên trái (`index.css:112`); ở màn Thao tác nó nằm sát - // mép panel 2 nên đọc ra thành một vệt xanh đậm chạy dọc màn hình, không ra "thẻ". - // 🔴 GỠ HẲN CLASS, KHÔNG đè `border-0`: `.card-accent` nằm NGOÀI `@layer` nên thắng - // utility Tailwind v4 — đè là vô hiệu (gotcha #66, đã cắn thật ở đợt B với panel duyệt). -- File căn cứ hồ sơ / shopdrawing của kế hoạch. Gắn vào 1 căn cứ cụ thể bằng nút ⬆ ở dòng căn cứ đó. -
- )} + {/* 🧊 [S175 — anh: "Bỏ hết mấy chữ thừa này đi"] Dòng mô-tả "File căn cứ hồ sơ / + shopdrawing của kế hoạch…" ĐÃ GỠ. Nút ⬆ ở mỗi dòng căn cứ đã tự nói nó làm gì + (có `title`), không cần một câu hướng-dẫn nằm chình ình trên đầu khối. */} {mergedRows.length === 0 ? (- Đã ẩn kế hoạch ở trạng thái “Đã duyệt” — xem đủ ở menu Kế hoạch ký kết HĐ. -
- )} + {/* 🧊 [S175 — anh: "Bỏ hết mấy chữ thừa này đi"] Dòng "Đã ẩn kế hoạch ở trạng + thái Đã duyệt…" ĐÃ GỠ. Nó giải-thích CƠ-CHẾ lọc bên trong; người dùng chỉ cần + thấy đúng phiếu, không cần biết cái gì bị ẩn vì sao. */} > )} diff --git a/fe-user/src/components/pipeline/PipelineTreePanel.tsx b/fe-user/src/components/pipeline/PipelineTreePanel.tsx index bc0387b..e7176ec 100644 --- a/fe-user/src/components/pipeline/PipelineTreePanel.tsx +++ b/fe-user/src/components/pipeline/PipelineTreePanel.tsx @@ -16,7 +16,7 @@ import { cn } from '@/lib/cn' import { usePipelineStages, type BuildStagesOptions } from '@/hooks/usePipelineStages' import { PipelineStageFolders, type PipelineStageNo } from '@/components/pipeline/PipelineStageFolders' import type { Paged } from '@/types/master' -import type { PeListItem } from '@/types/purchaseEvaluation' +import { PurchaseEvaluationPhase, type PeListItem } from '@/types/purchaseEvaluation' const STORAGE_KEY = 'pipeline_tree_expanded_v1' const PE_INDEX_PAGE_SIZE = 200 @@ -90,6 +90,8 @@ export function PipelineTreePanel({ stage2Options, hideHeader, hideSearch, + approvedPeOnly, + stagesOnly, }: { currentStage: PipelineStageNo className?: string @@ -116,6 +118,14 @@ export function PipelineTreePanel({ // dùng tới — người dùng gõ phát nào là cây biến mất phát đó. // 6 host còn lại KHÔNG truyền ⇒ giữ nguyên ô lọc cây như cũ. hideSearch?: boolean + // [S175 — anh: "chỗ này chỉ cần NCC đã duyệt là đc rồi nhé, còn lại bỏ hết đi"] + // Chỉ giữ phiếu Duyệt NCC ở phase DaDuyet. Màn Thao tác dùng cây này để CHỌN PE mà lập + // kế hoạch — phiếu chưa duyệt xong thì lập không được, hiện ra chỉ tổ mời bấm nhầm. + // 🔴 OPT-IN: 5 host còn lại không truyền ⇒ cây giữ NGUYÊN mọi phase như cũ. + approvedPeOnly?: boolean + // [S175] Chỉ vẽ các folder giai-đoạn trong danh sách này (vd `[1]` = chỉ Duyệt NCC). + // Không truyền ⇒ vẽ đủ 4 giai-đoạn như cũ. + stagesOnly?: PipelineStageNo[] }) { const { canPe, buildStages } = usePipelineStages() const [search, setSearch] = useState('') @@ -154,13 +164,18 @@ export function PipelineTreePanel({ const term = search.trim().toLowerCase() const rows = useMemo(() => { - const all = peIndex.data?.items ?? [] + // [S175] Lọc phase TRƯỚC khi dựng cây ⇒ số đếm ở mọi tầng (Năm/Dự án/Hạng mục) tự khớp + // tập đã lọc. Lọc SAU khi dựng thì badge đếm sẽ nói dối. + const base = peIndex.data?.items ?? [] + const all = approvedPeOnly + ? base.filter(p => p.phase === PurchaseEvaluationPhase.DaDuyet) + : base if (!term) return all return all.filter(p => [p.projectCode, p.projectName, p.workItemName, p.maPhieu, p.tenGoiThau] .some(v => (v ?? '').toLowerCase().includes(term)), ) - }, [peIndex.data, term]) + }, [peIndex.data, term, approvedPeOnly]) const years = useMemo(() => buildTree(rows), [rows]) const packageCount = years.reduce((n, y) => n + y.projects.reduce((m, p) => m + p.workItems.length, 0), 0) @@ -342,7 +357,12 @@ export function PipelineTreePanel({ className="ml-3 border-l border-slate-200" currentStage={currentStage} hideEmpty={hideEmpty} - stages={buildStages(wg.items, stage2Options ?? {})} + stages={(() => { + const built = buildStages(wg.items, stage2Options ?? {}) + // [S175] `stagesOnly` = lọc HIỂN THỊ, KHÔNG đụng bộ dựng — + // 5 host kia không truyền thì `built` đi thẳng như cũ. + return stagesOnly ? built.filter(st => stagesOnly.includes(st.n)) : built + })()} /> )