From 0d4fcfb8716da288bf25b7ded4f1ebeaf30600e7 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 30 Apr 2026 10:52:34 +0900 Subject: [PATCH] Remove 'source_type' column from GRID_COLUMNS in receiving pages for multiple companies to streamline data display. --- .../COMPANY_10/logistics/receiving/page.tsx | 1 - .../COMPANY_10/monitoring/quality/page.tsx | 119 +++++++++++++----- .../COMPANY_16/logistics/receiving/page.tsx | 1 - .../COMPANY_16/monitoring/quality/page.tsx | 119 +++++++++++++----- .../COMPANY_29/logistics/receiving/page.tsx | 1 - .../COMPANY_29/monitoring/quality/page.tsx | 119 +++++++++++++----- .../COMPANY_30/logistics/receiving/page.tsx | 1 - .../COMPANY_7/logistics/receiving/page.tsx | 1 - .../COMPANY_7/monitoring/quality/page.tsx | 119 +++++++++++++----- .../COMPANY_8/logistics/receiving/page.tsx | 1 - .../COMPANY_8/monitoring/quality/page.tsx | 119 +++++++++++++----- .../COMPANY_9/logistics/receiving/page.tsx | 1 - .../COMPANY_9/monitoring/quality/page.tsx | 119 +++++++++++++----- 13 files changed, 546 insertions(+), 175 deletions(-) diff --git a/frontend/app/(main)/COMPANY_10/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_10/logistics/receiving/page.tsx index b46930e3..ca4d442b 100644 --- a/frontend/app/(main)/COMPANY_10/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_10/logistics/receiving/page.tsx @@ -83,7 +83,6 @@ const GRID_COLUMNS = [ { key: "inbound_type", label: "입고유형" }, { key: "inbound_date", label: "입고일" }, { key: "reference_number", label: "참조번호" }, - { key: "source_type", label: "데이터출처" }, { key: "supplier_name", label: "공급처" }, { key: "item_number", label: "품목코드" }, { key: "item_name", label: "품목명" }, diff --git a/frontend/app/(main)/COMPANY_10/monitoring/quality/page.tsx b/frontend/app/(main)/COMPANY_10/monitoring/quality/page.tsx index da4eabab..f0e84054 100644 --- a/frontend/app/(main)/COMPANY_10/monitoring/quality/page.tsx +++ b/frontend/app/(main)/COMPANY_10/monitoring/quality/page.tsx @@ -88,25 +88,65 @@ export default function QualityMonitoringPage() { const [activeTab, setActiveTab] = useState("all"); const intervalRef = useRef | null>(null); + // 서버 페이징 + KPI summary + const [page, setPage] = useState(1); + const [pageSize] = useState(50); + const [total, setTotal] = useState(0); + const totalPages = Math.max(1, Math.ceil(total / pageSize)); + const [serverSummary, setServerSummary] = useState<{ total: number; passed: number; failed: number; pending: number; passRate: number }>({ total: 0, passed: 0, failed: 0, pending: 0, passRate: 0 }); + + // 기간 필터 (기본: 오늘) + const todayStr = new Date().toISOString().slice(0, 10); + const [dateFrom, setDateFrom] = useState(todayStr); + const [dateTo, setDateTo] = useState(todayStr); + const setRangeToday = () => { const t = new Date().toISOString().slice(0, 10); setDateFrom(t); setDateTo(t); }; + const setRangeThisWeek = () => { + const now = new Date(); + const day = now.getDay() || 7; + const mon = new Date(now); mon.setDate(now.getDate() - (day - 1)); + const sun = new Date(mon); sun.setDate(mon.getDate() + 6); + setDateFrom(mon.toISOString().slice(0, 10)); + setDateTo(sun.toISOString().slice(0, 10)); + }; + const setRangeThisMonth = () => { + const now = new Date(); + const first = new Date(now.getFullYear(), now.getMonth(), 1); + const last = new Date(now.getFullYear(), now.getMonth() + 1, 0); + setDateFrom(first.toISOString().slice(0, 10)); + setDateTo(last.toISOString().slice(0, 10)); + }; + /* ───── 시계 ───── */ useEffect(() => { const timer = setInterval(() => setCurrentTime(new Date()), 1000); return () => clearInterval(timer); }, []); - /* ───── 데이터 조회 ───── */ + /* ───── 데이터 조회 (서버 페이징 + summary) ───── */ const fetchData = useCallback(async () => { setLoading(true); try { - const res = await apiClient.post("/table-management/tables/work_order_process/data", { autoFilter: true }); - const rows: ProcessRow[] = res.data?.data?.rows ?? res.data?.rows ?? []; - setProcessData(rows); + const qp = new URLSearchParams({ + page: String(page), + size: String(pageSize), + from: dateFrom, + to: dateTo, + }); + const res = await apiClient.get(`/quality-monitoring/data?${qp.toString()}`); + if (res.data?.success) { + setProcessData((res.data.rows || []) as ProcessRow[]); + setTotal(res.data.total || 0); + setServerSummary(res.data.summary || { total: 0, passed: 0, failed: 0, pending: 0, passRate: 0 }); + } } catch (err) { console.error("품질점검현황 데이터 조회 실패:", err); } finally { setLoading(false); } - }, []); + }, [dateFrom, dateTo, page, pageSize]); + + // 기간 변경 시 1페이지로 리셋 + useEffect(() => { setPage(1); }, [dateFrom, dateTo]); useEffect(() => { fetchData(); @@ -123,15 +163,9 @@ export default function QualityMonitoringPage() { }, [autoRefresh, fetchData, settings.refreshInterval]); /* ───── 검사 행 변환 ───── */ + // 기간 필터는 fetchData에서 이미 적용 — 여기서는 추가 필터 없이 모두 변환 const inspectionRows: InspectionRow[] = useMemo(() => { - const today = new Date().toISOString().slice(0, 10); - return processData - .filter((r) => { - // 금일 데이터만 - const dt = r.completed_at || r.started_at || ""; - return dt.slice(0, 10) === today; - }) .map((r, idx) => { const inspQty = r.input_qty || r.plan_qty || 0; const goodQty = r.good_qty ?? 0; @@ -164,20 +198,19 @@ export default function QualityMonitoringPage() { return []; }, [activeTab, inspectionRows]); - /* ───── 요약 통계 ───── */ - const summary = useMemo(() => { - const total = inspectionRows.length; - const passed = inspectionRows.filter((r) => r.result === "합격").length; - const failed = inspectionRows.filter((r) => r.result === "불합격").length; - const pending = inspectionRows.filter((r) => r.result === "대기").length; - const passRate = total > 0 ? (passed / total) * 100 : 0; - return { total, passed, failed, pending, passRate }; - }, [inspectionRows]); + /* ───── 요약 통계 (서버 합산 사용) ───── */ + const summary = useMemo(() => ({ + total: serverSummary.total, + passed: serverSummary.passed, + failed: serverSummary.failed, + pending: serverSummary.pending, + passRate: serverSummary.passRate, + }), [serverSummary]); /* ───── 요약 카드 정의 ───── */ const summaryCards = [ { - label: "금일 검사건수", + label: "검사건수", value: fmt(summary.total), sub: "건", color: "from-slate-500 to-slate-600", @@ -265,9 +298,22 @@ export default function QualityMonitoringPage() { {/* ── 본문 ── */} -
+
+ {/* 기간 필터 */} +
+ 조회 기간 + setDateFrom(e.target.value)} + className={cn("h-8 rounded border px-2 text-sm", theme.cardBorder, theme.card, theme.text)} /> + ~ + setDateTo(e.target.value)} + className={cn("h-8 rounded border px-2 text-sm", theme.cardBorder, theme.card, theme.text)} /> + + + +
+ {/* 요약 카드 */} -
+
{summaryCards.map((card) => (

{card.label}

@@ -280,7 +326,7 @@ export default function QualityMonitoringPage() {
{/* 검사유형 탭 */} -
+
{TABS.map((tab) => ( + + {page} / {totalPages} + + +
+
+ )}
diff --git a/frontend/app/(main)/COMPANY_16/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_16/logistics/receiving/page.tsx index b46930e3..ca4d442b 100644 --- a/frontend/app/(main)/COMPANY_16/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_16/logistics/receiving/page.tsx @@ -83,7 +83,6 @@ const GRID_COLUMNS = [ { key: "inbound_type", label: "입고유형" }, { key: "inbound_date", label: "입고일" }, { key: "reference_number", label: "참조번호" }, - { key: "source_type", label: "데이터출처" }, { key: "supplier_name", label: "공급처" }, { key: "item_number", label: "품목코드" }, { key: "item_name", label: "품목명" }, diff --git a/frontend/app/(main)/COMPANY_16/monitoring/quality/page.tsx b/frontend/app/(main)/COMPANY_16/monitoring/quality/page.tsx index da4eabab..f0e84054 100644 --- a/frontend/app/(main)/COMPANY_16/monitoring/quality/page.tsx +++ b/frontend/app/(main)/COMPANY_16/monitoring/quality/page.tsx @@ -88,25 +88,65 @@ export default function QualityMonitoringPage() { const [activeTab, setActiveTab] = useState("all"); const intervalRef = useRef | null>(null); + // 서버 페이징 + KPI summary + const [page, setPage] = useState(1); + const [pageSize] = useState(50); + const [total, setTotal] = useState(0); + const totalPages = Math.max(1, Math.ceil(total / pageSize)); + const [serverSummary, setServerSummary] = useState<{ total: number; passed: number; failed: number; pending: number; passRate: number }>({ total: 0, passed: 0, failed: 0, pending: 0, passRate: 0 }); + + // 기간 필터 (기본: 오늘) + const todayStr = new Date().toISOString().slice(0, 10); + const [dateFrom, setDateFrom] = useState(todayStr); + const [dateTo, setDateTo] = useState(todayStr); + const setRangeToday = () => { const t = new Date().toISOString().slice(0, 10); setDateFrom(t); setDateTo(t); }; + const setRangeThisWeek = () => { + const now = new Date(); + const day = now.getDay() || 7; + const mon = new Date(now); mon.setDate(now.getDate() - (day - 1)); + const sun = new Date(mon); sun.setDate(mon.getDate() + 6); + setDateFrom(mon.toISOString().slice(0, 10)); + setDateTo(sun.toISOString().slice(0, 10)); + }; + const setRangeThisMonth = () => { + const now = new Date(); + const first = new Date(now.getFullYear(), now.getMonth(), 1); + const last = new Date(now.getFullYear(), now.getMonth() + 1, 0); + setDateFrom(first.toISOString().slice(0, 10)); + setDateTo(last.toISOString().slice(0, 10)); + }; + /* ───── 시계 ───── */ useEffect(() => { const timer = setInterval(() => setCurrentTime(new Date()), 1000); return () => clearInterval(timer); }, []); - /* ───── 데이터 조회 ───── */ + /* ───── 데이터 조회 (서버 페이징 + summary) ───── */ const fetchData = useCallback(async () => { setLoading(true); try { - const res = await apiClient.post("/table-management/tables/work_order_process/data", { autoFilter: true }); - const rows: ProcessRow[] = res.data?.data?.rows ?? res.data?.rows ?? []; - setProcessData(rows); + const qp = new URLSearchParams({ + page: String(page), + size: String(pageSize), + from: dateFrom, + to: dateTo, + }); + const res = await apiClient.get(`/quality-monitoring/data?${qp.toString()}`); + if (res.data?.success) { + setProcessData((res.data.rows || []) as ProcessRow[]); + setTotal(res.data.total || 0); + setServerSummary(res.data.summary || { total: 0, passed: 0, failed: 0, pending: 0, passRate: 0 }); + } } catch (err) { console.error("품질점검현황 데이터 조회 실패:", err); } finally { setLoading(false); } - }, []); + }, [dateFrom, dateTo, page, pageSize]); + + // 기간 변경 시 1페이지로 리셋 + useEffect(() => { setPage(1); }, [dateFrom, dateTo]); useEffect(() => { fetchData(); @@ -123,15 +163,9 @@ export default function QualityMonitoringPage() { }, [autoRefresh, fetchData, settings.refreshInterval]); /* ───── 검사 행 변환 ───── */ + // 기간 필터는 fetchData에서 이미 적용 — 여기서는 추가 필터 없이 모두 변환 const inspectionRows: InspectionRow[] = useMemo(() => { - const today = new Date().toISOString().slice(0, 10); - return processData - .filter((r) => { - // 금일 데이터만 - const dt = r.completed_at || r.started_at || ""; - return dt.slice(0, 10) === today; - }) .map((r, idx) => { const inspQty = r.input_qty || r.plan_qty || 0; const goodQty = r.good_qty ?? 0; @@ -164,20 +198,19 @@ export default function QualityMonitoringPage() { return []; }, [activeTab, inspectionRows]); - /* ───── 요약 통계 ───── */ - const summary = useMemo(() => { - const total = inspectionRows.length; - const passed = inspectionRows.filter((r) => r.result === "합격").length; - const failed = inspectionRows.filter((r) => r.result === "불합격").length; - const pending = inspectionRows.filter((r) => r.result === "대기").length; - const passRate = total > 0 ? (passed / total) * 100 : 0; - return { total, passed, failed, pending, passRate }; - }, [inspectionRows]); + /* ───── 요약 통계 (서버 합산 사용) ───── */ + const summary = useMemo(() => ({ + total: serverSummary.total, + passed: serverSummary.passed, + failed: serverSummary.failed, + pending: serverSummary.pending, + passRate: serverSummary.passRate, + }), [serverSummary]); /* ───── 요약 카드 정의 ───── */ const summaryCards = [ { - label: "금일 검사건수", + label: "검사건수", value: fmt(summary.total), sub: "건", color: "from-slate-500 to-slate-600", @@ -265,9 +298,22 @@ export default function QualityMonitoringPage() {
{/* ── 본문 ── */} -
+
+ {/* 기간 필터 */} +
+ 조회 기간 + setDateFrom(e.target.value)} + className={cn("h-8 rounded border px-2 text-sm", theme.cardBorder, theme.card, theme.text)} /> + ~ + setDateTo(e.target.value)} + className={cn("h-8 rounded border px-2 text-sm", theme.cardBorder, theme.card, theme.text)} /> + + + +
+ {/* 요약 카드 */} -
+
{summaryCards.map((card) => (

{card.label}

@@ -280,7 +326,7 @@ export default function QualityMonitoringPage() {
{/* 검사유형 탭 */} -
+
{TABS.map((tab) => ( + + {page} / {totalPages} + + +
+
+ )}
diff --git a/frontend/app/(main)/COMPANY_29/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_29/logistics/receiving/page.tsx index b46930e3..ca4d442b 100644 --- a/frontend/app/(main)/COMPANY_29/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_29/logistics/receiving/page.tsx @@ -83,7 +83,6 @@ const GRID_COLUMNS = [ { key: "inbound_type", label: "입고유형" }, { key: "inbound_date", label: "입고일" }, { key: "reference_number", label: "참조번호" }, - { key: "source_type", label: "데이터출처" }, { key: "supplier_name", label: "공급처" }, { key: "item_number", label: "품목코드" }, { key: "item_name", label: "품목명" }, diff --git a/frontend/app/(main)/COMPANY_29/monitoring/quality/page.tsx b/frontend/app/(main)/COMPANY_29/monitoring/quality/page.tsx index da4eabab..f0e84054 100644 --- a/frontend/app/(main)/COMPANY_29/monitoring/quality/page.tsx +++ b/frontend/app/(main)/COMPANY_29/monitoring/quality/page.tsx @@ -88,25 +88,65 @@ export default function QualityMonitoringPage() { const [activeTab, setActiveTab] = useState("all"); const intervalRef = useRef | null>(null); + // 서버 페이징 + KPI summary + const [page, setPage] = useState(1); + const [pageSize] = useState(50); + const [total, setTotal] = useState(0); + const totalPages = Math.max(1, Math.ceil(total / pageSize)); + const [serverSummary, setServerSummary] = useState<{ total: number; passed: number; failed: number; pending: number; passRate: number }>({ total: 0, passed: 0, failed: 0, pending: 0, passRate: 0 }); + + // 기간 필터 (기본: 오늘) + const todayStr = new Date().toISOString().slice(0, 10); + const [dateFrom, setDateFrom] = useState(todayStr); + const [dateTo, setDateTo] = useState(todayStr); + const setRangeToday = () => { const t = new Date().toISOString().slice(0, 10); setDateFrom(t); setDateTo(t); }; + const setRangeThisWeek = () => { + const now = new Date(); + const day = now.getDay() || 7; + const mon = new Date(now); mon.setDate(now.getDate() - (day - 1)); + const sun = new Date(mon); sun.setDate(mon.getDate() + 6); + setDateFrom(mon.toISOString().slice(0, 10)); + setDateTo(sun.toISOString().slice(0, 10)); + }; + const setRangeThisMonth = () => { + const now = new Date(); + const first = new Date(now.getFullYear(), now.getMonth(), 1); + const last = new Date(now.getFullYear(), now.getMonth() + 1, 0); + setDateFrom(first.toISOString().slice(0, 10)); + setDateTo(last.toISOString().slice(0, 10)); + }; + /* ───── 시계 ───── */ useEffect(() => { const timer = setInterval(() => setCurrentTime(new Date()), 1000); return () => clearInterval(timer); }, []); - /* ───── 데이터 조회 ───── */ + /* ───── 데이터 조회 (서버 페이징 + summary) ───── */ const fetchData = useCallback(async () => { setLoading(true); try { - const res = await apiClient.post("/table-management/tables/work_order_process/data", { autoFilter: true }); - const rows: ProcessRow[] = res.data?.data?.rows ?? res.data?.rows ?? []; - setProcessData(rows); + const qp = new URLSearchParams({ + page: String(page), + size: String(pageSize), + from: dateFrom, + to: dateTo, + }); + const res = await apiClient.get(`/quality-monitoring/data?${qp.toString()}`); + if (res.data?.success) { + setProcessData((res.data.rows || []) as ProcessRow[]); + setTotal(res.data.total || 0); + setServerSummary(res.data.summary || { total: 0, passed: 0, failed: 0, pending: 0, passRate: 0 }); + } } catch (err) { console.error("품질점검현황 데이터 조회 실패:", err); } finally { setLoading(false); } - }, []); + }, [dateFrom, dateTo, page, pageSize]); + + // 기간 변경 시 1페이지로 리셋 + useEffect(() => { setPage(1); }, [dateFrom, dateTo]); useEffect(() => { fetchData(); @@ -123,15 +163,9 @@ export default function QualityMonitoringPage() { }, [autoRefresh, fetchData, settings.refreshInterval]); /* ───── 검사 행 변환 ───── */ + // 기간 필터는 fetchData에서 이미 적용 — 여기서는 추가 필터 없이 모두 변환 const inspectionRows: InspectionRow[] = useMemo(() => { - const today = new Date().toISOString().slice(0, 10); - return processData - .filter((r) => { - // 금일 데이터만 - const dt = r.completed_at || r.started_at || ""; - return dt.slice(0, 10) === today; - }) .map((r, idx) => { const inspQty = r.input_qty || r.plan_qty || 0; const goodQty = r.good_qty ?? 0; @@ -164,20 +198,19 @@ export default function QualityMonitoringPage() { return []; }, [activeTab, inspectionRows]); - /* ───── 요약 통계 ───── */ - const summary = useMemo(() => { - const total = inspectionRows.length; - const passed = inspectionRows.filter((r) => r.result === "합격").length; - const failed = inspectionRows.filter((r) => r.result === "불합격").length; - const pending = inspectionRows.filter((r) => r.result === "대기").length; - const passRate = total > 0 ? (passed / total) * 100 : 0; - return { total, passed, failed, pending, passRate }; - }, [inspectionRows]); + /* ───── 요약 통계 (서버 합산 사용) ───── */ + const summary = useMemo(() => ({ + total: serverSummary.total, + passed: serverSummary.passed, + failed: serverSummary.failed, + pending: serverSummary.pending, + passRate: serverSummary.passRate, + }), [serverSummary]); /* ───── 요약 카드 정의 ───── */ const summaryCards = [ { - label: "금일 검사건수", + label: "검사건수", value: fmt(summary.total), sub: "건", color: "from-slate-500 to-slate-600", @@ -265,9 +298,22 @@ export default function QualityMonitoringPage() {
{/* ── 본문 ── */} -
+
+ {/* 기간 필터 */} +
+ 조회 기간 + setDateFrom(e.target.value)} + className={cn("h-8 rounded border px-2 text-sm", theme.cardBorder, theme.card, theme.text)} /> + ~ + setDateTo(e.target.value)} + className={cn("h-8 rounded border px-2 text-sm", theme.cardBorder, theme.card, theme.text)} /> + + + +
+ {/* 요약 카드 */} -
+
{summaryCards.map((card) => (

{card.label}

@@ -280,7 +326,7 @@ export default function QualityMonitoringPage() {
{/* 검사유형 탭 */} -
+
{TABS.map((tab) => ( + + {page} / {totalPages} + + +
+
+ )}
diff --git a/frontend/app/(main)/COMPANY_30/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_30/logistics/receiving/page.tsx index 9ca6a1fe..9246e4f8 100644 --- a/frontend/app/(main)/COMPANY_30/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_30/logistics/receiving/page.tsx @@ -113,7 +113,6 @@ const GRID_COLUMNS = [ { key: "inbound_type", label: "입고유형" }, { key: "inbound_date", label: "입고일" }, { key: "reference_number", label: "참조번호" }, - { key: "source_type", label: "데이터출처" }, { key: "supplier_name", label: "공급처" }, { key: "item_number", label: "품목코드" }, { key: "item_name", label: "품목명" }, diff --git a/frontend/app/(main)/COMPANY_7/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_7/logistics/receiving/page.tsx index b46930e3..ca4d442b 100644 --- a/frontend/app/(main)/COMPANY_7/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_7/logistics/receiving/page.tsx @@ -83,7 +83,6 @@ const GRID_COLUMNS = [ { key: "inbound_type", label: "입고유형" }, { key: "inbound_date", label: "입고일" }, { key: "reference_number", label: "참조번호" }, - { key: "source_type", label: "데이터출처" }, { key: "supplier_name", label: "공급처" }, { key: "item_number", label: "품목코드" }, { key: "item_name", label: "품목명" }, diff --git a/frontend/app/(main)/COMPANY_7/monitoring/quality/page.tsx b/frontend/app/(main)/COMPANY_7/monitoring/quality/page.tsx index da4eabab..f0e84054 100644 --- a/frontend/app/(main)/COMPANY_7/monitoring/quality/page.tsx +++ b/frontend/app/(main)/COMPANY_7/monitoring/quality/page.tsx @@ -88,25 +88,65 @@ export default function QualityMonitoringPage() { const [activeTab, setActiveTab] = useState("all"); const intervalRef = useRef | null>(null); + // 서버 페이징 + KPI summary + const [page, setPage] = useState(1); + const [pageSize] = useState(50); + const [total, setTotal] = useState(0); + const totalPages = Math.max(1, Math.ceil(total / pageSize)); + const [serverSummary, setServerSummary] = useState<{ total: number; passed: number; failed: number; pending: number; passRate: number }>({ total: 0, passed: 0, failed: 0, pending: 0, passRate: 0 }); + + // 기간 필터 (기본: 오늘) + const todayStr = new Date().toISOString().slice(0, 10); + const [dateFrom, setDateFrom] = useState(todayStr); + const [dateTo, setDateTo] = useState(todayStr); + const setRangeToday = () => { const t = new Date().toISOString().slice(0, 10); setDateFrom(t); setDateTo(t); }; + const setRangeThisWeek = () => { + const now = new Date(); + const day = now.getDay() || 7; + const mon = new Date(now); mon.setDate(now.getDate() - (day - 1)); + const sun = new Date(mon); sun.setDate(mon.getDate() + 6); + setDateFrom(mon.toISOString().slice(0, 10)); + setDateTo(sun.toISOString().slice(0, 10)); + }; + const setRangeThisMonth = () => { + const now = new Date(); + const first = new Date(now.getFullYear(), now.getMonth(), 1); + const last = new Date(now.getFullYear(), now.getMonth() + 1, 0); + setDateFrom(first.toISOString().slice(0, 10)); + setDateTo(last.toISOString().slice(0, 10)); + }; + /* ───── 시계 ───── */ useEffect(() => { const timer = setInterval(() => setCurrentTime(new Date()), 1000); return () => clearInterval(timer); }, []); - /* ───── 데이터 조회 ───── */ + /* ───── 데이터 조회 (서버 페이징 + summary) ───── */ const fetchData = useCallback(async () => { setLoading(true); try { - const res = await apiClient.post("/table-management/tables/work_order_process/data", { autoFilter: true }); - const rows: ProcessRow[] = res.data?.data?.rows ?? res.data?.rows ?? []; - setProcessData(rows); + const qp = new URLSearchParams({ + page: String(page), + size: String(pageSize), + from: dateFrom, + to: dateTo, + }); + const res = await apiClient.get(`/quality-monitoring/data?${qp.toString()}`); + if (res.data?.success) { + setProcessData((res.data.rows || []) as ProcessRow[]); + setTotal(res.data.total || 0); + setServerSummary(res.data.summary || { total: 0, passed: 0, failed: 0, pending: 0, passRate: 0 }); + } } catch (err) { console.error("품질점검현황 데이터 조회 실패:", err); } finally { setLoading(false); } - }, []); + }, [dateFrom, dateTo, page, pageSize]); + + // 기간 변경 시 1페이지로 리셋 + useEffect(() => { setPage(1); }, [dateFrom, dateTo]); useEffect(() => { fetchData(); @@ -123,15 +163,9 @@ export default function QualityMonitoringPage() { }, [autoRefresh, fetchData, settings.refreshInterval]); /* ───── 검사 행 변환 ───── */ + // 기간 필터는 fetchData에서 이미 적용 — 여기서는 추가 필터 없이 모두 변환 const inspectionRows: InspectionRow[] = useMemo(() => { - const today = new Date().toISOString().slice(0, 10); - return processData - .filter((r) => { - // 금일 데이터만 - const dt = r.completed_at || r.started_at || ""; - return dt.slice(0, 10) === today; - }) .map((r, idx) => { const inspQty = r.input_qty || r.plan_qty || 0; const goodQty = r.good_qty ?? 0; @@ -164,20 +198,19 @@ export default function QualityMonitoringPage() { return []; }, [activeTab, inspectionRows]); - /* ───── 요약 통계 ───── */ - const summary = useMemo(() => { - const total = inspectionRows.length; - const passed = inspectionRows.filter((r) => r.result === "합격").length; - const failed = inspectionRows.filter((r) => r.result === "불합격").length; - const pending = inspectionRows.filter((r) => r.result === "대기").length; - const passRate = total > 0 ? (passed / total) * 100 : 0; - return { total, passed, failed, pending, passRate }; - }, [inspectionRows]); + /* ───── 요약 통계 (서버 합산 사용) ───── */ + const summary = useMemo(() => ({ + total: serverSummary.total, + passed: serverSummary.passed, + failed: serverSummary.failed, + pending: serverSummary.pending, + passRate: serverSummary.passRate, + }), [serverSummary]); /* ───── 요약 카드 정의 ───── */ const summaryCards = [ { - label: "금일 검사건수", + label: "검사건수", value: fmt(summary.total), sub: "건", color: "from-slate-500 to-slate-600", @@ -265,9 +298,22 @@ export default function QualityMonitoringPage() {
{/* ── 본문 ── */} -
+
+ {/* 기간 필터 */} +
+ 조회 기간 + setDateFrom(e.target.value)} + className={cn("h-8 rounded border px-2 text-sm", theme.cardBorder, theme.card, theme.text)} /> + ~ + setDateTo(e.target.value)} + className={cn("h-8 rounded border px-2 text-sm", theme.cardBorder, theme.card, theme.text)} /> + + + +
+ {/* 요약 카드 */} -
+
{summaryCards.map((card) => (

{card.label}

@@ -280,7 +326,7 @@ export default function QualityMonitoringPage() {
{/* 검사유형 탭 */} -
+
{TABS.map((tab) => ( + + {page} / {totalPages} + + +
+
+ )}
diff --git a/frontend/app/(main)/COMPANY_8/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_8/logistics/receiving/page.tsx index a87e5136..2a73a04c 100644 --- a/frontend/app/(main)/COMPANY_8/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_8/logistics/receiving/page.tsx @@ -82,7 +82,6 @@ const GRID_COLUMNS = [ { key: "inbound_type", label: "입고유형" }, { key: "inbound_date", label: "입고일" }, { key: "reference_number", label: "참조번호" }, - { key: "source_type", label: "데이터출처" }, { key: "supplier_name", label: "공급처" }, { key: "item_number", label: "품목코드" }, { key: "item_name", label: "품목명" }, diff --git a/frontend/app/(main)/COMPANY_8/monitoring/quality/page.tsx b/frontend/app/(main)/COMPANY_8/monitoring/quality/page.tsx index da4eabab..f0e84054 100644 --- a/frontend/app/(main)/COMPANY_8/monitoring/quality/page.tsx +++ b/frontend/app/(main)/COMPANY_8/monitoring/quality/page.tsx @@ -88,25 +88,65 @@ export default function QualityMonitoringPage() { const [activeTab, setActiveTab] = useState("all"); const intervalRef = useRef | null>(null); + // 서버 페이징 + KPI summary + const [page, setPage] = useState(1); + const [pageSize] = useState(50); + const [total, setTotal] = useState(0); + const totalPages = Math.max(1, Math.ceil(total / pageSize)); + const [serverSummary, setServerSummary] = useState<{ total: number; passed: number; failed: number; pending: number; passRate: number }>({ total: 0, passed: 0, failed: 0, pending: 0, passRate: 0 }); + + // 기간 필터 (기본: 오늘) + const todayStr = new Date().toISOString().slice(0, 10); + const [dateFrom, setDateFrom] = useState(todayStr); + const [dateTo, setDateTo] = useState(todayStr); + const setRangeToday = () => { const t = new Date().toISOString().slice(0, 10); setDateFrom(t); setDateTo(t); }; + const setRangeThisWeek = () => { + const now = new Date(); + const day = now.getDay() || 7; + const mon = new Date(now); mon.setDate(now.getDate() - (day - 1)); + const sun = new Date(mon); sun.setDate(mon.getDate() + 6); + setDateFrom(mon.toISOString().slice(0, 10)); + setDateTo(sun.toISOString().slice(0, 10)); + }; + const setRangeThisMonth = () => { + const now = new Date(); + const first = new Date(now.getFullYear(), now.getMonth(), 1); + const last = new Date(now.getFullYear(), now.getMonth() + 1, 0); + setDateFrom(first.toISOString().slice(0, 10)); + setDateTo(last.toISOString().slice(0, 10)); + }; + /* ───── 시계 ───── */ useEffect(() => { const timer = setInterval(() => setCurrentTime(new Date()), 1000); return () => clearInterval(timer); }, []); - /* ───── 데이터 조회 ───── */ + /* ───── 데이터 조회 (서버 페이징 + summary) ───── */ const fetchData = useCallback(async () => { setLoading(true); try { - const res = await apiClient.post("/table-management/tables/work_order_process/data", { autoFilter: true }); - const rows: ProcessRow[] = res.data?.data?.rows ?? res.data?.rows ?? []; - setProcessData(rows); + const qp = new URLSearchParams({ + page: String(page), + size: String(pageSize), + from: dateFrom, + to: dateTo, + }); + const res = await apiClient.get(`/quality-monitoring/data?${qp.toString()}`); + if (res.data?.success) { + setProcessData((res.data.rows || []) as ProcessRow[]); + setTotal(res.data.total || 0); + setServerSummary(res.data.summary || { total: 0, passed: 0, failed: 0, pending: 0, passRate: 0 }); + } } catch (err) { console.error("품질점검현황 데이터 조회 실패:", err); } finally { setLoading(false); } - }, []); + }, [dateFrom, dateTo, page, pageSize]); + + // 기간 변경 시 1페이지로 리셋 + useEffect(() => { setPage(1); }, [dateFrom, dateTo]); useEffect(() => { fetchData(); @@ -123,15 +163,9 @@ export default function QualityMonitoringPage() { }, [autoRefresh, fetchData, settings.refreshInterval]); /* ───── 검사 행 변환 ───── */ + // 기간 필터는 fetchData에서 이미 적용 — 여기서는 추가 필터 없이 모두 변환 const inspectionRows: InspectionRow[] = useMemo(() => { - const today = new Date().toISOString().slice(0, 10); - return processData - .filter((r) => { - // 금일 데이터만 - const dt = r.completed_at || r.started_at || ""; - return dt.slice(0, 10) === today; - }) .map((r, idx) => { const inspQty = r.input_qty || r.plan_qty || 0; const goodQty = r.good_qty ?? 0; @@ -164,20 +198,19 @@ export default function QualityMonitoringPage() { return []; }, [activeTab, inspectionRows]); - /* ───── 요약 통계 ───── */ - const summary = useMemo(() => { - const total = inspectionRows.length; - const passed = inspectionRows.filter((r) => r.result === "합격").length; - const failed = inspectionRows.filter((r) => r.result === "불합격").length; - const pending = inspectionRows.filter((r) => r.result === "대기").length; - const passRate = total > 0 ? (passed / total) * 100 : 0; - return { total, passed, failed, pending, passRate }; - }, [inspectionRows]); + /* ───── 요약 통계 (서버 합산 사용) ───── */ + const summary = useMemo(() => ({ + total: serverSummary.total, + passed: serverSummary.passed, + failed: serverSummary.failed, + pending: serverSummary.pending, + passRate: serverSummary.passRate, + }), [serverSummary]); /* ───── 요약 카드 정의 ───── */ const summaryCards = [ { - label: "금일 검사건수", + label: "검사건수", value: fmt(summary.total), sub: "건", color: "from-slate-500 to-slate-600", @@ -265,9 +298,22 @@ export default function QualityMonitoringPage() {
{/* ── 본문 ── */} -
+
+ {/* 기간 필터 */} +
+ 조회 기간 + setDateFrom(e.target.value)} + className={cn("h-8 rounded border px-2 text-sm", theme.cardBorder, theme.card, theme.text)} /> + ~ + setDateTo(e.target.value)} + className={cn("h-8 rounded border px-2 text-sm", theme.cardBorder, theme.card, theme.text)} /> + + + +
+ {/* 요약 카드 */} -
+
{summaryCards.map((card) => (

{card.label}

@@ -280,7 +326,7 @@ export default function QualityMonitoringPage() {
{/* 검사유형 탭 */} -
+
{TABS.map((tab) => ( + + {page} / {totalPages} + + +
+
+ )}
diff --git a/frontend/app/(main)/COMPANY_9/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_9/logistics/receiving/page.tsx index b46930e3..ca4d442b 100644 --- a/frontend/app/(main)/COMPANY_9/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_9/logistics/receiving/page.tsx @@ -83,7 +83,6 @@ const GRID_COLUMNS = [ { key: "inbound_type", label: "입고유형" }, { key: "inbound_date", label: "입고일" }, { key: "reference_number", label: "참조번호" }, - { key: "source_type", label: "데이터출처" }, { key: "supplier_name", label: "공급처" }, { key: "item_number", label: "품목코드" }, { key: "item_name", label: "품목명" }, diff --git a/frontend/app/(main)/COMPANY_9/monitoring/quality/page.tsx b/frontend/app/(main)/COMPANY_9/monitoring/quality/page.tsx index da4eabab..f0e84054 100644 --- a/frontend/app/(main)/COMPANY_9/monitoring/quality/page.tsx +++ b/frontend/app/(main)/COMPANY_9/monitoring/quality/page.tsx @@ -88,25 +88,65 @@ export default function QualityMonitoringPage() { const [activeTab, setActiveTab] = useState("all"); const intervalRef = useRef | null>(null); + // 서버 페이징 + KPI summary + const [page, setPage] = useState(1); + const [pageSize] = useState(50); + const [total, setTotal] = useState(0); + const totalPages = Math.max(1, Math.ceil(total / pageSize)); + const [serverSummary, setServerSummary] = useState<{ total: number; passed: number; failed: number; pending: number; passRate: number }>({ total: 0, passed: 0, failed: 0, pending: 0, passRate: 0 }); + + // 기간 필터 (기본: 오늘) + const todayStr = new Date().toISOString().slice(0, 10); + const [dateFrom, setDateFrom] = useState(todayStr); + const [dateTo, setDateTo] = useState(todayStr); + const setRangeToday = () => { const t = new Date().toISOString().slice(0, 10); setDateFrom(t); setDateTo(t); }; + const setRangeThisWeek = () => { + const now = new Date(); + const day = now.getDay() || 7; + const mon = new Date(now); mon.setDate(now.getDate() - (day - 1)); + const sun = new Date(mon); sun.setDate(mon.getDate() + 6); + setDateFrom(mon.toISOString().slice(0, 10)); + setDateTo(sun.toISOString().slice(0, 10)); + }; + const setRangeThisMonth = () => { + const now = new Date(); + const first = new Date(now.getFullYear(), now.getMonth(), 1); + const last = new Date(now.getFullYear(), now.getMonth() + 1, 0); + setDateFrom(first.toISOString().slice(0, 10)); + setDateTo(last.toISOString().slice(0, 10)); + }; + /* ───── 시계 ───── */ useEffect(() => { const timer = setInterval(() => setCurrentTime(new Date()), 1000); return () => clearInterval(timer); }, []); - /* ───── 데이터 조회 ───── */ + /* ───── 데이터 조회 (서버 페이징 + summary) ───── */ const fetchData = useCallback(async () => { setLoading(true); try { - const res = await apiClient.post("/table-management/tables/work_order_process/data", { autoFilter: true }); - const rows: ProcessRow[] = res.data?.data?.rows ?? res.data?.rows ?? []; - setProcessData(rows); + const qp = new URLSearchParams({ + page: String(page), + size: String(pageSize), + from: dateFrom, + to: dateTo, + }); + const res = await apiClient.get(`/quality-monitoring/data?${qp.toString()}`); + if (res.data?.success) { + setProcessData((res.data.rows || []) as ProcessRow[]); + setTotal(res.data.total || 0); + setServerSummary(res.data.summary || { total: 0, passed: 0, failed: 0, pending: 0, passRate: 0 }); + } } catch (err) { console.error("품질점검현황 데이터 조회 실패:", err); } finally { setLoading(false); } - }, []); + }, [dateFrom, dateTo, page, pageSize]); + + // 기간 변경 시 1페이지로 리셋 + useEffect(() => { setPage(1); }, [dateFrom, dateTo]); useEffect(() => { fetchData(); @@ -123,15 +163,9 @@ export default function QualityMonitoringPage() { }, [autoRefresh, fetchData, settings.refreshInterval]); /* ───── 검사 행 변환 ───── */ + // 기간 필터는 fetchData에서 이미 적용 — 여기서는 추가 필터 없이 모두 변환 const inspectionRows: InspectionRow[] = useMemo(() => { - const today = new Date().toISOString().slice(0, 10); - return processData - .filter((r) => { - // 금일 데이터만 - const dt = r.completed_at || r.started_at || ""; - return dt.slice(0, 10) === today; - }) .map((r, idx) => { const inspQty = r.input_qty || r.plan_qty || 0; const goodQty = r.good_qty ?? 0; @@ -164,20 +198,19 @@ export default function QualityMonitoringPage() { return []; }, [activeTab, inspectionRows]); - /* ───── 요약 통계 ───── */ - const summary = useMemo(() => { - const total = inspectionRows.length; - const passed = inspectionRows.filter((r) => r.result === "합격").length; - const failed = inspectionRows.filter((r) => r.result === "불합격").length; - const pending = inspectionRows.filter((r) => r.result === "대기").length; - const passRate = total > 0 ? (passed / total) * 100 : 0; - return { total, passed, failed, pending, passRate }; - }, [inspectionRows]); + /* ───── 요약 통계 (서버 합산 사용) ───── */ + const summary = useMemo(() => ({ + total: serverSummary.total, + passed: serverSummary.passed, + failed: serverSummary.failed, + pending: serverSummary.pending, + passRate: serverSummary.passRate, + }), [serverSummary]); /* ───── 요약 카드 정의 ───── */ const summaryCards = [ { - label: "금일 검사건수", + label: "검사건수", value: fmt(summary.total), sub: "건", color: "from-slate-500 to-slate-600", @@ -265,9 +298,22 @@ export default function QualityMonitoringPage() {
{/* ── 본문 ── */} -
+
+ {/* 기간 필터 */} +
+ 조회 기간 + setDateFrom(e.target.value)} + className={cn("h-8 rounded border px-2 text-sm", theme.cardBorder, theme.card, theme.text)} /> + ~ + setDateTo(e.target.value)} + className={cn("h-8 rounded border px-2 text-sm", theme.cardBorder, theme.card, theme.text)} /> + + + +
+ {/* 요약 카드 */} -
+
{summaryCards.map((card) => (

{card.label}

@@ -280,7 +326,7 @@ export default function QualityMonitoringPage() {
{/* 검사유형 탭 */} -
+
{TABS.map((tab) => ( + + {page} / {totalPages} + + +
+
+ )}