9f00988110
[신규 화면] - 설비허브 + 설비관리 + 설비점검 - 재고조정 + 재고이동 [버그 수정] - 창고 NULL status 누락 - 작업지시 sync detail fallback - InspectionModal API 경로 - 검사결과 DB 저장 - seq_no 비순차 대응 - 출고 재고 부족 검증 - 자동 창고 매칭 - 내 접수 목록 필터 [UI 개선] - 사이드바 카드형 - 자재투입 컴팩트 - 커스텀 모달 - 불필요 버튼 제거
170 lines
6.8 KiB
TypeScript
170 lines
6.8 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect, useCallback } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { apiClient } from "@/lib/api/client";
|
|
import { PopShell } from "../PopShell";
|
|
|
|
interface EquipmentItem {
|
|
id: string;
|
|
equipment_code: string;
|
|
equipment_name: string;
|
|
equipment_type?: string;
|
|
location?: string;
|
|
status?: string;
|
|
last_inspection_date?: string;
|
|
next_inspection_date?: string;
|
|
memo?: string;
|
|
}
|
|
|
|
export function EquipmentList() {
|
|
const router = useRouter();
|
|
const [items, setItems] = useState<EquipmentItem[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [searchKeyword, setSearchKeyword] = useState("");
|
|
|
|
const fetchEquipments = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const res = await apiClient.get("/data/equipment_mng", {
|
|
params: { pageSize: 500 },
|
|
});
|
|
const data = res.data?.data?.data ?? res.data?.data ?? [];
|
|
setItems(Array.isArray(data) ? data : []);
|
|
} catch {
|
|
setItems([]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchEquipments();
|
|
}, [fetchEquipments]);
|
|
|
|
const filtered = items.filter((item) => {
|
|
if (!searchKeyword) return true;
|
|
const kw = searchKeyword.toLowerCase();
|
|
return (
|
|
(item.equipment_code || "").toLowerCase().includes(kw) ||
|
|
(item.equipment_name || "").toLowerCase().includes(kw) ||
|
|
(item.equipment_type || "").toLowerCase().includes(kw)
|
|
);
|
|
});
|
|
|
|
// KPI
|
|
const totalCount = items.length;
|
|
const activeCount = items.filter((i) => i.status === "가동" || i.status === "정상" || !i.status).length;
|
|
const stopCount = items.filter((i) => i.status === "정지" || i.status === "비가동").length;
|
|
|
|
return (
|
|
<PopShell title="설비관리" showBanner={false}>
|
|
<div className="min-h-screen bg-gray-50">
|
|
{/* Header */}
|
|
<div className="bg-white border-b border-gray-200 px-5 pt-5 pb-4">
|
|
<div className="flex items-center gap-3 mb-4">
|
|
<button
|
|
onClick={() => router.back()}
|
|
className="w-9 h-9 rounded-xl bg-gray-100 flex items-center justify-center"
|
|
>
|
|
<svg className="w-5 h-5 text-gray-600" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
|
</svg>
|
|
</button>
|
|
<div>
|
|
<h1 className="text-xl font-bold text-gray-900">설비관리</h1>
|
|
<p className="text-sm text-gray-500">설비 현황을 조회합니다</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Search */}
|
|
<div className="relative">
|
|
<svg className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
|
|
</svg>
|
|
<input
|
|
type="text"
|
|
placeholder="설비명 또는 코드 검색..."
|
|
value={searchKeyword}
|
|
onChange={(e) => setSearchKeyword(e.target.value)}
|
|
className="w-full pl-10 pr-4 py-3 rounded-xl border border-gray-200 text-sm focus:outline-none focus:border-blue-400"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* KPI */}
|
|
<div className="px-5 py-3">
|
|
<div className="grid grid-cols-3 gap-3">
|
|
<div className="bg-white rounded-xl border border-gray-200 p-3 text-center">
|
|
<p className="text-xs text-gray-400">전체</p>
|
|
<p className="text-2xl font-bold text-gray-900">{totalCount}</p>
|
|
</div>
|
|
<div className="bg-white rounded-xl border border-green-200 p-3 text-center">
|
|
<p className="text-xs text-green-500">가동</p>
|
|
<p className="text-2xl font-bold text-green-600">{activeCount}</p>
|
|
</div>
|
|
<div className="bg-white rounded-xl border border-red-200 p-3 text-center">
|
|
<p className="text-xs text-red-500">비가동</p>
|
|
<p className="text-2xl font-bold text-red-600">{stopCount}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* List */}
|
|
<div className="px-5 pb-24">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<h2 className="text-sm font-semibold text-gray-500">설비 목록</h2>
|
|
<span className="text-xs text-gray-400">{filtered.length}건</span>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-20">
|
|
<div className="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
|
</div>
|
|
) : filtered.length === 0 ? (
|
|
<div className="flex flex-col items-center justify-center py-20 text-gray-400">
|
|
<svg className="w-16 h-16 mb-4 text-gray-200" fill="none" stroke="currentColor" strokeWidth={1} viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M11.42 15.17l-5.65-5.65a8 8 0 1111.31 0l-5.65 5.65z" />
|
|
</svg>
|
|
<p className="text-sm">등록된 설비가 없습니다</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{filtered.map((item) => (
|
|
<div
|
|
key={item.id}
|
|
className="bg-white rounded-2xl border border-gray-200 p-4 hover:shadow-md transition-shadow"
|
|
>
|
|
<div className="flex items-start justify-between mb-2">
|
|
<div>
|
|
<h3 className="font-bold text-gray-900">{item.equipment_name || "-"}</h3>
|
|
<p className="text-xs text-gray-400">{item.equipment_code}</p>
|
|
</div>
|
|
<span
|
|
className={`text-xs px-2.5 py-1 rounded-full font-semibold ${
|
|
item.status === "정지" || item.status === "비가동"
|
|
? "bg-red-100 text-red-600"
|
|
: "bg-green-100 text-green-600"
|
|
}`}
|
|
>
|
|
{item.status || "정상"}
|
|
</span>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-2 text-xs text-gray-500">
|
|
{item.equipment_type && (
|
|
<div>유형: <span className="text-gray-700 font-medium">{item.equipment_type}</span></div>
|
|
)}
|
|
{item.location && (
|
|
<div>위치: <span className="text-gray-700 font-medium">{item.location}</span></div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</PopShell>
|
|
);
|
|
}
|