"use client"; import React, { useState, useEffect } from "react"; import { DashboardElement } from "@/components/admin/dashboard/types"; import { getApiUrl } from "@/lib/utils/apiUrl"; interface StatusSummaryWidgetProps { element: DashboardElement; title?: string; icon?: string; bgGradient?: string; statusConfig?: StatusConfig; } interface StatusConfig { [key: string]: { label: string; color: "blue" | "green" | "red" | "yellow" | "orange" | "purple" | "gray"; }; } // 영어 상태명 → 한글 자동 변환 const statusTranslations: { [key: string]: string } = { // 배송 관련 delayed: "지연", pickup_waiting: "픽업 대기", in_transit: "배송 중", delivered: "배송완료", pending: "대기중", processing: "처리중", completed: "완료", cancelled: "취소됨", failed: "실패", // 일반 상태 active: "활성", inactive: "비활성", enabled: "사용중", disabled: "사용안함", online: "온라인", offline: "오프라인", available: "사용가능", unavailable: "사용불가", // 승인 관련 approved: "승인됨", rejected: "거절됨", waiting: "대기중", // 차량 관련 driving: "운행중", parked: "주차", maintenance: "정비중", // 기사 관련 (존중하는 표현) waiting: "대기중", resting: "휴식중", unavailable: "운행불가", // 기사 평가 excellent: "우수", good: "양호", average: "보통", poor: "미흡", // 기사 경력 veteran: "베테랑", experienced: "숙련", intermediate: "중급", beginner: "초급", }; // 영어 테이블명 → 한글 자동 변환 const tableTranslations: { [key: string]: string } = { // 배송/물류 관련 deliveries: "배송", delivery: "배송", shipments: "출하", shipment: "출하", orders: "주문", order: "주문", cargo: "화물", cargos: "화물", packages: "소포", package: "소포", // 차량 관련 vehicles: "차량", vehicle: "차량", vehicle_locations: "차량위치", vehicle_status: "차량상태", drivers: "기사", driver: "기사", // 사용자/고객 관련 users: "사용자", user: "사용자", customers: "고객", customer: "고객", members: "회원", member: "회원", // 제품/재고 관련 products: "제품", product: "제품", items: "항목", item: "항목", inventory: "재고", stock: "재고", // 업무 관련 tasks: "작업", task: "작업", projects: "프로젝트", project: "프로젝트", issues: "이슈", issue: "이슈", tickets: "티켓", ticket: "티켓", // 기타 logs: "로그", log: "로그", reports: "리포트", report: "리포트", alerts: "알림", alert: "알림", }; interface StatusData { status: string; count: number; } /** * 범용 상태 요약 위젯 * - 쿼리 결과를 상태별로 카운트해서 카드로 표시 * - 색상과 라벨은 statusConfig로 커스터마이징 가능 */ export default function StatusSummaryWidget({ element, title = "상태 요약", icon = "📊", bgGradient = "from-background to-primary/10", statusConfig, }: StatusSummaryWidgetProps) { const [statusData, setStatusData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [tableName, setTableName] = useState(null); useEffect(() => { loadData(); // 자동 새로고침 (30초마다) const interval = setInterval(loadData, 30000); return () => clearInterval(interval); }, [element]); const loadData = async () => { if (!element?.dataSource?.query) { // 쿼리가 없으면 에러가 아니라 초기 상태로 처리 setError(null); setLoading(false); setTableName(null); return; } // 쿼리에서 테이블 이름 추출 const extractTableName = (query: string): string | null => { const fromMatch = query.match(/FROM\s+([a-zA-Z0-9_가-힣]+)/i); if (fromMatch) { return fromMatch[1]; } return null; }; try { setLoading(true); const extractedTableName = extractTableName(element.dataSource.query); setTableName(extractedTableName); const token = localStorage.getItem("authToken"); const response = await fetch(getApiUrl("/api/dashboards/execute-query"), { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ query: element.dataSource.query, connectionType: element.dataSource.connectionType || "current", connectionId: element.dataSource.connectionId, }), }); if (!response.ok) throw new Error("데이터 로딩 실패"); const result = await response.json(); // 데이터 처리 if (result.success && result.data?.rows) { const rows = result.data.rows; // 상태별 카운트 계산 const statusCounts: { [key: string]: number } = {}; // GROUP BY 형식인지 확인 const isGroupedData = rows.length > 0 && rows[0].count !== undefined; if (isGroupedData) { // GROUP BY 형식: SELECT status, COUNT(*) as count rows.forEach((row: any) => { // 다양한 컬럼명 지원 (status, 상태, state 등) let status = row.status || row.상태 || row.state || row.STATUS || row.label || row.name || "알 수 없음"; // 영어 → 한글 자동 번역 status = statusTranslations[status] || statusTranslations[status.toLowerCase()] || status; const count = parseInt(row.count || row.개수 || row.COUNT || row.cnt) || 0; statusCounts[status] = count; }); } else { // SELECT * 형식: 전체 데이터를 가져와서 카운트 rows.forEach((row: any) => { // 다양한 컬럼명 지원 let status = row.status || row.상태 || row.state || row.STATUS || row.label || row.name || "알 수 없음"; // 영어 → 한글 자동 번역 status = statusTranslations[status] || statusTranslations[status.toLowerCase()] || status; statusCounts[status] = (statusCounts[status] || 0) + 1; }); } // statusConfig가 있으면 해당 순서대로, 없으면 전체 표시 let formattedData: StatusData[]; if (statusConfig) { formattedData = Object.keys(statusConfig).map((key) => ({ status: statusConfig[key].label, count: statusCounts[key] || 0, })); } else { formattedData = Object.entries(statusCounts).map(([status, count]) => ({ status, count, })); } setStatusData(formattedData); } setError(null); } catch (err) { setError(err instanceof Error ? err.message : "데이터 로딩 실패"); } finally { setLoading(false); } }; const getColorClasses = (status: string) => { // statusConfig에서 색상 찾기 let color: string = "gray"; if (statusConfig) { const configEntry = Object.entries(statusConfig).find(([_, v]) => v.label === status); if (configEntry) { color = configEntry[1].color; } } const colorMap = { blue: { border: "border-primary", dot: "bg-primary", text: "text-primary" }, green: { border: "border-success", dot: "bg-success", text: "text-success" }, red: { border: "border-destructive", dot: "bg-destructive", text: "text-destructive" }, yellow: { border: "border-warning", dot: "bg-warning", text: "text-warning" }, orange: { border: "border-warning", dot: "bg-warning", text: "text-warning" }, purple: { border: "border-primary", dot: "bg-primary", text: "text-primary" }, gray: { border: "border-border", dot: "bg-muted-foreground", text: "text-muted-foreground" }, }; return colorMap[color as keyof typeof colorMap] || colorMap.gray; }; if (loading) { return (

