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")}