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.
This commit is contained in:
@@ -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<Record<string, any>>({});
|
||||
const [eqSaving, setEqSaving] = useState(false);
|
||||
const [eqKeyword, setEqKeyword] = useState("");
|
||||
const [eqSortKey, setEqSortKey] = useState<string | null>(null);
|
||||
const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc");
|
||||
|
||||
/* ───── 채번 ───── */
|
||||
const [numberingRuleId, setNumberingRuleId] = useState<string | null>(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 <ArrowUpDown className="ml-1 inline h-3 w-3 opacity-40" />;
|
||||
return eqSortDir === "asc" ? (
|
||||
<ArrowUp className="ml-1 inline h-3 w-3" />
|
||||
) : (
|
||||
<ArrowDown className="ml-1 inline h-3 w-3" />
|
||||
);
|
||||
};
|
||||
|
||||
/* ═══════════════════ 검사기준 CRUD ═══════════════════ */
|
||||
const openInspCreate = async () => {
|
||||
@@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() {
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase w-[60px] text-center">
|
||||
이미지
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비코드
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_code")}>
|
||||
장비코드{renderEqSortIcon("equipment_code")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_name")}>
|
||||
장비명{renderEqSortIcon("equipment_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비유형
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_type")}>
|
||||
장비유형{renderEqSortIcon("equipment_type")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
모델명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("model_name")}>
|
||||
모델명{renderEqSortIcon("model_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
제조사
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manufacturer")}>
|
||||
제조사{renderEqSortIcon("manufacturer")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
설치장소
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("installation_location")}>
|
||||
설치장소{renderEqSortIcon("installation_location")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
최근교정일
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("last_calibration_date")}>
|
||||
최근교정일{renderEqSortIcon("last_calibration_date")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
교정주기(개월)
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("calibration_period")}>
|
||||
교정주기(개월){renderEqSortIcon("calibration_period")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비상태
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_status")}>
|
||||
장비상태{renderEqSortIcon("equipment_status")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
담당자
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manager_id")}>
|
||||
담당자{renderEqSortIcon("manager_id")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -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<Record<string, any>>({});
|
||||
const [eqSaving, setEqSaving] = useState(false);
|
||||
const [eqKeyword, setEqKeyword] = useState("");
|
||||
const [eqSortKey, setEqSortKey] = useState<string | null>(null);
|
||||
const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc");
|
||||
|
||||
/* ───── 채번 ───── */
|
||||
const [numberingRuleId, setNumberingRuleId] = useState<string | null>(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 <ArrowUpDown className="ml-1 inline h-3 w-3 opacity-40" />;
|
||||
return eqSortDir === "asc" ? (
|
||||
<ArrowUp className="ml-1 inline h-3 w-3" />
|
||||
) : (
|
||||
<ArrowDown className="ml-1 inline h-3 w-3" />
|
||||
);
|
||||
};
|
||||
|
||||
/* ═══════════════════ 검사기준 CRUD ═══════════════════ */
|
||||
const openInspCreate = async () => {
|
||||
@@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() {
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase w-[60px] text-center">
|
||||
이미지
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비코드
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_code")}>
|
||||
장비코드{renderEqSortIcon("equipment_code")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_name")}>
|
||||
장비명{renderEqSortIcon("equipment_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비유형
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_type")}>
|
||||
장비유형{renderEqSortIcon("equipment_type")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
모델명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("model_name")}>
|
||||
모델명{renderEqSortIcon("model_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
제조사
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manufacturer")}>
|
||||
제조사{renderEqSortIcon("manufacturer")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
설치장소
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("installation_location")}>
|
||||
설치장소{renderEqSortIcon("installation_location")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
최근교정일
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("last_calibration_date")}>
|
||||
최근교정일{renderEqSortIcon("last_calibration_date")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
교정주기(개월)
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("calibration_period")}>
|
||||
교정주기(개월){renderEqSortIcon("calibration_period")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비상태
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_status")}>
|
||||
장비상태{renderEqSortIcon("equipment_status")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
담당자
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manager_id")}>
|
||||
담당자{renderEqSortIcon("manager_id")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -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<Record<string, any>>({});
|
||||
const [eqSaving, setEqSaving] = useState(false);
|
||||
const [eqKeyword, setEqKeyword] = useState("");
|
||||
const [eqSortKey, setEqSortKey] = useState<string | null>(null);
|
||||
const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc");
|
||||
|
||||
/* ───── 채번 ───── */
|
||||
const [numberingRuleId, setNumberingRuleId] = useState<string | null>(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 <ArrowUpDown className="ml-1 inline h-3 w-3 opacity-40" />;
|
||||
return eqSortDir === "asc" ? (
|
||||
<ArrowUp className="ml-1 inline h-3 w-3" />
|
||||
) : (
|
||||
<ArrowDown className="ml-1 inline h-3 w-3" />
|
||||
);
|
||||
};
|
||||
|
||||
/* ═══════════════════ 검사기준 CRUD ═══════════════════ */
|
||||
const openInspCreate = async () => {
|
||||
@@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() {
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase w-[60px] text-center">
|
||||
이미지
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비코드
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_code")}>
|
||||
장비코드{renderEqSortIcon("equipment_code")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_name")}>
|
||||
장비명{renderEqSortIcon("equipment_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비유형
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_type")}>
|
||||
장비유형{renderEqSortIcon("equipment_type")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
모델명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("model_name")}>
|
||||
모델명{renderEqSortIcon("model_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
제조사
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manufacturer")}>
|
||||
제조사{renderEqSortIcon("manufacturer")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
설치장소
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("installation_location")}>
|
||||
설치장소{renderEqSortIcon("installation_location")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
최근교정일
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("last_calibration_date")}>
|
||||
최근교정일{renderEqSortIcon("last_calibration_date")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
교정주기(개월)
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("calibration_period")}>
|
||||
교정주기(개월){renderEqSortIcon("calibration_period")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비상태
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_status")}>
|
||||
장비상태{renderEqSortIcon("equipment_status")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
담당자
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manager_id")}>
|
||||
담당자{renderEqSortIcon("manager_id")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -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<Record<string, any>>({});
|
||||
const [eqSaving, setEqSaving] = useState(false);
|
||||
const [eqKeyword, setEqKeyword] = useState("");
|
||||
const [eqSortKey, setEqSortKey] = useState<string | null>(null);
|
||||
const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc");
|
||||
|
||||
/* ───── 채번 ───── */
|
||||
const [numberingRuleId, setNumberingRuleId] = useState<string | null>(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 <ArrowUpDown className="ml-1 inline h-3 w-3 opacity-40" />;
|
||||
return eqSortDir === "asc" ? (
|
||||
<ArrowUp className="ml-1 inline h-3 w-3" />
|
||||
) : (
|
||||
<ArrowDown className="ml-1 inline h-3 w-3" />
|
||||
);
|
||||
};
|
||||
|
||||
/* ═══════════════════ 검사기준 CRUD ═══════════════════ */
|
||||
const openInspCreate = async () => {
|
||||
@@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() {
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase w-[60px] text-center">
|
||||
이미지
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비코드
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_code")}>
|
||||
장비코드{renderEqSortIcon("equipment_code")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_name")}>
|
||||
장비명{renderEqSortIcon("equipment_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비유형
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_type")}>
|
||||
장비유형{renderEqSortIcon("equipment_type")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
모델명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("model_name")}>
|
||||
모델명{renderEqSortIcon("model_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
제조사
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manufacturer")}>
|
||||
제조사{renderEqSortIcon("manufacturer")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
설치장소
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("installation_location")}>
|
||||
설치장소{renderEqSortIcon("installation_location")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
최근교정일
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("last_calibration_date")}>
|
||||
최근교정일{renderEqSortIcon("last_calibration_date")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
교정주기(개월)
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("calibration_period")}>
|
||||
교정주기(개월){renderEqSortIcon("calibration_period")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비상태
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_status")}>
|
||||
장비상태{renderEqSortIcon("equipment_status")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
담당자
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manager_id")}>
|
||||
담당자{renderEqSortIcon("manager_id")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -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<Record<string, any>>({});
|
||||
const [eqSaving, setEqSaving] = useState(false);
|
||||
const [eqKeyword, setEqKeyword] = useState("");
|
||||
const [eqSortKey, setEqSortKey] = useState<string | null>(null);
|
||||
const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc");
|
||||
|
||||
/* ───── 채번 ───── */
|
||||
const [numberingRuleId, setNumberingRuleId] = useState<string | null>(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 <ArrowUpDown className="ml-1 inline h-3 w-3 opacity-40" />;
|
||||
return eqSortDir === "asc" ? (
|
||||
<ArrowUp className="ml-1 inline h-3 w-3" />
|
||||
) : (
|
||||
<ArrowDown className="ml-1 inline h-3 w-3" />
|
||||
);
|
||||
};
|
||||
|
||||
/* ═══════════════════ 검사기준 CRUD ═══════════════════ */
|
||||
const openInspCreate = async () => {
|
||||
@@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() {
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase w-[60px] text-center">
|
||||
이미지
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비코드
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_code")}>
|
||||
장비코드{renderEqSortIcon("equipment_code")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_name")}>
|
||||
장비명{renderEqSortIcon("equipment_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비유형
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_type")}>
|
||||
장비유형{renderEqSortIcon("equipment_type")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
모델명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("model_name")}>
|
||||
모델명{renderEqSortIcon("model_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
제조사
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manufacturer")}>
|
||||
제조사{renderEqSortIcon("manufacturer")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
설치장소
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("installation_location")}>
|
||||
설치장소{renderEqSortIcon("installation_location")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
최근교정일
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("last_calibration_date")}>
|
||||
최근교정일{renderEqSortIcon("last_calibration_date")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
교정주기(개월)
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("calibration_period")}>
|
||||
교정주기(개월){renderEqSortIcon("calibration_period")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비상태
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_status")}>
|
||||
장비상태{renderEqSortIcon("equipment_status")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
담당자
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manager_id")}>
|
||||
담당자{renderEqSortIcon("manager_id")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -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<Record<string, any>>({});
|
||||
const [eqSaving, setEqSaving] = useState(false);
|
||||
const [eqKeyword, setEqKeyword] = useState("");
|
||||
const [eqSortKey, setEqSortKey] = useState<string | null>(null);
|
||||
const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc");
|
||||
|
||||
/* ───── 채번 ───── */
|
||||
const [numberingRuleId, setNumberingRuleId] = useState<string | null>(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 <ArrowUpDown className="ml-1 inline h-3 w-3 opacity-40" />;
|
||||
return eqSortDir === "asc" ? (
|
||||
<ArrowUp className="ml-1 inline h-3 w-3" />
|
||||
) : (
|
||||
<ArrowDown className="ml-1 inline h-3 w-3" />
|
||||
);
|
||||
};
|
||||
|
||||
/* ═══════════════════ 검사기준 CRUD ═══════════════════ */
|
||||
const openInspCreate = async () => {
|
||||
@@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() {
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase w-[60px] text-center">
|
||||
이미지
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비코드
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_code")}>
|
||||
장비코드{renderEqSortIcon("equipment_code")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_name")}>
|
||||
장비명{renderEqSortIcon("equipment_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비유형
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_type")}>
|
||||
장비유형{renderEqSortIcon("equipment_type")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
모델명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("model_name")}>
|
||||
모델명{renderEqSortIcon("model_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
제조사
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manufacturer")}>
|
||||
제조사{renderEqSortIcon("manufacturer")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
설치장소
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("installation_location")}>
|
||||
설치장소{renderEqSortIcon("installation_location")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
최근교정일
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("last_calibration_date")}>
|
||||
최근교정일{renderEqSortIcon("last_calibration_date")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
교정주기(개월)
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("calibration_period")}>
|
||||
교정주기(개월){renderEqSortIcon("calibration_period")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비상태
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_status")}>
|
||||
장비상태{renderEqSortIcon("equipment_status")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
담당자
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manager_id")}>
|
||||
담당자{renderEqSortIcon("manager_id")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -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<Record<string, any>>({});
|
||||
const [eqSaving, setEqSaving] = useState(false);
|
||||
const [eqKeyword, setEqKeyword] = useState("");
|
||||
const [eqSortKey, setEqSortKey] = useState<string | null>(null);
|
||||
const [eqSortDir, setEqSortDir] = useState<"asc" | "desc">("asc");
|
||||
|
||||
/* ───── 채번 ───── */
|
||||
const [numberingRuleId, setNumberingRuleId] = useState<string | null>(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 <ArrowUpDown className="ml-1 inline h-3 w-3 opacity-40" />;
|
||||
return eqSortDir === "asc" ? (
|
||||
<ArrowUp className="ml-1 inline h-3 w-3" />
|
||||
) : (
|
||||
<ArrowDown className="ml-1 inline h-3 w-3" />
|
||||
);
|
||||
};
|
||||
|
||||
/* ═══════════════════ 검사기준 CRUD ═══════════════════ */
|
||||
const openInspCreate = async () => {
|
||||
@@ -1586,35 +1632,35 @@ export default function InspectionManagementPage() {
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase w-[60px] text-center">
|
||||
이미지
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비코드
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_code")}>
|
||||
장비코드{renderEqSortIcon("equipment_code")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_name")}>
|
||||
장비명{renderEqSortIcon("equipment_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비유형
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_type")}>
|
||||
장비유형{renderEqSortIcon("equipment_type")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
모델명
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("model_name")}>
|
||||
모델명{renderEqSortIcon("model_name")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
제조사
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manufacturer")}>
|
||||
제조사{renderEqSortIcon("manufacturer")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
설치장소
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("installation_location")}>
|
||||
설치장소{renderEqSortIcon("installation_location")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
최근교정일
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("last_calibration_date")}>
|
||||
최근교정일{renderEqSortIcon("last_calibration_date")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
교정주기(개월)
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("calibration_period")}>
|
||||
교정주기(개월){renderEqSortIcon("calibration_period")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
장비상태
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("equipment_status")}>
|
||||
장비상태{renderEqSortIcon("equipment_status")}
|
||||
</TableHead>
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase">
|
||||
담당자
|
||||
<TableHead className="text-muted-foreground text-[11px] font-bold tracking-wide uppercase cursor-pointer select-none hover:text-foreground" onClick={() => handleEqSort("manager_id")}>
|
||||
담당자{renderEqSortIcon("manager_id")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
Reference in New Issue
Block a user