데이터 로딩 중...

); } if (error) { return (

⚠️ {error}

); } if (!element?.dataSource?.query) { return (
{icon}

{title}

📊 상태별 데이터 집계 위젯

  • • SQL 쿼리로 데이터를 불러옵니다
  • • 상태별로 자동 집계하여 카드로 표시
  • • 실시간 데이터 모니터링 가능
  • • 색상과 라벨 커스터마이징 지원

⚙️ 설정 방법

SQL 쿼리를 입력하고 저장하세요

); } const totalCount = statusData.reduce((sum, item) => sum + item.count, 0); // 테이블 이름이 있으면 제목을 테이블 이름으로 변경 const translateTableName = (name: string): string => { // 정확한 매칭 시도 if (tableTranslations[name]) { return tableTranslations[name]; } // 소문자로 변환하여 매칭 시도 if (tableTranslations[name.toLowerCase()]) { return tableTranslations[name.toLowerCase()]; } // 언더스코어 제거하고 매칭 시도 const nameWithoutUnderscore = name.replace(/_/g, ""); if (tableTranslations[nameWithoutUnderscore.toLowerCase()]) { return tableTranslations[nameWithoutUnderscore.toLowerCase()]; } // 번역이 없으면 원본 반환 return name; }; // customTitle이 있으면 사용, 없으면 테이블명으로 자동 생성 const displayTitle = element.customTitle || (tableName ? `${translateTableName(tableName)} 현황` : title); return (
{/* 헤더 */}

{icon} {displayTitle}

{totalCount > 0 ? (

총 {totalCount.toLocaleString()}건

) : (

⚙️ 데이터 연결 필요

)}
{/* 스크롤 가능한 콘텐츠 영역 */}
{/* 상태별 카드 */}
{statusData.map((item) => { const colors = getColorClasses(item.status); return (
{item.status}
{item.count.toLocaleString()}
); })}
); }