import { useState, useEffect, useCallback } from 'react'; // ─── 타입 ────────────────────────────────────────────────── interface EtaClct { startDate: string; endDate: string; } interface ResultClct { resultDate: string; count: number; } interface HrCollectItem { id: string; rootId: string; ip: string; depth1: string; depth2: string; depth3: string; depth4: string | null; clctName: string; clctType: string; clctTypeName: string; trnsmtCycle: string | null; receiveCycle: string | null; targetTable: string; seq: number; estmtRqrd: string; activeYn: string; clctStartDt: string; clctEndDt: string; clctDate: string | null; jobName: string; resultClctList: ResultClct[]; etaClctList: EtaClct[]; } // ─── Mock 데이터 ──────────────────────────────────────────── // TODO: 실제 API 연동 시 fetch 호출로 교체 const MOCK_DATA: HrCollectItem[] = [ { id: '100200', rootId: '2', ip: '127.0.0.1', depth1: '연계', depth2: '해양경찰청', depth3: '해경업무포탈시스템(KBP)', depth4: null, clctName: '사용자부서', clctType: '000002', clctTypeName: '배치', trnsmtCycle: null, receiveCycle: '0 20 4 * * *', targetTable: 'common.t_dept_info', seq: 101, estmtRqrd: '1', activeYn: 'Y', clctStartDt: '2024-12-16', clctEndDt: '9999-12-31', clctDate: '2024-12-16', jobName: 'DeptJob', resultClctList: [], etaClctList: [{ startDate: '2025-09-12 04:20', endDate: '2025-09-12 04:21' }], }, { id: '100200', rootId: '2', ip: '127.0.0.1', depth1: '연계', depth2: '해양경찰청', depth3: '해경업무포탈시스템(KBP)', depth4: null, clctName: '사용자계정', clctType: '000002', clctTypeName: '배치', trnsmtCycle: null, receiveCycle: '0 20 4 * 1 *', targetTable: 'common.t_usr', seq: 102, estmtRqrd: '5', activeYn: 'Y', clctStartDt: '2024-12-17', clctEndDt: '9999-12-31', clctDate: null, jobName: 'UserFlowJob', resultClctList: [], etaClctList: [], }, { id: '100201', rootId: '2', ip: '127.0.0.1', depth1: '연계', depth2: '해양경찰청', depth3: '해경업무포탈시스템(KBP)', depth4: null, clctName: '사용자직위', clctType: '000002', clctTypeName: '배치', trnsmtCycle: null, receiveCycle: '0 30 4 * * *', targetTable: 'common.t_position_info', seq: 103, estmtRqrd: '1', activeYn: 'Y', clctStartDt: '2024-12-16', clctEndDt: '9999-12-31', clctDate: '2025-01-10', jobName: 'PositionJob', resultClctList: [{ resultDate: '2025-09-12 04:30', count: 42 }], etaClctList: [{ startDate: '2025-09-12 04:30', endDate: '2025-09-12 04:31' }], }, { id: '100202', rootId: '2', ip: '127.0.0.1', depth1: '연계', depth2: '해양경찰청', depth3: '해경업무포탈시스템(KBP)', depth4: null, clctName: '조직정보', clctType: '000002', clctTypeName: '배치', trnsmtCycle: null, receiveCycle: '0 40 4 * * *', targetTable: 'common.t_org_info', seq: 104, estmtRqrd: '2', activeYn: 'Y', clctStartDt: '2024-12-18', clctEndDt: '9999-12-31', clctDate: '2025-03-20', jobName: 'OrgJob', resultClctList: [{ resultDate: '2025-09-12 04:40', count: 15 }], etaClctList: [{ startDate: '2025-09-12 04:40', endDate: '2025-09-12 04:41' }], }, { id: '100203', rootId: '2', ip: '127.0.0.1', depth1: '연계', depth2: '해양경찰청', depth3: '해경업무포탈시스템(KBP)', depth4: null, clctName: '근무상태', clctType: '000002', clctTypeName: '배치', trnsmtCycle: null, receiveCycle: '0 0 5 * * *', targetTable: 'common.t_work_status', seq: 105, estmtRqrd: '3', activeYn: 'N', clctStartDt: '2025-01-15', clctEndDt: '9999-12-31', clctDate: null, jobName: 'WorkStatusJob', resultClctList: [], etaClctList: [], }, ]; function fetchHrCollectData(): Promise { return new Promise((resolve) => { setTimeout(() => resolve(MOCK_DATA), 300); }); } // ─── 상태 뱃지 ───────────────────────────────────────────── function getCollectStatus(item: HrCollectItem): { label: string; color: string } { if (item.activeYn !== 'Y') { return { label: '비활성', color: 'text-t3 bg-bg-elevated' }; } if (item.etaClctList.length > 0) { return { label: '완료', color: 'text-emerald-400 bg-emerald-500/10' }; } return { label: '대기', color: 'text-yellow-400 bg-yellow-500/10' }; } // ─── cron 표현식 → 읽기 쉬운 형태 ───────────────────────── function formatCron(cron: string | null): string { if (!cron) return '-'; const parts = cron.split(' '); if (parts.length < 6) return cron; const [sec, min, hour, , ,] = parts; return `매일 ${hour}:${min.padStart(2, '0')}:${sec.padStart(2, '0')}`; } // ─── 테이블 ───────────────────────────────────────────────── const HEADERS = [ '번호', '수집항목', '기관', '시스템', '유형', '수집주기', '대상테이블', 'Job명', '활성', '수집시작일', '최근수집일', '상태', ]; function HrTable({ rows, loading }: { rows: HrCollectItem[]; loading: boolean }) { return (
{HEADERS.map((h) => ( ))} {loading && rows.length === 0 ? Array.from({ length: 5 }).map((_, i) => ( {HEADERS.map((_, j) => ( ))} )) : rows.map((row, idx) => { const status = getCollectStatus(row); return ( ); })}
{h}
{idx + 1} {row.clctName} {row.depth2} {row.depth3} {row.clctTypeName} {formatCron(row.receiveCycle)} {row.targetTable} {row.jobName} {row.activeYn === 'Y' ? 'Y' : 'N'} {row.clctStartDt} {row.clctDate ?? '-'} {status.label}
); } // ─── 메인 패널 ────────────────────────────────────────────── export default function CollectHrPanel() { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(false); const [lastUpdate, setLastUpdate] = useState(null); const fetchData = useCallback(async () => { setLoading(true); const data = await fetchHrCollectData(); setRows(data); setLoading(false); setLastUpdate(new Date()); }, []); useEffect(() => { let isMounted = true; if (rows.length === 0) { void Promise.resolve().then(() => { if (isMounted) void fetchData(); }); } return () => { isMounted = false; }; }, [rows.length, fetchData]); const activeCount = rows.filter((r) => r.activeYn === 'Y').length; const completedCount = rows.filter((r) => r.etaClctList.length > 0).length; return (
{/* 헤더 */}

인사정보 수집 현황

{lastUpdate && ( 갱신:{' '} {lastUpdate.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit', })} )}
{/* 상태 표시줄 */}
수집 완료 {completedCount}건 전체 {rows.length}건 (활성: {activeCount} / 비활성: {rows.length - activeCount})
{/* 테이블 */}
); }