From c01166263bacf6604ca92239ab0ff9e0e7d1328b Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 24 Apr 2026 09:29:41 +0900 Subject: [PATCH] feat: Implement sorting functionality in inspection management page - Added sorting capabilities for equipment listings, allowing users to sort by various columns such as equipment code, name, type, and more. - Introduced visual indicators for sort direction using icons (ArrowUp, ArrowDown, ArrowUpDown). - Enhanced filtering logic to accommodate sorting, improving data retrieval and user experience. These changes aim to provide users with better control over equipment data presentation and enhance overall usability in the inspection management workflow. --- .../COMPANY_10/quality/inspection/page.tsx | 100 +++++++++++++----- .../COMPANY_16/quality/inspection/page.tsx | 100 +++++++++++++----- .../COMPANY_29/quality/inspection/page.tsx | 100 +++++++++++++----- .../COMPANY_30/quality/inspection/page.tsx | 100 +++++++++++++----- .../COMPANY_7/quality/inspection/page.tsx | 100 +++++++++++++----- .../COMPANY_8/quality/inspection/page.tsx | 100 +++++++++++++----- .../COMPANY_9/quality/inspection/page.tsx | 100 +++++++++++++----- 7 files changed, 511 insertions(+), 189 deletions(-) diff --git a/frontend/app/(main)/COMPANY_10/quality/inspection/page.tsx b/frontend/app/(main)/COMPANY_10/quality/inspection/page.tsx index 8b93fa89..dadb4a2f 100644 --- a/frontend/app/(main)/COMPANY_10/quality/inspection/page.tsx +++ b/frontend/app/(main)/COMPANY_10/quality/inspection/page.tsx @@ -30,6 +30,9 @@ import { Inbox, Settings2, Upload, + ArrowUpDown, + ArrowUp, + ArrowDown, } from "lucide-react"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { ImageUpload } from "@/components/common/ImageUpload"; @@ -113,6 +116,8 @@ export default function InspectionManagementPage() { const [eqForm, setEqForm] = useState>({}); const [eqSaving, setEqSaving] = useState(false); const [eqKeyword, setEqKeyword] = useState(""); + const [eqSortKey, setEqSortKey] = useState(null); + const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc"); /* ───── 채번 ───── */ const [numberingRuleId, setNumberingRuleId] = useState(null); @@ -288,13 +293,54 @@ export default function InspectionManagementPage() { ) : defects; - const filteredEquipments = eqKeyword.trim() - ? equipments.filter( - (r) => - (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || - (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), - ) - : equipments; + const filteredEquipments = useMemo(() => { + const base = eqKeyword.trim() + ? equipments.filter( + (r) => + (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || + (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), + ) + : equipments; + if (!eqSortKey) return base; + const key = eqSortKey; + const dir = eqSortDir; + return [...base].sort((a, b) => { + const av = a[key]; + const bv = b[key]; + const aEmpty = av === null || av === undefined || av === ""; + const bEmpty = bv === null || bv === undefined || bv === ""; + if (aEmpty && bEmpty) return 0; + if (aEmpty) return 1; + if (bEmpty) return -1; + const na = Number(av); + const nb = Number(bv); + if (!isNaN(na) && !isNaN(nb)) return dir === "asc" ? na - nb : nb - na; + const sa = String(av).toLowerCase(); + const sb = String(bv).toLowerCase(); + return dir === "asc" ? sa.localeCompare(sb) : sb.localeCompare(sa); + }); + }, [equipments, eqKeyword, eqSortKey, eqSortDir]); + + const handleEqSort = useCallback((key: string) => { + setEqSortKey((prev) => { + if (prev === key) { + setEqSortDir((d) => (d === "asc" ? "desc" : "asc")); + return prev; + } + setEqSortDir("asc"); + return key; + }); + }, []); + + const renderEqSortIcon = (key: string) => { + if (eqSortKey !== key) + return ; + return eqSortDir === "asc" ? ( + + ) : ( + + ); + }; /* ═══════════════════ 검사기준 CRUD ═══════════════════ */ const openInspCreate = async () => { @@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() { 이미지 - - 장비코드 + handleEqSort("equipment_code")}> + 장비코드{renderEqSortIcon("equipment_code")} - - 장비명 + handleEqSort("equipment_name")}> + 장비명{renderEqSortIcon("equipment_name")} - - 장비유형 + handleEqSort("equipment_type")}> + 장비유형{renderEqSortIcon("equipment_type")} - - 모델명 + handleEqSort("model_name")}> + 모델명{renderEqSortIcon("model_name")} - - 제조사 + handleEqSort("manufacturer")}> + 제조사{renderEqSortIcon("manufacturer")} - - 설치장소 + handleEqSort("installation_location")}> + 설치장소{renderEqSortIcon("installation_location")} - - 최근교정일 + handleEqSort("last_calibration_date")}> + 최근교정일{renderEqSortIcon("last_calibration_date")} - - 교정주기(개월) + handleEqSort("calibration_period")}> + 교정주기(개월){renderEqSortIcon("calibration_period")} - - 장비상태 + handleEqSort("equipment_status")}> + 장비상태{renderEqSortIcon("equipment_status")} - - 담당자 + handleEqSort("manager_id")}> + 담당자{renderEqSortIcon("manager_id")} diff --git a/frontend/app/(main)/COMPANY_16/quality/inspection/page.tsx b/frontend/app/(main)/COMPANY_16/quality/inspection/page.tsx index c0a7b6b9..12f837fb 100644 --- a/frontend/app/(main)/COMPANY_16/quality/inspection/page.tsx +++ b/frontend/app/(main)/COMPANY_16/quality/inspection/page.tsx @@ -30,6 +30,9 @@ import { Inbox, Settings2, Upload, + ArrowUpDown, + ArrowUp, + ArrowDown, } from "lucide-react"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { ImageUpload } from "@/components/common/ImageUpload"; @@ -113,6 +116,8 @@ export default function InspectionManagementPage() { const [eqForm, setEqForm] = useState>({}); const [eqSaving, setEqSaving] = useState(false); const [eqKeyword, setEqKeyword] = useState(""); + const [eqSortKey, setEqSortKey] = useState(null); + const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc"); /* ───── 채번 ───── */ const [numberingRuleId, setNumberingRuleId] = useState(null); @@ -288,13 +293,54 @@ export default function InspectionManagementPage() { ) : defects; - const filteredEquipments = eqKeyword.trim() - ? equipments.filter( - (r) => - (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || - (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), - ) - : equipments; + const filteredEquipments = useMemo(() => { + const base = eqKeyword.trim() + ? equipments.filter( + (r) => + (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || + (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), + ) + : equipments; + if (!eqSortKey) return base; + const key = eqSortKey; + const dir = eqSortDir; + return [...base].sort((a, b) => { + const av = a[key]; + const bv = b[key]; + const aEmpty = av === null || av === undefined || av === ""; + const bEmpty = bv === null || bv === undefined || bv === ""; + if (aEmpty && bEmpty) return 0; + if (aEmpty) return 1; + if (bEmpty) return -1; + const na = Number(av); + const nb = Number(bv); + if (!isNaN(na) && !isNaN(nb)) return dir === "asc" ? na - nb : nb - na; + const sa = String(av).toLowerCase(); + const sb = String(bv).toLowerCase(); + return dir === "asc" ? sa.localeCompare(sb) : sb.localeCompare(sa); + }); + }, [equipments, eqKeyword, eqSortKey, eqSortDir]); + + const handleEqSort = useCallback((key: string) => { + setEqSortKey((prev) => { + if (prev === key) { + setEqSortDir((d) => (d === "asc" ? "desc" : "asc")); + return prev; + } + setEqSortDir("asc"); + return key; + }); + }, []); + + const renderEqSortIcon = (key: string) => { + if (eqSortKey !== key) + return ; + return eqSortDir === "asc" ? ( + + ) : ( + + ); + }; /* ═══════════════════ 검사기준 CRUD ═══════════════════ */ const openInspCreate = async () => { @@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() { 이미지 - - 장비코드 + handleEqSort("equipment_code")}> + 장비코드{renderEqSortIcon("equipment_code")} - - 장비명 + handleEqSort("equipment_name")}> + 장비명{renderEqSortIcon("equipment_name")} - - 장비유형 + handleEqSort("equipment_type")}> + 장비유형{renderEqSortIcon("equipment_type")} - - 모델명 + handleEqSort("model_name")}> + 모델명{renderEqSortIcon("model_name")} - - 제조사 + handleEqSort("manufacturer")}> + 제조사{renderEqSortIcon("manufacturer")} - - 설치장소 + handleEqSort("installation_location")}> + 설치장소{renderEqSortIcon("installation_location")} - - 최근교정일 + handleEqSort("last_calibration_date")}> + 최근교정일{renderEqSortIcon("last_calibration_date")} - - 교정주기(개월) + handleEqSort("calibration_period")}> + 교정주기(개월){renderEqSortIcon("calibration_period")} - - 장비상태 + handleEqSort("equipment_status")}> + 장비상태{renderEqSortIcon("equipment_status")} - - 담당자 + handleEqSort("manager_id")}> + 담당자{renderEqSortIcon("manager_id")} diff --git a/frontend/app/(main)/COMPANY_29/quality/inspection/page.tsx b/frontend/app/(main)/COMPANY_29/quality/inspection/page.tsx index 8b93fa89..dadb4a2f 100644 --- a/frontend/app/(main)/COMPANY_29/quality/inspection/page.tsx +++ b/frontend/app/(main)/COMPANY_29/quality/inspection/page.tsx @@ -30,6 +30,9 @@ import { Inbox, Settings2, Upload, + ArrowUpDown, + ArrowUp, + ArrowDown, } from "lucide-react"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { ImageUpload } from "@/components/common/ImageUpload"; @@ -113,6 +116,8 @@ export default function InspectionManagementPage() { const [eqForm, setEqForm] = useState>({}); const [eqSaving, setEqSaving] = useState(false); const [eqKeyword, setEqKeyword] = useState(""); + const [eqSortKey, setEqSortKey] = useState(null); + const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc"); /* ───── 채번 ───── */ const [numberingRuleId, setNumberingRuleId] = useState(null); @@ -288,13 +293,54 @@ export default function InspectionManagementPage() { ) : defects; - const filteredEquipments = eqKeyword.trim() - ? equipments.filter( - (r) => - (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || - (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), - ) - : equipments; + const filteredEquipments = useMemo(() => { + const base = eqKeyword.trim() + ? equipments.filter( + (r) => + (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || + (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), + ) + : equipments; + if (!eqSortKey) return base; + const key = eqSortKey; + const dir = eqSortDir; + return [...base].sort((a, b) => { + const av = a[key]; + const bv = b[key]; + const aEmpty = av === null || av === undefined || av === ""; + const bEmpty = bv === null || bv === undefined || bv === ""; + if (aEmpty && bEmpty) return 0; + if (aEmpty) return 1; + if (bEmpty) return -1; + const na = Number(av); + const nb = Number(bv); + if (!isNaN(na) && !isNaN(nb)) return dir === "asc" ? na - nb : nb - na; + const sa = String(av).toLowerCase(); + const sb = String(bv).toLowerCase(); + return dir === "asc" ? sa.localeCompare(sb) : sb.localeCompare(sa); + }); + }, [equipments, eqKeyword, eqSortKey, eqSortDir]); + + const handleEqSort = useCallback((key: string) => { + setEqSortKey((prev) => { + if (prev === key) { + setEqSortDir((d) => (d === "asc" ? "desc" : "asc")); + return prev; + } + setEqSortDir("asc"); + return key; + }); + }, []); + + const renderEqSortIcon = (key: string) => { + if (eqSortKey !== key) + return ; + return eqSortDir === "asc" ? ( + + ) : ( + + ); + }; /* ═══════════════════ 검사기준 CRUD ═══════════════════ */ const openInspCreate = async () => { @@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() { 이미지 - - 장비코드 + handleEqSort("equipment_code")}> + 장비코드{renderEqSortIcon("equipment_code")} - - 장비명 + handleEqSort("equipment_name")}> + 장비명{renderEqSortIcon("equipment_name")} - - 장비유형 + handleEqSort("equipment_type")}> + 장비유형{renderEqSortIcon("equipment_type")} - - 모델명 + handleEqSort("model_name")}> + 모델명{renderEqSortIcon("model_name")} - - 제조사 + handleEqSort("manufacturer")}> + 제조사{renderEqSortIcon("manufacturer")} - - 설치장소 + handleEqSort("installation_location")}> + 설치장소{renderEqSortIcon("installation_location")} - - 최근교정일 + handleEqSort("last_calibration_date")}> + 최근교정일{renderEqSortIcon("last_calibration_date")} - - 교정주기(개월) + handleEqSort("calibration_period")}> + 교정주기(개월){renderEqSortIcon("calibration_period")} - - 장비상태 + handleEqSort("equipment_status")}> + 장비상태{renderEqSortIcon("equipment_status")} - - 담당자 + handleEqSort("manager_id")}> + 담당자{renderEqSortIcon("manager_id")} diff --git a/frontend/app/(main)/COMPANY_30/quality/inspection/page.tsx b/frontend/app/(main)/COMPANY_30/quality/inspection/page.tsx index 53f0e142..ced73cbe 100644 --- a/frontend/app/(main)/COMPANY_30/quality/inspection/page.tsx +++ b/frontend/app/(main)/COMPANY_30/quality/inspection/page.tsx @@ -30,6 +30,9 @@ import { Inbox, Settings2, Upload, + ArrowUpDown, + ArrowUp, + ArrowDown, } from "lucide-react"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { ImageUpload } from "@/components/common/ImageUpload"; @@ -113,6 +116,8 @@ export default function InspectionManagementPage() { const [eqForm, setEqForm] = useState>({}); const [eqSaving, setEqSaving] = useState(false); const [eqKeyword, setEqKeyword] = useState(""); + const [eqSortKey, setEqSortKey] = useState(null); + const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc"); /* ───── 채번 ───── */ const [numberingRuleId, setNumberingRuleId] = useState(null); @@ -288,13 +293,54 @@ export default function InspectionManagementPage() { ) : defects; - const filteredEquipments = eqKeyword.trim() - ? equipments.filter( - (r) => - (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || - (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), - ) - : equipments; + const filteredEquipments = useMemo(() => { + const base = eqKeyword.trim() + ? equipments.filter( + (r) => + (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || + (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), + ) + : equipments; + if (!eqSortKey) return base; + const key = eqSortKey; + const dir = eqSortDir; + return [...base].sort((a, b) => { + const av = a[key]; + const bv = b[key]; + const aEmpty = av === null || av === undefined || av === ""; + const bEmpty = bv === null || bv === undefined || bv === ""; + if (aEmpty && bEmpty) return 0; + if (aEmpty) return 1; + if (bEmpty) return -1; + const na = Number(av); + const nb = Number(bv); + if (!isNaN(na) && !isNaN(nb)) return dir === "asc" ? na - nb : nb - na; + const sa = String(av).toLowerCase(); + const sb = String(bv).toLowerCase(); + return dir === "asc" ? sa.localeCompare(sb) : sb.localeCompare(sa); + }); + }, [equipments, eqKeyword, eqSortKey, eqSortDir]); + + const handleEqSort = useCallback((key: string) => { + setEqSortKey((prev) => { + if (prev === key) { + setEqSortDir((d) => (d === "asc" ? "desc" : "asc")); + return prev; + } + setEqSortDir("asc"); + return key; + }); + }, []); + + const renderEqSortIcon = (key: string) => { + if (eqSortKey !== key) + return ; + return eqSortDir === "asc" ? ( + + ) : ( + + ); + }; /* ═══════════════════ 검사기준 CRUD ═══════════════════ */ const openInspCreate = async () => { @@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() { 이미지 - - 장비코드 + handleEqSort("equipment_code")}> + 장비코드{renderEqSortIcon("equipment_code")} - - 장비명 + handleEqSort("equipment_name")}> + 장비명{renderEqSortIcon("equipment_name")} - - 장비유형 + handleEqSort("equipment_type")}> + 장비유형{renderEqSortIcon("equipment_type")} - - 모델명 + handleEqSort("model_name")}> + 모델명{renderEqSortIcon("model_name")} - - 제조사 + handleEqSort("manufacturer")}> + 제조사{renderEqSortIcon("manufacturer")} - - 설치장소 + handleEqSort("installation_location")}> + 설치장소{renderEqSortIcon("installation_location")} - - 최근교정일 + handleEqSort("last_calibration_date")}> + 최근교정일{renderEqSortIcon("last_calibration_date")} - - 교정주기(개월) + handleEqSort("calibration_period")}> + 교정주기(개월){renderEqSortIcon("calibration_period")} - - 장비상태 + handleEqSort("equipment_status")}> + 장비상태{renderEqSortIcon("equipment_status")} - - 담당자 + handleEqSort("manager_id")}> + 담당자{renderEqSortIcon("manager_id")} diff --git a/frontend/app/(main)/COMPANY_7/quality/inspection/page.tsx b/frontend/app/(main)/COMPANY_7/quality/inspection/page.tsx index 8b93fa89..dadb4a2f 100644 --- a/frontend/app/(main)/COMPANY_7/quality/inspection/page.tsx +++ b/frontend/app/(main)/COMPANY_7/quality/inspection/page.tsx @@ -30,6 +30,9 @@ import { Inbox, Settings2, Upload, + ArrowUpDown, + ArrowUp, + ArrowDown, } from "lucide-react"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { ImageUpload } from "@/components/common/ImageUpload"; @@ -113,6 +116,8 @@ export default function InspectionManagementPage() { const [eqForm, setEqForm] = useState>({}); const [eqSaving, setEqSaving] = useState(false); const [eqKeyword, setEqKeyword] = useState(""); + const [eqSortKey, setEqSortKey] = useState(null); + const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc"); /* ───── 채번 ───── */ const [numberingRuleId, setNumberingRuleId] = useState(null); @@ -288,13 +293,54 @@ export default function InspectionManagementPage() { ) : defects; - const filteredEquipments = eqKeyword.trim() - ? equipments.filter( - (r) => - (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || - (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), - ) - : equipments; + const filteredEquipments = useMemo(() => { + const base = eqKeyword.trim() + ? equipments.filter( + (r) => + (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || + (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), + ) + : equipments; + if (!eqSortKey) return base; + const key = eqSortKey; + const dir = eqSortDir; + return [...base].sort((a, b) => { + const av = a[key]; + const bv = b[key]; + const aEmpty = av === null || av === undefined || av === ""; + const bEmpty = bv === null || bv === undefined || bv === ""; + if (aEmpty && bEmpty) return 0; + if (aEmpty) return 1; + if (bEmpty) return -1; + const na = Number(av); + const nb = Number(bv); + if (!isNaN(na) && !isNaN(nb)) return dir === "asc" ? na - nb : nb - na; + const sa = String(av).toLowerCase(); + const sb = String(bv).toLowerCase(); + return dir === "asc" ? sa.localeCompare(sb) : sb.localeCompare(sa); + }); + }, [equipments, eqKeyword, eqSortKey, eqSortDir]); + + const handleEqSort = useCallback((key: string) => { + setEqSortKey((prev) => { + if (prev === key) { + setEqSortDir((d) => (d === "asc" ? "desc" : "asc")); + return prev; + } + setEqSortDir("asc"); + return key; + }); + }, []); + + const renderEqSortIcon = (key: string) => { + if (eqSortKey !== key) + return ; + return eqSortDir === "asc" ? ( + + ) : ( + + ); + }; /* ═══════════════════ 검사기준 CRUD ═══════════════════ */ const openInspCreate = async () => { @@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() { 이미지 - - 장비코드 + handleEqSort("equipment_code")}> + 장비코드{renderEqSortIcon("equipment_code")} - - 장비명 + handleEqSort("equipment_name")}> + 장비명{renderEqSortIcon("equipment_name")} - - 장비유형 + handleEqSort("equipment_type")}> + 장비유형{renderEqSortIcon("equipment_type")} - - 모델명 + handleEqSort("model_name")}> + 모델명{renderEqSortIcon("model_name")} - - 제조사 + handleEqSort("manufacturer")}> + 제조사{renderEqSortIcon("manufacturer")} - - 설치장소 + handleEqSort("installation_location")}> + 설치장소{renderEqSortIcon("installation_location")} - - 최근교정일 + handleEqSort("last_calibration_date")}> + 최근교정일{renderEqSortIcon("last_calibration_date")} - - 교정주기(개월) + handleEqSort("calibration_period")}> + 교정주기(개월){renderEqSortIcon("calibration_period")} - - 장비상태 + handleEqSort("equipment_status")}> + 장비상태{renderEqSortIcon("equipment_status")} - - 담당자 + handleEqSort("manager_id")}> + 담당자{renderEqSortIcon("manager_id")} diff --git a/frontend/app/(main)/COMPANY_8/quality/inspection/page.tsx b/frontend/app/(main)/COMPANY_8/quality/inspection/page.tsx index 8b93fa89..dadb4a2f 100644 --- a/frontend/app/(main)/COMPANY_8/quality/inspection/page.tsx +++ b/frontend/app/(main)/COMPANY_8/quality/inspection/page.tsx @@ -30,6 +30,9 @@ import { Inbox, Settings2, Upload, + ArrowUpDown, + ArrowUp, + ArrowDown, } from "lucide-react"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { ImageUpload } from "@/components/common/ImageUpload"; @@ -113,6 +116,8 @@ export default function InspectionManagementPage() { const [eqForm, setEqForm] = useState>({}); const [eqSaving, setEqSaving] = useState(false); const [eqKeyword, setEqKeyword] = useState(""); + const [eqSortKey, setEqSortKey] = useState(null); + const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc"); /* ───── 채번 ───── */ const [numberingRuleId, setNumberingRuleId] = useState(null); @@ -288,13 +293,54 @@ export default function InspectionManagementPage() { ) : defects; - const filteredEquipments = eqKeyword.trim() - ? equipments.filter( - (r) => - (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || - (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), - ) - : equipments; + const filteredEquipments = useMemo(() => { + const base = eqKeyword.trim() + ? equipments.filter( + (r) => + (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || + (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), + ) + : equipments; + if (!eqSortKey) return base; + const key = eqSortKey; + const dir = eqSortDir; + return [...base].sort((a, b) => { + const av = a[key]; + const bv = b[key]; + const aEmpty = av === null || av === undefined || av === ""; + const bEmpty = bv === null || bv === undefined || bv === ""; + if (aEmpty && bEmpty) return 0; + if (aEmpty) return 1; + if (bEmpty) return -1; + const na = Number(av); + const nb = Number(bv); + if (!isNaN(na) && !isNaN(nb)) return dir === "asc" ? na - nb : nb - na; + const sa = String(av).toLowerCase(); + const sb = String(bv).toLowerCase(); + return dir === "asc" ? sa.localeCompare(sb) : sb.localeCompare(sa); + }); + }, [equipments, eqKeyword, eqSortKey, eqSortDir]); + + const handleEqSort = useCallback((key: string) => { + setEqSortKey((prev) => { + if (prev === key) { + setEqSortDir((d) => (d === "asc" ? "desc" : "asc")); + return prev; + } + setEqSortDir("asc"); + return key; + }); + }, []); + + const renderEqSortIcon = (key: string) => { + if (eqSortKey !== key) + return ; + return eqSortDir === "asc" ? ( + + ) : ( + + ); + }; /* ═══════════════════ 검사기준 CRUD ═══════════════════ */ const openInspCreate = async () => { @@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() { 이미지 - - 장비코드 + handleEqSort("equipment_code")}> + 장비코드{renderEqSortIcon("equipment_code")} - - 장비명 + handleEqSort("equipment_name")}> + 장비명{renderEqSortIcon("equipment_name")} - - 장비유형 + handleEqSort("equipment_type")}> + 장비유형{renderEqSortIcon("equipment_type")} - - 모델명 + handleEqSort("model_name")}> + 모델명{renderEqSortIcon("model_name")} - - 제조사 + handleEqSort("manufacturer")}> + 제조사{renderEqSortIcon("manufacturer")} - - 설치장소 + handleEqSort("installation_location")}> + 설치장소{renderEqSortIcon("installation_location")} - - 최근교정일 + handleEqSort("last_calibration_date")}> + 최근교정일{renderEqSortIcon("last_calibration_date")} - - 교정주기(개월) + handleEqSort("calibration_period")}> + 교정주기(개월){renderEqSortIcon("calibration_period")} - - 장비상태 + handleEqSort("equipment_status")}> + 장비상태{renderEqSortIcon("equipment_status")} - - 담당자 + handleEqSort("manager_id")}> + 담당자{renderEqSortIcon("manager_id")} diff --git a/frontend/app/(main)/COMPANY_9/quality/inspection/page.tsx b/frontend/app/(main)/COMPANY_9/quality/inspection/page.tsx index 3edaadc3..83a712d5 100644 --- a/frontend/app/(main)/COMPANY_9/quality/inspection/page.tsx +++ b/frontend/app/(main)/COMPANY_9/quality/inspection/page.tsx @@ -30,6 +30,9 @@ import { Inbox, Settings2, Upload, + ArrowUpDown, + ArrowUp, + ArrowDown, } from "lucide-react"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { ImageUpload } from "@/components/common/ImageUpload"; @@ -113,6 +116,8 @@ export default function InspectionManagementPage() { const [eqForm, setEqForm] = useState>({}); const [eqSaving, setEqSaving] = useState(false); const [eqKeyword, setEqKeyword] = useState(""); + const [eqSortKey, setEqSortKey] = useState(null); + const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc"); /* ───── 채번 ───── */ const [numberingRuleId, setNumberingRuleId] = useState(null); @@ -288,13 +293,54 @@ export default function InspectionManagementPage() { ) : defects; - const filteredEquipments = eqKeyword.trim() - ? equipments.filter( - (r) => - (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || - (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), - ) - : equipments; + const filteredEquipments = useMemo(() => { + const base = eqKeyword.trim() + ? equipments.filter( + (r) => + (r.equipment_name || "").toLowerCase().includes(eqKeyword.toLowerCase()) || + (r.model_name || "").toLowerCase().includes(eqKeyword.toLowerCase()), + ) + : equipments; + if (!eqSortKey) return base; + const key = eqSortKey; + const dir = eqSortDir; + return [...base].sort((a, b) => { + const av = a[key]; + const bv = b[key]; + const aEmpty = av === null || av === undefined || av === ""; + const bEmpty = bv === null || bv === undefined || bv === ""; + if (aEmpty && bEmpty) return 0; + if (aEmpty) return 1; + if (bEmpty) return -1; + const na = Number(av); + const nb = Number(bv); + if (!isNaN(na) && !isNaN(nb)) return dir === "asc" ? na - nb : nb - na; + const sa = String(av).toLowerCase(); + const sb = String(bv).toLowerCase(); + return dir === "asc" ? sa.localeCompare(sb) : sb.localeCompare(sa); + }); + }, [equipments, eqKeyword, eqSortKey, eqSortDir]); + + const handleEqSort = useCallback((key: string) => { + setEqSortKey((prev) => { + if (prev === key) { + setEqSortDir((d) => (d === "asc" ? "desc" : "asc")); + return prev; + } + setEqSortDir("asc"); + return key; + }); + }, []); + + const renderEqSortIcon = (key: string) => { + if (eqSortKey !== key) + return ; + return eqSortDir === "asc" ? ( + + ) : ( + + ); + }; /* ═══════════════════ 검사기준 CRUD ═══════════════════ */ const openInspCreate = async () => { @@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() { 이미지 - - 장비코드 + handleEqSort("equipment_code")}> + 장비코드{renderEqSortIcon("equipment_code")} - - 장비명 + handleEqSort("equipment_name")}> + 장비명{renderEqSortIcon("equipment_name")} - - 장비유형 + handleEqSort("equipment_type")}> + 장비유형{renderEqSortIcon("equipment_type")} - - 모델명 + handleEqSort("model_name")}> + 모델명{renderEqSortIcon("model_name")} - - 제조사 + handleEqSort("manufacturer")}> + 제조사{renderEqSortIcon("manufacturer")} - - 설치장소 + handleEqSort("installation_location")}> + 설치장소{renderEqSortIcon("installation_location")} - - 최근교정일 + handleEqSort("last_calibration_date")}> + 최근교정일{renderEqSortIcon("last_calibration_date")} - - 교정주기(개월) + handleEqSort("calibration_period")}> + 교정주기(개월){renderEqSortIcon("calibration_period")} - - 장비상태 + handleEqSort("equipment_status")}> + 장비상태{renderEqSortIcon("equipment_status")} - - 담당자 + handleEqSort("manager_id")}> + 담당자{renderEqSortIcon("manager_id")}