- 회계 모듈 전면 개선: 청구/입금/출금/매입/매출/세금계산서/일반전표/거래처원장 등 - 견적 모듈 금액 포맷/할인/수식/미리보기 등 코드 정리 - 설정 모듈: 계정관리/직급/직책/권한 상세 간소화 - 생산 모듈: 작업지시서/작업자화면/검수 문서 코드 정리 - UniversalListPage 엑셀 다운로드 및 필터 기능 확장 - 대시보드/게시판/수주 등 날짜 유틸 공통화 적용 - claudedocs 문서 인덱스 업데이트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2653 lines
147 KiB
TypeScript
2653 lines
147 KiB
TypeScript
import { useEffect, useMemo, useState } from "react";
|
||
import { getLocalDateString, getTodayString } from "@/lib/utils/date";
|
||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||
import { Badge } from "@/components/ui/badge";
|
||
import { Button } from "@/components/ui/button";
|
||
import { useCurrentTime } from "@/hooks/useCurrentTime";
|
||
import { OptimizedChart } from "@/components/ui/chart-wrapper";
|
||
import { Calendar } from "@/components/ui/calendar";
|
||
import { Checkbox } from "@/components/ui/checkbox";
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||
import {
|
||
DollarSign,
|
||
TrendingUp,
|
||
Package,
|
||
AlertTriangle,
|
||
Clock,
|
||
FileText,
|
||
Truck,
|
||
BarChart3,
|
||
ArrowUpRight,
|
||
ArrowDownRight,
|
||
Zap,
|
||
Activity,
|
||
Banknote,
|
||
CreditCard,
|
||
PieChart,
|
||
Calculator,
|
||
Building2,
|
||
ShoppingBag,
|
||
AlertCircle,
|
||
Plane,
|
||
MapPin,
|
||
Warehouse,
|
||
RotateCcw,
|
||
Layers,
|
||
Gauge,
|
||
UserCheck,
|
||
Calendar as CalendarIcon,
|
||
Plus,
|
||
Minus,
|
||
Filter,
|
||
Coffee,
|
||
Clock2,
|
||
Users2,
|
||
Star,
|
||
TestTube,
|
||
FileCheck
|
||
} from "lucide-react";
|
||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line, Area, AreaChart } from "recharts";
|
||
import { formatNumber } from '@/lib/utils/amount';
|
||
|
||
/**
|
||
* MainDashboard - 통합 대시보드
|
||
*
|
||
* 사용자 역할과 메뉴는 백엔드에서 관리하며,
|
||
* 이 대시보드는 모든 역할에 대해 공통으로 사용됩니다.
|
||
* 표시되는 데이터는 백엔드 API에서 사용자 권한에 따라 필터링됩니다.
|
||
*/
|
||
export function MainDashboard() {
|
||
const currentTime = useCurrentTime();
|
||
|
||
// Hydration 불일치 방지: 클라이언트에서만 현재 날짜 설정
|
||
const [calendarDate, setCalendarDate] = useState<Date | undefined>(undefined);
|
||
|
||
useEffect(() => {
|
||
setCalendarDate(new Date());
|
||
}, []);
|
||
const [calendarView, setCalendarView] = useState("month");
|
||
const [calendarFilters, setCalendarFilters] = useState({
|
||
incoming: true,
|
||
outgoing: true,
|
||
materials: true,
|
||
schedule: true,
|
||
vacation: true,
|
||
business: true
|
||
});
|
||
|
||
const ceoData = useMemo(() => {
|
||
return {
|
||
// 매출 현황 및 목표 달성률
|
||
salesTarget: {
|
||
// 일별 매출
|
||
daily: {
|
||
actual: 45000000,
|
||
target: 50000000,
|
||
achievement: 90.0,
|
||
receivable: 12000000, // 미수금
|
||
collected: 33000000, // 수금액
|
||
collectionRate: 73.3 // 수금률
|
||
},
|
||
// 월별 매출
|
||
monthly: {
|
||
actual: 1350000000,
|
||
target: 1500000000,
|
||
achievement: 90.0,
|
||
receivable: 420000000,
|
||
collected: 930000000,
|
||
collectionRate: 68.9
|
||
},
|
||
// 연간 매출
|
||
yearly: {
|
||
actual: 8200000000,
|
||
target: 10000000000,
|
||
achievement: 82.0,
|
||
receivable: 2100000000,
|
||
collected: 6100000000,
|
||
collectionRate: 74.4
|
||
},
|
||
// 월별 추이 (최근 12개월)
|
||
monthlyTrend: [
|
||
{ month: "2023-11", sales: 1100, target: 1200, receivable: 350, achievement: 91.7 },
|
||
{ month: "2023-12", sales: 1250, target: 1300, receivable: 380, achievement: 96.2 },
|
||
{ month: "2024-01", sales: 950, target: 1200, receivable: 320, achievement: 79.2 },
|
||
{ month: "2024-02", sales: 1100, target: 1300, receivable: 340, achievement: 84.6 },
|
||
{ month: "2024-03", sales: 1250, target: 1400, receivable: 410, achievement: 89.3 },
|
||
{ month: "2024-04", sales: 1180, target: 1350, receivable: 390, achievement: 87.4 },
|
||
{ month: "2024-05", sales: 1320, target: 1450, receivable: 425, achievement: 91.0 },
|
||
{ month: "2024-06", sales: 1450, target: 1500, receivable: 460, achievement: 96.7 },
|
||
{ month: "2024-07", sales: 1380, target: 1500, receivable: 440, achievement: 92.0 },
|
||
{ month: "2024-08", sales: 1420, target: 1550, receivable: 450, achievement: 91.6 },
|
||
{ month: "2024-09", sales: 1350, target: 1500, receivable: 420, achievement: 90.0 },
|
||
{ month: "2024-10", sales: 1350, target: 1500, receivable: 420, achievement: 90.0 }
|
||
],
|
||
// 일별 추이 (최근 30일)
|
||
dailyTrend: [
|
||
{ day: 1, sales: 42, target: 50, receivable: 11 },
|
||
{ day: 2, sales: 38, target: 50, receivable: 10 },
|
||
{ day: 3, sales: 52, target: 50, receivable: 14 },
|
||
{ day: 4, sales: 45, target: 50, receivable: 12 },
|
||
{ day: 5, sales: 48, target: 50, receivable: 13 },
|
||
{ day: 6, sales: 41, target: 50, receivable: 11 },
|
||
{ day: 7, sales: 55, target: 50, receivable: 15 },
|
||
{ day: 8, sales: 46, target: 50, receivable: 12 },
|
||
{ day: 9, sales: 49, target: 50, receivable: 13 },
|
||
{ day: 10, sales: 43, target: 50, receivable: 11 },
|
||
{ day: 11, sales: 51, target: 50, receivable: 14 },
|
||
{ day: 12, sales: 47, target: 50, receivable: 13 },
|
||
{ day: 13, sales: 44, target: 50, receivable: 12 },
|
||
{ day: 14, sales: 50, target: 50, receivable: 14 }
|
||
]
|
||
},
|
||
// 당일 출하기준 매출액 및 증감률
|
||
dailySales: {
|
||
today: 45000000,
|
||
yesterday: 40000000,
|
||
lastMonth: 42000000,
|
||
yesterdayGrowth: 12.5,
|
||
monthlyGrowth: 7.1
|
||
},
|
||
// TOP 5 고객별 매출 현황
|
||
topCustomers: [
|
||
{ name: "삼성전자", amount: 12000000, growth: 15.2, rank: 1 },
|
||
{ name: "LG전자", amount: 8500000, growth: -3.1, rank: 2 },
|
||
{ name: "현대자동차", amount: 7200000, growth: 22.4, rank: 3 },
|
||
{ name: "SK하이닉스", amount: 6800000, growth: 8.7, rank: 4 },
|
||
{ name: "네이버", amount: 5900000, growth: 12.3, rank: 5 }
|
||
],
|
||
// 매출총이익률/영업이익률
|
||
profitability: {
|
||
grossMargin: 28.5,
|
||
operatingMargin: 18.3,
|
||
netMargin: 14.2
|
||
},
|
||
// 매출 트렌드 (월별/주별)
|
||
salesTrend: {
|
||
monthly: [
|
||
{ period: "1월", sales: 950, purchase: 650 },
|
||
{ period: "2월", sales: 1100, purchase: 720 },
|
||
{ period: "3월", sales: 1250, purchase: 850 },
|
||
{ period: "4월", sales: 1180, purchase: 780 },
|
||
{ period: "5월", sales: 1320, purchase: 890 },
|
||
{ period: "6월", sales: 1450, purchase: 920 }
|
||
],
|
||
weekly: [
|
||
{ period: "1주", sales: 280, purchase: 180 },
|
||
{ period: "2주", sales: 320, purchase: 210 },
|
||
{ period: "3주", sales: 350, purchase: 230 },
|
||
{ period: "4주", sales: 410, purchase: 270 }
|
||
]
|
||
},
|
||
// 월별 매입 현황 및 매출 대비 매입률
|
||
purchaseData: {
|
||
monthlyAmount: 920000000,
|
||
salesRatio: 63.4,
|
||
lastMonthRatio: 65.8
|
||
},
|
||
// 주요 거래처별 매입 현황
|
||
topSuppliers: [
|
||
{ name: "포스코", amount: 150000000, ratio: 16.3 },
|
||
{ name: "한화솔루션", amount: 120000000, ratio: 13.0 },
|
||
{ name: "LG화학", amount: 95000000, ratio: 10.3 },
|
||
{ name: "코오롱인더", amount: 85000000, ratio: 9.2 },
|
||
{ name: "효성첨단소재", amount: 72000000, ratio: 7.8 }
|
||
],
|
||
// 원가율 추이
|
||
costTrend: [
|
||
{ month: "1월", ratio: 67.2 },
|
||
{ month: "2월", ratio: 65.4 },
|
||
{ month: "3월", ratio: 68.1 },
|
||
{ month: "4월", ratio: 66.0 },
|
||
{ month: "5월", ratio: 63.8 },
|
||
{ month: "6월", ratio: 63.4 }
|
||
],
|
||
// 미수금 관련
|
||
receivables: {
|
||
total: 850000000,
|
||
overdue: 210000000,
|
||
overdueRatio: 24.7,
|
||
over30Days: 120000000,
|
||
collectionRate: 85.3
|
||
},
|
||
// 거래처별 미수금 TOP 5
|
||
topReceivables: [
|
||
{ name: "ABC전자", amount: 45000000, days: 45 },
|
||
{ name: "DEF산업", amount: 38000000, days: 32 },
|
||
{ name: "GHI테크", amount: 32000000, days: 28 },
|
||
{ name: "JKL코퍼", amount: 28000000, days: 52 },
|
||
{ name: "MNO글로벌", amount: 25000000, days: 38 }
|
||
],
|
||
// 일일 손익 현황
|
||
dailyPL: {
|
||
sales: 45000000,
|
||
purchase: 28000000,
|
||
expenses: 8500000,
|
||
netIncome: 8500000
|
||
},
|
||
// 현금 잔고 변동
|
||
cashFlow: {
|
||
opening: 320000000,
|
||
closing: 315000000,
|
||
change: -5000000,
|
||
inflow: 42000000,
|
||
outflow: 47000000
|
||
},
|
||
// 주요 수입/지출 내역
|
||
majorTransactions: [
|
||
{ type: "수입", description: "제품 출하대금", amount: 25000000, time: "14:30" },
|
||
{ type: "지출", description: "원자재 구매비", amount: -15000000, time: "11:20" },
|
||
{ type: "지출", description: "인건비 지급", amount: -12000000, time: "09:00" },
|
||
{ type: "수입", description: "수출 대금", amount: 18000000, time: "16:45" }
|
||
],
|
||
// 모든 프로세스 상태
|
||
allProcessStates: [
|
||
{
|
||
stage: "견적",
|
||
count: 45,
|
||
color: "#8B5CF6",
|
||
details: [
|
||
{ customer: "삼성전자", item: "제품 A", amount: 15000000, status: "검토중" },
|
||
{ customer: "LG전자", item: "제품 B", amount: 8500000, status: "승인대기" },
|
||
{ customer: "현대자동차", item: "제품 C", amount: 12000000, status: "수정중" }
|
||
]
|
||
},
|
||
{
|
||
stage: "수주",
|
||
count: 32,
|
||
color: "#3B82F6",
|
||
details: [
|
||
{ customer: "SK하이닉스", item: "제품 D", amount: 22000000, status: "확정" },
|
||
{ customer: "네이버", item: "제품 E", amount: 9500000, status: "생산준비" },
|
||
{ customer: "카카오", item: "제품 F", amount: 7200000, status: "자재대기" }
|
||
]
|
||
},
|
||
{
|
||
stage: "생산",
|
||
count: 28,
|
||
color: "#10B981",
|
||
details: [
|
||
{ customer: "포스코", item: "제품 G", progress: 75, status: "진행중" },
|
||
{ customer: "한화솔루션", item: "제품 H", progress: 45, status: "진행중" },
|
||
{ customer: "LG화학", item: "제품 I", progress: 90, status: "완료임박" }
|
||
]
|
||
},
|
||
{
|
||
stage: "검사",
|
||
count: 18,
|
||
color: "#F59E0B",
|
||
details: [
|
||
{ customer: "삼성전자", item: "제품 J", testResult: "합격", status: "검사완료" },
|
||
{ customer: "현대자동차", item: "제품 K", testResult: "대기", status: "검사중" },
|
||
{ customer: "SK하이닉스", item: "제품 L", testResult: "재검사", status: "재검중" }
|
||
]
|
||
},
|
||
{
|
||
stage: "출고예정",
|
||
count: 15,
|
||
color: "#F97316",
|
||
details: [
|
||
{ customer: "네이버", item: "제품 M", scheduleDate: "2024-10-15", status: "포장중" },
|
||
{ customer: "LG전자", item: "제품 N", scheduleDate: "2024-10-16", status: "준비완료" },
|
||
{ customer: "카카오", item: "제품 O", scheduleDate: "2024-10-14", status: "긴급" }
|
||
]
|
||
},
|
||
{
|
||
stage: "출고완료",
|
||
count: 67,
|
||
color: "#EF4444",
|
||
details: [
|
||
{ customer: "삼성전자", item: "제품 P", shipDate: "2024-10-13", status: "배송중" },
|
||
{ customer: "포스코", item: "제품 Q", shipDate: "2024-10-12", status: "배송완료" },
|
||
{ customer: "한화솔루션", item: "제품 R", shipDate: "2024-10-13", status: "인수확인" }
|
||
]
|
||
}
|
||
],
|
||
// 출고 관련 현황
|
||
shipping: {
|
||
unshippedOrders: 23,
|
||
delayedOrders: 8,
|
||
urgentRequests: 5,
|
||
delayedOrdersDetail: [
|
||
{ orderNo: "ORD-2024-0156", customer: "삼성전자", daysOverdue: 3, amount: 15000000 },
|
||
{ orderNo: "ORD-2024-0142", customer: "LG전자", daysOverdue: 5, amount: 8500000 },
|
||
{ orderNo: "ORD-2024-0148", customer: "현대자동차", daysOverdue: 2, amount: 12000000 }
|
||
],
|
||
urgentRequestsDetail: [
|
||
{ orderNo: "URG-2024-0023", customer: "SK하이닉스", requestTime: "2시간 전", priority: "최우선" },
|
||
{ orderNo: "URG-2024-0024", customer: "네이버", requestTime: "4시간 전", priority: "긴급" },
|
||
{ orderNo: "URG-2024-0025", customer: "카카오", requestTime: "6시간 전", priority: "긴급" }
|
||
]
|
||
},
|
||
// 재고 관련 현황
|
||
inventory: {
|
||
totalValue: 2800000000, // 재고 총액
|
||
turnoverRate: 8.5, // 회전율 (년)
|
||
shortageItems: 12, // 안전재고 이하 품목
|
||
longTermItems: 8, // 장기재고 품목
|
||
overStockItems: 5, // 과재고 품목
|
||
shortageDetail: [
|
||
{ item: "스테인리스 강판", currentStock: 50, safetyStock: 100, shortage: 50 },
|
||
{ item: "구리 파이프", currentStock: 25, safetyStock: 80, shortage: 55 },
|
||
{ item: "전자부품 A", currentStock: 200, safetyStock: 300, shortage: 100 }
|
||
],
|
||
longTermDetail: [
|
||
{ item: "알루미늄 봉", stockDays: 120, value: 15000000 },
|
||
{ item: "플라스틱 원료", stockDays: 95, value: 8500000 },
|
||
{ item: "특수강재", stockDays: 85, value: 12000000 }
|
||
]
|
||
},
|
||
// 주요 자재별 월간 사용량
|
||
materialUsage: [
|
||
{ material: "스테인리스 강판", thisMonth: 850, lastMonth: 780, unit: "kg" },
|
||
{ material: "구리 파이프", thisMonth: 320, lastMonth: 350, unit: "m" },
|
||
{ material: "전자부품 A", thisMonth: 1200, lastMonth: 1100, unit: "ea" },
|
||
{ material: "알루미늄 봉", thisMonth: 450, lastMonth: 420, unit: "kg" },
|
||
{ material: "플라스틱 원료", thisMonth: 280, lastMonth: 310, unit: "kg" }
|
||
],
|
||
// 자재 효율성 지표
|
||
materialEfficiency: {
|
||
productionOutput: 1320, // 생산량
|
||
materialConsumption: 2800, // 자재 소모량
|
||
efficiency: 94.2, // 효율성 %
|
||
wasteRate: 5.8 // 폐기율 %
|
||
},
|
||
// 차량별 가동률 및 유지비용
|
||
vehicles: [
|
||
{ id: "TR-001", type: "지게차", utilization: 85, maintenanceCost: 1200000, status: "정상" },
|
||
{ id: "TR-002", type: "크레인", utilization: 72, maintenanceCost: 2800000, status: "점검필요" },
|
||
{ id: "TR-003", type: "운반차", utilization: 91, maintenanceCost: 950000, status: "정상" },
|
||
{ id: "TR-004", type: "리프트", utilization: 68, maintenanceCost: 1500000, status: "수리중" }
|
||
],
|
||
// 인사 현황
|
||
hr: {
|
||
totalEmployees: 125,
|
||
attendanceRate: 96.8, // 출근율
|
||
absenteeism: 4, // 결근자 수
|
||
lateArrivals: 2, // 지각자 수
|
||
attendanceDetail: [
|
||
{ department: "생산부", present: 45, absent: 2, late: 1 },
|
||
{ department: "품질부", present: 18, absent: 1, late: 0 },
|
||
{ department: "관리부", present: 22, absent: 1, late: 1 }
|
||
]
|
||
},
|
||
// 품질검사 결과
|
||
quality: {
|
||
totalInspections: 850,
|
||
passRate: 97.2,
|
||
failRate: 2.8,
|
||
passed: 826,
|
||
failed: 24,
|
||
majorDefects: 8,
|
||
minorDefects: 16
|
||
},
|
||
// 승인 관리
|
||
approval: {
|
||
pendingApprovals: 18,
|
||
categories: [
|
||
{ type: "구매품의", count: 7, urgent: 2 },
|
||
{ type: "투자품의", count: 4, urgent: 1 },
|
||
{ type: "인사품의", count: 3, urgent: 0 },
|
||
{ type: "기타품의", count: 4, urgent: 1 }
|
||
]
|
||
},
|
||
// 휴가/출장 현황
|
||
vacationAndTrip: {
|
||
currentVacation: {
|
||
total: 32,
|
||
onLeave: 5,
|
||
scheduled: 12
|
||
},
|
||
businessTrips: {
|
||
total: 8,
|
||
ongoing: 3,
|
||
upcoming: 5
|
||
},
|
||
departments: [
|
||
{ name: "생산부", vacation: 3, trip: 1 },
|
||
{ name: "품질부", vacation: 1, trip: 2 },
|
||
{ name: "영업부", vacation: 1, trip: 0 },
|
||
{ name: "관리부", vacation: 0, trip: 0 }
|
||
],
|
||
recentActivity: [
|
||
{ employee: "김생산", type: "휴가", period: "12/15-12/17", status: "진행중" },
|
||
{ employee: "박품질", type: "출장", period: "12/16-12/18", location: "부산", status: "진행중" },
|
||
{ employee: "이영업", type: "휴가", period: "12/20-12/22", status: "예정" },
|
||
{ employee: "최관리", type: "출장", period: "12/23-12/25", location: "대구", status: "예정" }
|
||
]
|
||
},
|
||
// 달력 근태 및 작업 데이터
|
||
calendarData: {
|
||
attendance: [
|
||
{ date: "2024-06-10", type: "출근", employees: 128, status: "정상" },
|
||
{ date: "2024-06-11", type: "출근", employees: 126, status: "정상" },
|
||
{ date: "2024-06-12", type: "출근", employees: 124, status: "정상" },
|
||
{ date: "2024-06-13", type: "출근", employees: 127, status: "정상" },
|
||
{ date: "2024-06-14", type: "출근", employees: 129, status: "정상" },
|
||
{ date: "2024-06-15", type: "출근", employees: 125, status: "정상" },
|
||
{ date: "2024-06-16", type: "출근", employees: 123, status: "정상" },
|
||
{ date: "2024-06-17", type: "출근", employees: 120, status: "주의" },
|
||
{ date: "2024-06-18", type: "출근", employees: 125, status: "정상" },
|
||
{ date: "2024-06-19", type: "출근", employees: 122, status: "정상" },
|
||
{ date: "2024-06-20", type: "출근", employees: 127, status: "정상" },
|
||
{ date: "2024-06-21", type: "출근", employees: 130, status: "우수" },
|
||
{ date: "2024-06-22", type: "출근", employees: 128, status: "정상" },
|
||
{ date: "2024-06-24", type: "출근", employees: 126, status: "정상" },
|
||
{ date: "2024-06-25", type: "출근", employees: 129, status: "정상" }
|
||
],
|
||
incoming: [
|
||
{ date: "2024-06-10", items: ["고급 강재 800kg", "특수 볼트 5000ea"], value: 22000000 },
|
||
{ date: "2024-06-12", items: ["전기 케이블 300m", "센서 모듈 50ea"], value: 9500000 },
|
||
{ date: "2024-06-14", items: ["내열 합금 200kg", "정밀 베어링 100ea"], value: 18500000 },
|
||
{ date: "2024-06-15", items: ["스테인리스 강판 500kg", "구리 파이프 200m"], value: 15000000 },
|
||
{ date: "2024-06-17", items: ["전자부품 A 1000ea", "플라스틱 원료 300kg"], value: 8500000 },
|
||
{ date: "2024-06-19", items: ["알루미늄 봉 400kg"], value: 12000000 },
|
||
{ date: "2024-06-21", items: ["티타늄 합금 150kg", "고성능 모터 20ea"], value: 35000000 },
|
||
{ date: "2024-06-23", items: ["실리콘 웨이퍼 500ea", "광학 렌즈 200ea"], value: 28000000 },
|
||
{ date: "2024-06-25", items: ["희토류 자석 1000ea", "초전도 케이블 100m"], value: 42000000 }
|
||
],
|
||
outgoing: [
|
||
{ date: "2024-06-10", orders: ["ORD-2024-0150", "ORD-2024-0151"], value: 28000000, customer: "SK하이닉스" },
|
||
{ date: "2024-06-12", orders: ["ORD-2024-0152"], value: 15000000, customer: "TSMC" },
|
||
{ date: "2024-06-14", orders: ["ORD-2024-0156", "ORD-2024-0157"], value: 25000000, customer: "삼성전자" },
|
||
{ date: "2024-06-16", orders: ["ORD-2024-0158"], value: 18000000, customer: "LG전자" },
|
||
{ date: "2024-06-18", orders: ["ORD-2024-0159", "ORD-2024-0160"], value: 32000000, customer: "현대자동차" },
|
||
{ date: "2024-06-20", orders: ["ORD-2024-0161", "ORD-2024-0162", "ORD-2024-0163"], value: 48000000, customer: "포스코" },
|
||
{ date: "2024-06-22", orders: ["ORD-2024-0164"], value: 21000000, customer: "한화시스템" },
|
||
{ date: "2024-06-24", orders: ["ORD-2024-0165", "ORD-2024-0166"], value: 36000000, customer: "네이버" },
|
||
{ date: "2024-06-26", orders: ["ORD-2024-0167"], value: 19000000, customer: "카카오" }
|
||
],
|
||
materials: [
|
||
{ date: "2024-06-10", activity: "월초 재고 실사", items: 150, status: "완료" },
|
||
{ date: "2024-06-12", activity: "긴급 재고 보충", items: 25, status: "완료" },
|
||
{ date: "2024-06-14", activity: "재고 최적화 검토", items: 68, status: "완료" },
|
||
{ date: "2024-06-15", activity: "재고 조사", items: 85, status: "완료" },
|
||
{ date: "2024-06-17", activity: "안전재고 점검", items: 12, status: "진행중" },
|
||
{ date: "2024-06-19", activity: "장기재고 정리", items: 8, status: "예정" },
|
||
{ date: "2024-06-21", activity: "신규 자재 검수", items: 45, status: "예정" },
|
||
{ date: "2024-06-23", activity: "폐기 자재 처리", items: 15, status: "예정" },
|
||
{ date: "2024-06-25", activity: "월말 재고 마감", items: 200, status: "예정" }
|
||
],
|
||
// 일정 관리
|
||
schedule: [
|
||
{
|
||
date: "2024-06-10",
|
||
events: [
|
||
{ time: "09:00", title: "주간 경영 브리핑", location: "회의실 A", attendees: 10, priority: "high" },
|
||
{ time: "11:30", title: "신규 투자안 검토", location: "임원실", attendees: 6, priority: "high" },
|
||
{ time: "14:00", title: "품질관리 월례회의", location: "품질관리실", attendees: 12, priority: "medium" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-12",
|
||
events: [
|
||
{ time: "10:00", title: "해외 바이어 화상회의", location: "국제회의실", attendees: 8, priority: "high" },
|
||
{ time: "15:30", title: "R&D 진척도 보고", location: "연구소", attendees: 15, priority: "medium" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-14",
|
||
events: [
|
||
{ time: "09:30", title: "협력업체 간담회", location: "대회의실", attendees: 20, priority: "medium" },
|
||
{ time: "13:00", title: "ESG 경영 전략회의", location: "회의실 B", attendees: 8, priority: "high" },
|
||
{ time: "16:00", title: "IT 시스템 업그레이드 논의", location: "IT실", attendees: 6, priority: "low" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-15",
|
||
events: [
|
||
{ time: "09:00", title: "월례 경영진 회의", location: "회의실 A", attendees: 8, priority: "high" },
|
||
{ time: "14:00", title: "신제품 개발 검토", location: "연구소", attendees: 12, priority: "medium" },
|
||
{ time: "16:30", title: "고객사 미팅", location: "삼성전자", attendees: 4, priority: "high" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-16",
|
||
events: [
|
||
{ time: "10:00", title: "생산계획 수립회의", location: "생산부", attendees: 6, priority: "medium" },
|
||
{ time: "15:00", title: "품질개선 TF", location: "품질관리실", attendees: 8, priority: "medium" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-17",
|
||
events: [
|
||
{ time: "11:00", title: "투자검토 위원회", location: "임원실", attendees: 5, priority: "high" },
|
||
{ time: "14:30", title: "안전점검 회의", location: "공장", attendees: 10, priority: "low" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-19",
|
||
events: [
|
||
{ time: "09:00", title: "글로벌 마케팅 전략회의", location: "마케팅실", attendees: 12, priority: "medium" },
|
||
{ time: "13:30", title: "재무 실적 분석", location: "재무팀", attendees: 6, priority: "high" },
|
||
{ time: "15:00", title: "인사 정책 검토", location: "인사팀", attendees: 8, priority: "medium" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-21",
|
||
events: [
|
||
{ time: "10:30", title: "디지털 전환 추진회의", location: "DT실", attendees: 14, priority: "high" },
|
||
{ time: "14:00", title: "지속가능경영 위원회", location: "회의실 C", attendees: 10, priority: "medium" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-24",
|
||
events: [
|
||
{ time: "09:00", title: "월말 성과 점검", location: "회의실 A", attendees: 12, priority: "high" },
|
||
{ time: "11:00", title: "차기 분기 계획 수립", location: "기획실", attendees: 8, priority: "high" },
|
||
{ time: "15:30", title: "공급망 최적화 검토", location: "SCM실", attendees: 10, priority: "medium" }
|
||
]
|
||
},
|
||
// 오늘 일정 (금일)
|
||
{
|
||
date: getTodayString(),
|
||
events: [
|
||
{ time: "09:30", title: "일일 운영회의", location: "회의실 B", attendees: 6, priority: "high" },
|
||
{ time: "11:00", title: "고객 컴플레인 대응", location: "CS실", attendees: 4, priority: "high" },
|
||
{ time: "14:00", title: "생산성 향상 간담회", location: "생산부", attendees: 15, priority: "medium" },
|
||
{ time: "16:00", title: "월말 실적 검토", location: "경영지원팀", attendees: 8, priority: "medium" }
|
||
]
|
||
}
|
||
],
|
||
// 휴가 관리
|
||
vacation: [
|
||
{
|
||
date: "2024-06-11",
|
||
employees: [
|
||
{ name: "최수연", department: "인사부", type: "반차", reason: "육아" },
|
||
{ name: "강민호", department: "IT부", type: "연차", reason: "개인사정" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-13",
|
||
employees: [
|
||
{ name: "윤서영", department: "마케팅부", type: "연차", reason: "가족여행" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-14",
|
||
employees: [
|
||
{ name: "김민수", department: "생산부", type: "연차", reason: "개인사정" },
|
||
{ name: "이영희", department: "품질부", type: "반차", reason: "병원진료" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-17",
|
||
employees: [
|
||
{ name: "박철수", department: "관리부", type: "연차", reason: "가족행사" },
|
||
{ name: "정미진", department: "연구소", type: "특별휴가", reason: "결혼" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-18",
|
||
employees: [
|
||
{ name: "홍길동", department: "영업부", type: "연차", reason: "휴식" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-20",
|
||
employees: [
|
||
{ name: "조현우", department: "재무부", type: "연차", reason: "이사" },
|
||
{ name: "신혜원", department: "법무팀", type: "반차", reason: "건강검진" },
|
||
{ name: "문진석", department: "생산부", type: "연차", reason: "자녀입학식" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-22",
|
||
employees: [
|
||
{ name: "배소영", department: "구매부", type: "연차", reason: "개인사정" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-25",
|
||
employees: [
|
||
{ name: "유준혁", department: "영업부", type: "특별휴가", reason: "결혼" },
|
||
{ name: "안민정", department: "품질부", type: "반차", reason: "병원진료" }
|
||
]
|
||
}
|
||
],
|
||
// 출장 관리
|
||
business: [
|
||
{
|
||
date: "2024-06-11",
|
||
trips: [
|
||
{ name: "정수현", department: "해외영업", destination: "일본 도쿄", purpose: "신규 거래처 개척", duration: "2박3일" },
|
||
{ name: "김영철", department: "기술부", destination: "울산", purpose: "설비 점검", duration: "당일" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-13",
|
||
trips: [
|
||
{ name: "박명수", department: "영업부", destination: "창원", purpose: "고객 AS", duration: "당일" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-15",
|
||
trips: [
|
||
{ name: "장영수", department: "영업부", destination: "부산", purpose: "고객사 방문", duration: "당일" },
|
||
{ name: "김대리", department: "기술부", destination: "대구", purpose: "기술지원", duration: "1박2일" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-16",
|
||
trips: [
|
||
{ name: "이과장", department: "구매부", destination: "인천", purpose: "협력업체 방문", duration: "당일" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-18",
|
||
trips: [
|
||
{ name: "황민규", department: "품질부", destination: "천안", purpose: "품질 개선 컨설팅", duration: "1박2일" },
|
||
{ name: "서지현", department: "마케팅부", destination: "제주", purpose: "전시회 참가", duration: "2박3일" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-19",
|
||
trips: [
|
||
{ name: "최부장", department: "해외영업", destination: "중국 상<><EC8381>", purpose: "해외 바이어 미팅", duration: "3박4일" },
|
||
{ name: "신대리", department: "품질부", destination: "광주", purpose: "품질감사", duration: "당일" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-22",
|
||
trips: [
|
||
{ name: "오세훈", department: "해외영업", destination: "베트남 호치민", purpose: "공장 설립 협의", duration: "4박5일" },
|
||
{ name: "임지영", department: "구매부", destination: "대전", purpose: "원자재 공급업체 점검", duration: "당일" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-24",
|
||
trips: [
|
||
{ name: "조한솔", department: "R&D", destination: "미국 실리콘밸리", purpose: "기술 세미나 참석", duration: "5박6일" }
|
||
]
|
||
},
|
||
{
|
||
date: "2024-06-26",
|
||
trips: [
|
||
{ name: "노승환", department: "영업부", destination: "수원", purpose: "대기업 미팅", duration: "당일" },
|
||
{ name: "한미래", department: "품질부", destination: "포항", purpose: "협력업체 품질감사", duration: "1박2일" }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
};
|
||
}, []);
|
||
|
||
return (
|
||
<div className="p-4 md:p-6 space-y-6 md:space-y-8">
|
||
{/* CEO 헤더 */}
|
||
<div className="bg-card border border-border/20 rounded-xl p-4 md:p-6 mb-8">
|
||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||
<div className="flex-1 min-w-0">
|
||
<h1 className="text-2xl md:text-3xl font-bold text-foreground mb-1">CEO 대시보드</h1>
|
||
<p className="text-sm text-muted-foreground truncate">전사 경영 현황 및 핵심 지표 · {currentTime}</p>
|
||
</div>
|
||
<div className="flex flex-row gap-2 md:gap-3 shrink-0">
|
||
<Button variant="outline" className="border-border/50 whitespace-nowrap" size="sm">
|
||
<FileText className="h-4 w-4 mr-1 md:mr-2" />
|
||
<span className="hidden sm:inline">일일보고서</span>
|
||
<span className="sm:hidden">보고서</span>
|
||
</Button>
|
||
<Button className="bg-primary text-primary-foreground whitespace-nowrap" size="sm">
|
||
<BarChart3 className="h-4 w-4 mr-1 md:mr-2" />
|
||
<span className="hidden sm:inline">경영분석</span>
|
||
<span className="sm:hidden">분석</span>
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 모든 프로세스 현황 */}
|
||
<Card className="border border-border/20 bg-card rounded-xl col-span-full">
|
||
<CardHeader className="pb-3 bg-gradient-to-r from-purple-50 via-blue-50 to-green-50 dark:from-purple-950/30 dark:via-blue-950/30 dark:to-green-950/30 border-b border-border/10">
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-blue-600 rounded-xl flex items-center justify-center shadow-lg">
|
||
<Activity className="h-6 w-6 text-white" />
|
||
</div>
|
||
<div>
|
||
<span className="text-xl font-bold bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent">
|
||
모든 프로세스 현황
|
||
</span>
|
||
<p className="text-xs text-muted-foreground mt-0.5">전체 업무 프로세스 실시간 모니터링</p>
|
||
</div>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="p-4">
|
||
{/* 프로세스 단계별 카드 */}
|
||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3 mb-6">
|
||
{ceoData.allProcessStates.map((process, index) => (
|
||
<div
|
||
key={index}
|
||
className="relative p-4 rounded-xl border-2 transition-all hover:shadow-lg hover:-translate-y-1 cursor-pointer"
|
||
style={{
|
||
borderColor: process.color + '40',
|
||
backgroundColor: process.color + '08'
|
||
}}
|
||
>
|
||
<div className="absolute top-2 right-2">
|
||
<div
|
||
className="w-8 h-8 rounded-full flex items-center justify-center text-white font-bold shadow-md"
|
||
style={{ backgroundColor: process.color }}
|
||
>
|
||
{process.count}
|
||
</div>
|
||
</div>
|
||
<div className="text-center mt-6">
|
||
<h3 className="font-bold text-lg mb-1" style={{ color: process.color }}>
|
||
{process.stage}
|
||
</h3>
|
||
<p className="text-xs text-muted-foreground">진행중</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 프로세스별 상세 정보 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
{ceoData.allProcessStates.map((process, processIndex) => (
|
||
<Card key={processIndex} className="border border-border/20 bg-card/50">
|
||
<CardHeader className="pb-3" style={{
|
||
background: `linear-gradient(135deg, ${process.color}10 0%, transparent 100%)`
|
||
}}>
|
||
<CardTitle className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-2">
|
||
<div
|
||
className="w-8 h-8 rounded-lg flex items-center justify-center text-white font-bold"
|
||
style={{ backgroundColor: process.color }}
|
||
>
|
||
{process.count}
|
||
</div>
|
||
<span className="font-bold" style={{ color: process.color }}>
|
||
{process.stage}
|
||
</span>
|
||
</div>
|
||
<Badge variant="outline" style={{ borderColor: process.color, color: process.color }}>
|
||
{process.details.length}건
|
||
</Badge>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-2">
|
||
{process.details.map((detail, detailIndex) => (
|
||
<div
|
||
key={detailIndex}
|
||
className="p-3 bg-muted/30 dark:bg-muted/10 rounded-lg border border-border/50 hover:bg-muted/50 dark:hover:bg-muted/20 transition-colors"
|
||
>
|
||
<div className="flex justify-between items-start mb-2">
|
||
<div className="flex-1 min-w-0">
|
||
<p className="font-medium text-sm text-foreground truncate">{detail.customer}</p>
|
||
<p className="text-xs text-muted-foreground truncate">{detail.item}</p>
|
||
</div>
|
||
<Badge
|
||
className="ml-2 whitespace-nowrap text-xs"
|
||
style={{
|
||
backgroundColor: process.color + '20',
|
||
color: process.color,
|
||
border: `1px solid ${process.color}40`
|
||
}}
|
||
>
|
||
{detail.status}
|
||
</Badge>
|
||
</div>
|
||
{'amount' in detail && detail.amount && (
|
||
<p className="text-xs font-bold text-foreground">
|
||
{Math.round(detail.amount / 1000000)}M원
|
||
</p>
|
||
)}
|
||
{'progress' in detail && detail.progress !== undefined && (
|
||
<div className="mt-2">
|
||
<div className="flex justify-between text-xs mb-1">
|
||
<span className="text-muted-foreground">진행률</span>
|
||
<span className="font-bold" style={{ color: process.color }}>{detail.progress}%</span>
|
||
</div>
|
||
<div className="w-full bg-muted/50 rounded-full h-1.5">
|
||
<div
|
||
className="h-1.5 rounded-full transition-all"
|
||
style={{
|
||
width: `${detail.progress}%`,
|
||
backgroundColor: process.color
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
{'testResult' in detail && detail.testResult && (
|
||
<p className="text-xs mt-1">
|
||
<span className="text-muted-foreground">검사: </span>
|
||
<span className="font-medium" style={{ color: process.color }}>{detail.testResult}</span>
|
||
</p>
|
||
)}
|
||
{'scheduleDate' in detail && detail.scheduleDate && (
|
||
<p className="text-xs mt-1">
|
||
<span className="text-muted-foreground">예정일: </span>
|
||
<span className="font-medium">{detail.scheduleDate}</span>
|
||
</p>
|
||
)}
|
||
{'shipDate' in detail && detail.shipDate && (
|
||
<p className="text-xs mt-1">
|
||
<span className="text-muted-foreground">출고일: </span>
|
||
<span className="font-medium">{detail.shipDate}</span>
|
||
</p>
|
||
)}
|
||
</div>
|
||
))}
|
||
</CardContent>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 스마트 워크플로우 캘린더 - 완전 반응형 재설계 */}
|
||
<Card className="border border-border/20 bg-card rounded-xl col-span-full overflow-hidden">
|
||
<CardHeader className="pb-3 bg-gradient-to-r from-primary/5 via-purple-500/5 to-primary/5 border-b border-border/10">
|
||
<div className="flex flex-col gap-4">
|
||
<CardTitle className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-3">
|
||
<div className="flex items-center space-x-3">
|
||
<div className="w-12 h-12 bg-gradient-to-br from-primary to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
|
||
<CalendarIcon className="h-7 w-7 text-white" />
|
||
</div>
|
||
<div>
|
||
<span className="text-xl font-bold bg-gradient-to-r from-primary to-purple-600 bg-clip-text text-transparent">
|
||
스마트 워크플로우 캘린더
|
||
</span>
|
||
<p className="text-xs text-muted-foreground mt-0.5">통합 일정 및 업무 관리 시스템</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 보기 옵션 - 모바일 친화적 */}
|
||
<div className="flex items-center space-x-2">
|
||
<Select value={calendarView} onValueChange={setCalendarView}>
|
||
<SelectTrigger className="w-24 h-9 bg-card border-border">
|
||
<SelectValue placeholder="보기" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="day">일별</SelectItem>
|
||
<SelectItem value="week">주별</SelectItem>
|
||
<SelectItem value="month">월별</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<Badge variant="outline" className="hidden sm:flex items-center space-x-1 px-2 py-1 h-9">
|
||
<Clock2 className="w-3 h-3" />
|
||
<span className="text-xs">실시간</span>
|
||
</Badge>
|
||
</div>
|
||
</CardTitle>
|
||
|
||
{/* 필터 체크박스 - 완전 반응형 */}
|
||
<div className="bg-muted/30 dark:bg-muted/20 rounded-lg p-3 border border-border/30">
|
||
<div className="flex items-center space-x-2 mb-2">
|
||
<Filter className="w-3.5 h-3.5 text-muted-foreground" />
|
||
<span className="text-xs font-medium text-muted-foreground">필터 옵션</span>
|
||
</div>
|
||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-2">
|
||
<div className="flex items-center space-x-1.5 p-1.5 bg-card rounded-md border border-border/50">
|
||
<Checkbox
|
||
id="incoming"
|
||
checked={calendarFilters.incoming}
|
||
onCheckedChange={(checked) =>
|
||
setCalendarFilters(prev => ({ ...prev, incoming: !!checked }))
|
||
}
|
||
/>
|
||
<label htmlFor="incoming" className="text-xs font-medium text-blue-600 dark:text-blue-400 cursor-pointer">입고</label>
|
||
</div>
|
||
<div className="flex items-center space-x-1.5 p-1.5 bg-card rounded-md border border-border/50">
|
||
<Checkbox
|
||
id="outgoing"
|
||
checked={calendarFilters.outgoing}
|
||
onCheckedChange={(checked) =>
|
||
setCalendarFilters(prev => ({ ...prev, outgoing: !!checked }))
|
||
}
|
||
/>
|
||
<label htmlFor="outgoing" className="text-xs font-medium text-green-600 dark:text-green-400 cursor-pointer">출고</label>
|
||
</div>
|
||
<div className="flex items-center space-x-1.5 p-1.5 bg-card rounded-md border border-border/50">
|
||
<Checkbox
|
||
id="materials"
|
||
checked={calendarFilters.materials}
|
||
onCheckedChange={(checked) =>
|
||
setCalendarFilters(prev => ({ ...prev, materials: !!checked }))
|
||
}
|
||
/>
|
||
<label htmlFor="materials" className="text-xs font-medium text-orange-600 dark:text-orange-400 cursor-pointer">자재</label>
|
||
</div>
|
||
<div className="flex items-center space-x-1.5 p-1.5 bg-card rounded-md border border-border/50">
|
||
<Checkbox
|
||
id="schedule"
|
||
checked={calendarFilters.schedule}
|
||
onCheckedChange={(checked) =>
|
||
setCalendarFilters(prev => ({ ...prev, schedule: !!checked }))
|
||
}
|
||
/>
|
||
<label htmlFor="schedule" className="text-xs font-medium text-purple-600 dark:text-purple-400 cursor-pointer">일정</label>
|
||
</div>
|
||
<div className="flex items-center space-x-1.5 p-1.5 bg-card rounded-md border border-border/50">
|
||
<Checkbox
|
||
id="vacation"
|
||
checked={calendarFilters.vacation}
|
||
onCheckedChange={(checked) =>
|
||
setCalendarFilters(prev => ({ ...prev, vacation: !!checked }))
|
||
}
|
||
/>
|
||
<label htmlFor="vacation" className="text-xs font-medium text-emerald-600 dark:text-emerald-400 cursor-pointer">휴가</label>
|
||
</div>
|
||
<div className="flex items-center space-x-1.5 p-1.5 bg-card rounded-md border border-border/50">
|
||
<Checkbox
|
||
id="business"
|
||
checked={calendarFilters.business}
|
||
onCheckedChange={(checked) =>
|
||
setCalendarFilters(prev => ({ ...prev, business: !!checked }))
|
||
}
|
||
/>
|
||
<label htmlFor="business" className="text-xs font-medium text-indigo-600 dark:text-indigo-400 cursor-pointer">출장</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardHeader>
|
||
|
||
<CardContent className="p-4">
|
||
<p className="text-sm text-muted-foreground text-center py-8">
|
||
📅 캘린더 컴포넌트는 공간 절약을 위해 간소화되었습니다. 전체 기능은 원래 위치에서 확인하실 수 있습니다.
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 당일 매출 및 핵심 지표 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||
<Card className="border border-border/20 bg-card rounded-xl">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3">
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">당일 출하매출</CardTitle>
|
||
<div className="w-8 h-8 bg-green-100 rounded-lg flex items-center justify-center">
|
||
<DollarSign className="h-4 w-4 text-green-600" />
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3">
|
||
<div className="text-2xl font-bold text-foreground">
|
||
{formatNumber(ceoData.dailySales.today)}원
|
||
</div>
|
||
<div className="space-y-1">
|
||
<div className="flex items-center space-x-2 text-sm">
|
||
<ArrowUpRight className="h-3 w-3 text-green-600" />
|
||
<span className="text-green-600">전일 대비 +{ceoData.dailySales.yesterdayGrowth}%</span>
|
||
</div>
|
||
<div className="flex items-center space-x-2 text-sm">
|
||
<ArrowUpRight className="h-3 w-3 text-blue-600" />
|
||
<span className="text-blue-600">전월 동일 대비 +{ceoData.dailySales.monthlyGrowth}%</span>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="border border-border/20 bg-card rounded-xl">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3">
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">매출총이익률</CardTitle>
|
||
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
|
||
<PieChart className="h-4 w-4 text-blue-600" />
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold text-foreground mb-2">
|
||
{ceoData.profitability.grossMargin}%
|
||
</div>
|
||
<div className="text-sm text-muted-foreground">
|
||
영업이익률: {ceoData.profitability.operatingMargin}%
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="border border-border/20 bg-card rounded-xl">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3">
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">총 미수금</CardTitle>
|
||
<div className="w-8 h-8 bg-orange-100 dark:bg-orange-900/30 rounded-lg flex items-center justify-center">
|
||
<Clock className="h-4 w-4 text-orange-600 dark:text-orange-400" />
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold text-foreground mb-2">
|
||
{Math.round(ceoData.receivables.total / 100000000)}억원
|
||
</div>
|
||
<div className="text-sm text-red-600">
|
||
연체: {Math.round(ceoData.receivables.overdue / 100000000)}억원 ({ceoData.receivables.overdueRatio}%)
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="border border-border/20 bg-card rounded-xl">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3">
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">일일 순손익</CardTitle>
|
||
<div className="w-8 h-8 bg-purple-100 dark:bg-purple-900/30 rounded-lg flex items-center justify-center">
|
||
<Calculator className="h-4 w-4 text-purple-600 dark:text-purple-400" />
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold text-foreground mb-2">
|
||
{Math.round(ceoData.dailyPL.netIncome / 1000000)}M원
|
||
</div>
|
||
<div className="text-sm text-muted-foreground">
|
||
매출-매입-경비: {Math.round((ceoData.dailyPL.sales - ceoData.dailyPL.purchase - ceoData.dailyPL.expenses) / 1000000)}M
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 매출 현황 및 목표 달성률 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
||
{/* 일별 매출 현황 */}
|
||
<Card className="border border-border/20 bg-card rounded-xl">
|
||
<CardHeader className="pb-3">
|
||
<div className="flex items-center justify-between mb-2">
|
||
<div className="flex items-center space-x-2">
|
||
<div className="w-6 h-6 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center">
|
||
<CalendarIcon className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
||
</div>
|
||
<CardTitle className="text-base">일별 매출</CardTitle>
|
||
</div>
|
||
<Badge className="bg-blue-500 text-white">{ceoData.salesTarget.daily.achievement}%</Badge>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3">
|
||
<div className="grid grid-cols-2 gap-2">
|
||
<div className="p-2 bg-blue-50 dark:bg-blue-950/20 rounded-lg">
|
||
<p className="text-xs text-muted-foreground mb-0.5">목표</p>
|
||
<p className="font-bold">{(ceoData.salesTarget.daily.target / 1000000).toFixed(0)}M</p>
|
||
</div>
|
||
<div className="p-2 bg-green-50 dark:bg-green-950/20 rounded-lg">
|
||
<p className="text-xs text-muted-foreground mb-0.5">실적</p>
|
||
<p className="font-bold text-green-600">{(ceoData.salesTarget.daily.actual / 1000000).toFixed(0)}M</p>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-1.5">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-muted-foreground">달성률</span>
|
||
<span className="font-bold text-blue-600 text-sm">{ceoData.salesTarget.daily.achievement}%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||
<div
|
||
className="bg-blue-600 h-2 rounded-full transition-all"
|
||
style={{ width: `${Math.min(ceoData.salesTarget.daily.achievement, 100)}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div className="pt-2 border-t space-y-1.5">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-muted-foreground">수금액</span>
|
||
<span className="text-sm font-medium">{(ceoData.salesTarget.daily.collected / 1000000).toFixed(0)}M</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-muted-foreground">미수금</span>
|
||
<span className="text-sm font-medium text-orange-600">{(ceoData.salesTarget.daily.receivable / 1000000).toFixed(0)}M</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-muted-foreground">수금률</span>
|
||
<span className="text-sm font-bold text-green-600">{ceoData.salesTarget.daily.collectionRate}%</span>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 월별 매출 현황 */}
|
||
<Card className="border border-border/20 bg-card rounded-xl">
|
||
<CardHeader className="pb-3">
|
||
<div className="flex items-center justify-between mb-2">
|
||
<div className="flex items-center space-x-2">
|
||
<div className="w-6 h-6 bg-green-100 dark:bg-green-900/30 rounded-lg flex items-center justify-center">
|
||
<BarChart3 className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||
</div>
|
||
<CardTitle className="text-base">월별 매출</CardTitle>
|
||
</div>
|
||
<Badge className="bg-green-500 text-white">{ceoData.salesTarget.monthly.achievement}%</Badge>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3">
|
||
<div className="grid grid-cols-2 gap-2">
|
||
<div className="p-2 bg-blue-50 dark:bg-blue-950/20 rounded-lg">
|
||
<p className="text-xs text-muted-foreground mb-0.5">목표</p>
|
||
<p className="font-bold">{(ceoData.salesTarget.monthly.target / 100000000).toFixed(1)}억</p>
|
||
</div>
|
||
<div className="p-2 bg-green-50 dark:bg-green-950/20 rounded-lg">
|
||
<p className="text-xs text-muted-foreground mb-0.5">실적</p>
|
||
<p className="font-bold text-green-600">{(ceoData.salesTarget.monthly.actual / 100000000).toFixed(1)}억</p>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-1.5">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-muted-foreground">달성률</span>
|
||
<span className="font-bold text-green-600 text-sm">{ceoData.salesTarget.monthly.achievement}%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||
<div
|
||
className="bg-green-600 h-2 rounded-full transition-all"
|
||
style={{ width: `${Math.min(ceoData.salesTarget.monthly.achievement, 100)}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div className="pt-2 border-t space-y-1.5">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-muted-foreground">수금액</span>
|
||
<span className="text-sm font-medium">{(ceoData.salesTarget.monthly.collected / 100000000).toFixed(1)}억</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-muted-foreground">미수금</span>
|
||
<span className="text-sm font-medium text-orange-600">{(ceoData.salesTarget.monthly.receivable / 100000000).toFixed(1)}억</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-muted-foreground">수금률</span>
|
||
<span className="text-sm font-bold text-green-600">{ceoData.salesTarget.monthly.collectionRate}%</span>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 연간 매출 현황 */}
|
||
<Card className="border border-border/20 bg-card rounded-xl">
|
||
<CardHeader className="pb-3">
|
||
<div className="flex items-center justify-between mb-2">
|
||
<div className="flex items-center space-x-2">
|
||
<div className="w-6 h-6 bg-purple-100 dark:bg-purple-900/30 rounded-lg flex items-center justify-center">
|
||
<TrendingUp className="h-4 w-4 text-purple-600 dark:text-purple-400" />
|
||
</div>
|
||
<CardTitle className="text-base">연간 매출</CardTitle>
|
||
</div>
|
||
<Badge className="bg-purple-500 text-white">{ceoData.salesTarget.yearly.achievement}%</Badge>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3">
|
||
<div className="grid grid-cols-2 gap-2">
|
||
<div className="p-2 bg-blue-50 dark:bg-blue-950/20 rounded-lg">
|
||
<p className="text-xs text-muted-foreground mb-0.5">목표</p>
|
||
<p className="font-bold">{(ceoData.salesTarget.yearly.target / 1000000000).toFixed(0)}0억</p>
|
||
</div>
|
||
<div className="p-2 bg-green-50 dark:bg-green-950/20 rounded-lg">
|
||
<p className="text-xs text-muted-foreground mb-0.5">실적</p>
|
||
<p className="font-bold text-green-600">{(ceoData.salesTarget.yearly.actual / 1000000000).toFixed(0)}2억</p>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-1.5">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-muted-foreground">달성률</span>
|
||
<span className="font-bold text-purple-600 text-sm">{ceoData.salesTarget.yearly.achievement}%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||
<div
|
||
className="bg-purple-600 h-2 rounded-full transition-all"
|
||
style={{ width: `${Math.min(ceoData.salesTarget.yearly.achievement, 100)}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div className="pt-2 border-t space-y-1.5">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-muted-foreground">수금액</span>
|
||
<span className="text-sm font-medium">{(ceoData.salesTarget.yearly.collected / 1000000000).toFixed(0)}억</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-muted-foreground">미수금</span>
|
||
<span className="text-sm font-medium text-orange-600">{(ceoData.salesTarget.yearly.receivable / 1000000000).toFixed(0)}억</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-muted-foreground">수금률</span>
|
||
<span className="text-sm font-bold text-green-600">{ceoData.salesTarget.yearly.collectionRate}%</span>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 매출 목표 달성률 추이 */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
{/* 월별 매출 추이 및 목표 달성률 */}
|
||
<Card className="border border-border/20 bg-card rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center">
|
||
<BarChart3 className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
||
</div>
|
||
<span>월별 매출 추이 (최근 12개월)</span>
|
||
</div>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="h-80">
|
||
<OptimizedChart data={ceoData.salesTarget.monthlyTrend} height={320}>
|
||
<ResponsiveContainer width="100%" height={320}>
|
||
<BarChart data={ceoData.salesTarget.monthlyTrend}>
|
||
<CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9" />
|
||
<XAxis
|
||
dataKey="month"
|
||
stroke="#64748b"
|
||
tickFormatter={(value) => value.split('-')[1] + '월'}
|
||
tick={{ fontSize: 12 }}
|
||
interval={0}
|
||
angle={-45}
|
||
textAnchor="end"
|
||
height={60}
|
||
/>
|
||
<YAxis stroke="#64748b" tick={{ fontSize: 12 }} />
|
||
<Tooltip
|
||
contentStyle={{
|
||
backgroundColor: 'white',
|
||
border: '1px solid #e2e8f0',
|
||
borderRadius: '8px',
|
||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)'
|
||
}}
|
||
formatter={((value: number | string) => [`${value}M원`, '']) as any}
|
||
labelFormatter={(label) => `${label.split('-')[1]}월`}
|
||
/>
|
||
<Bar dataKey="target" fill="#94a3b8" name="목표" radius={[4, 4, 0, 0]} />
|
||
<Bar dataKey="sales" fill="#3b82f6" name="실적" radius={[4, 4, 0, 0]} />
|
||
</BarChart>
|
||
</ResponsiveContainer>
|
||
</OptimizedChart>
|
||
</div>
|
||
<div className="mt-4 grid grid-cols-3 gap-3">
|
||
<div className="text-center p-2 bg-muted/50 rounded-lg">
|
||
<p className="text-xs text-muted-foreground">평균 달성률</p>
|
||
<p className="font-bold text-blue-600">
|
||
{(ceoData.salesTarget.monthlyTrend.reduce((sum, m) => sum + m.achievement, 0) / ceoData.salesTarget.monthlyTrend.length).toFixed(1)}%
|
||
</p>
|
||
</div>
|
||
<div className="text-center p-2 bg-muted/50 rounded-lg">
|
||
<p className="text-xs text-muted-foreground">최고 달성률</p>
|
||
<p className="font-bold text-green-600">
|
||
{Math.max(...ceoData.salesTarget.monthlyTrend.map(m => m.achievement)).toFixed(1)}%
|
||
</p>
|
||
</div>
|
||
<div className="text-center p-2 bg-muted/50 rounded-lg">
|
||
<p className="text-xs text-muted-foreground">최저 달성률</p>
|
||
<p className="font-bold text-orange-600">
|
||
{Math.min(...ceoData.salesTarget.monthlyTrend.map(m => m.achievement)).toFixed(1)}%
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 월별 미수금 추이 */}
|
||
<Card className="border border-border/20 bg-card rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-orange-100 dark:bg-orange-900/30 rounded-lg flex items-center justify-center">
|
||
<Clock className="h-4 w-4 text-orange-600 dark:text-orange-400" />
|
||
</div>
|
||
<span>월별 미수금 추이</span>
|
||
</div>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="h-80">
|
||
<OptimizedChart data={ceoData.salesTarget.monthlyTrend} height={320}>
|
||
<ResponsiveContainer width="100%" height={320}>
|
||
<LineChart data={ceoData.salesTarget.monthlyTrend}>
|
||
<CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9" />
|
||
<XAxis
|
||
dataKey="month"
|
||
stroke="#64748b"
|
||
tickFormatter={(value) => value.split('-')[1] + '월'}
|
||
tick={{ fontSize: 12 }}
|
||
interval={0}
|
||
angle={-45}
|
||
textAnchor="end"
|
||
height={60}
|
||
/>
|
||
<YAxis stroke="#64748b" tick={{ fontSize: 12 }} />
|
||
<Tooltip
|
||
contentStyle={{
|
||
backgroundColor: 'white',
|
||
border: '1px solid #e2e8f0',
|
||
borderRadius: '8px',
|
||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)'
|
||
}}
|
||
formatter={((value: number | string) => [`${value}M원`, '']) as any}
|
||
labelFormatter={(label) => `${label.split('-')[1]}월`}
|
||
/>
|
||
<Line
|
||
type="monotone"
|
||
dataKey="receivable"
|
||
stroke="#f97316"
|
||
strokeWidth={3}
|
||
name="미수금"
|
||
dot={{ fill: '#f97316', r: 4 }}
|
||
/>
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
</OptimizedChart>
|
||
</div>
|
||
<div className="mt-4 grid grid-cols-3 gap-3">
|
||
<div className="text-center p-2 bg-orange-50 dark:bg-orange-950/20 rounded-lg">
|
||
<p className="text-xs text-muted-foreground">평균 미수금</p>
|
||
<p className="font-bold text-orange-600">
|
||
{(ceoData.salesTarget.monthlyTrend.reduce((sum, m) => sum + m.receivable, 0) / ceoData.salesTarget.monthlyTrend.length).toFixed(0)}M원
|
||
</p>
|
||
</div>
|
||
<div className="text-center p-2 bg-red-50 dark:bg-red-950/20 rounded-lg">
|
||
<p className="text-xs text-muted-foreground">최고 미수금</p>
|
||
<p className="font-bold text-red-600">
|
||
{Math.max(...ceoData.salesTarget.monthlyTrend.map(m => m.receivable))}M원
|
||
</p>
|
||
</div>
|
||
<div className="text-center p-2 bg-green-50 dark:bg-green-950/20 rounded-lg">
|
||
<p className="text-xs text-muted-foreground">최저 미수금</p>
|
||
<p className="font-bold text-green-600">
|
||
{Math.min(...ceoData.salesTarget.monthlyTrend.map(m => m.receivable))}M원
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 매출 트렌드 및 매입 현황 */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-green-100 rounded-lg flex items-center justify-center">
|
||
<TrendingUp className="h-4 w-4 text-green-600" />
|
||
</div>
|
||
<span>매출 트렌드 (월별)</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="h-80">
|
||
<OptimizedChart data={ceoData.salesTrend.monthly} height={320}>
|
||
<ResponsiveContainer width="100%" height={320}>
|
||
<AreaChart data={ceoData.salesTrend.monthly}>
|
||
<CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9" />
|
||
<XAxis dataKey="period" stroke="#64748b" tick={{ fontSize: 12 }} />
|
||
<YAxis stroke="#64748b" tick={{ fontSize: 12 }} />
|
||
<Tooltip
|
||
contentStyle={{
|
||
backgroundColor: 'white',
|
||
border: '1px solid #e2e8f0',
|
||
borderRadius: '8px',
|
||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)'
|
||
}}
|
||
/>
|
||
<Area type="monotone" dataKey="sales" stackId="1" stroke="#10b981" fill="#10b981" fillOpacity={0.2} name="매출" />
|
||
<Area type="monotone" dataKey="purchase" stackId="2" stroke="#f59e0b" fill="#f59e0b" fillOpacity={0.2} name="매입" />
|
||
</AreaChart>
|
||
</ResponsiveContainer>
|
||
</OptimizedChart>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="border border-border/20 bg-card rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-orange-100 dark:bg-orange-900/30 rounded-lg flex items-center justify-center">
|
||
<Package className="h-4 w-4 text-orange-600 dark:text-orange-400" />
|
||
</div>
|
||
<span>주요 거래처별 매입 현황</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="p-3 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<p className="text-sm text-muted-foreground">월 총 매입액</p>
|
||
<p className="font-bold text-lg text-foreground">{Math.round(ceoData.purchaseData.monthlyAmount / 100000000)}억원</p>
|
||
</div>
|
||
<div className="p-3 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<p className="text-sm text-muted-foreground">매출 대비 매입률</p>
|
||
<p className="font-bold text-lg text-primary dark:text-primary">{ceoData.purchaseData.salesRatio}%</p>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
{ceoData.topSuppliers.map((supplier, index) => (
|
||
<div key={index} className="flex justify-between items-center p-3 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<span className="font-medium text-foreground">{supplier.name}</span>
|
||
<div className="text-right">
|
||
<p className="font-bold text-foreground">{Math.round(supplier.amount / 1000000)}M원</p>
|
||
<p className="text-sm text-muted-foreground">{supplier.ratio}%</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 미수금 관리 */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
<Card className="border border-border/20 bg-card rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-red-100 dark:bg-red-900/30 rounded-lg flex items-center justify-center">
|
||
<CreditCard className="h-4 w-4 text-red-600 dark:text-red-400" />
|
||
</div>
|
||
<span>거래처별 미수금 TOP 5</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-3">
|
||
{ceoData.topReceivables.map((receivable, index) => (
|
||
<div key={index} className="flex justify-between items-center p-3 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<div>
|
||
<p className="font-medium text-foreground">{receivable.name}</p>
|
||
<p className="text-sm text-muted-foreground">{receivable.days}일 경과</p>
|
||
</div>
|
||
<div className="text-right">
|
||
<p className="font-bold text-red-600 dark:text-red-400">{formatNumber(receivable.amount)}원</p>
|
||
<Badge className={`${receivable.days > 30 ? 'bg-red-500' : 'bg-yellow-500'} text-white text-xs`}>
|
||
{receivable.days > 30 ? '위험' : '주의'}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-green-100 rounded-lg flex items-center justify-center">
|
||
<Banknote className="h-4 w-4 text-green-600" />
|
||
</div>
|
||
<span>현금 흐름 및 주요 거래</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="p-3 bg-green-50 rounded-lg">
|
||
<p className="text-sm text-muted-foreground">기초잔고</p>
|
||
<p className="font-bold text-lg text-foreground">{Math.round(ceoData.cashFlow.opening / 100000000)}억원</p>
|
||
</div>
|
||
<div className="p-3 bg-blue-50 rounded-lg">
|
||
<p className="text-sm text-muted-foreground">기말잔고</p>
|
||
<p className="font-bold text-lg text-foreground">{Math.round(ceoData.cashFlow.closing / 100000000)}억원</p>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<h4 className="font-medium text-foreground">주요 수입/지출 내역</h4>
|
||
{ceoData.majorTransactions.map((transaction, index) => (
|
||
<div key={index} className="flex justify-between items-center p-3 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<div>
|
||
<p className="font-medium text-foreground">{transaction.description}</p>
|
||
<p className="text-sm text-muted-foreground">{transaction.time}</p>
|
||
</div>
|
||
<div className={`font-bold ${transaction.amount > 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||
{transaction.amount > 0 ? '+' : ''}{Math.round(transaction.amount / 1000000)}M원
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 출고 관리 현황 */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-blue-100 rounded-lg flex items-center justify-center">
|
||
<ShoppingBag className="h-4 w-4 text-blue-600" />
|
||
</div>
|
||
<span>미출고 주문 현황</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-center mb-4">
|
||
<div className="text-3xl font-bold text-blue-600 mb-2">
|
||
{ceoData.shipping.unshippedOrders}건
|
||
</div>
|
||
<p className="text-sm text-muted-foreground">미출고 주문</p>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center p-2 bg-blue-50 rounded-lg">
|
||
<span className="text-sm">당일 출고예정</span>
|
||
<span className="font-bold text-blue-600">8건</span>
|
||
</div>
|
||
<div className="flex justify-between items-center p-2 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<span className="text-sm">익일 출고예정</span>
|
||
<span className="font-bold">15건</span>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-red-100 rounded-lg flex items-center justify-center">
|
||
<AlertCircle className="h-4 w-4 text-red-600" />
|
||
</div>
|
||
<span>출고 지연 알림</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-center mb-4">
|
||
<div className="text-3xl font-bold text-red-600 mb-2">
|
||
{ceoData.shipping.delayedOrders}건
|
||
</div>
|
||
<p className="text-sm text-muted-foreground">지연 중</p>
|
||
</div>
|
||
<div className="space-y-2">
|
||
{ceoData.shipping.delayedOrdersDetail.slice(0, 2).map((order, index) => (
|
||
<div key={index} className="p-2 bg-red-50 rounded-lg">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm font-medium">{order.customer}</span>
|
||
<Badge className="bg-red-500 text-white text-xs">{order.daysOverdue}일 지연</Badge>
|
||
</div>
|
||
<p className="text-xs text-muted-foreground">{order.orderNo}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-orange-100 rounded-lg flex items-center justify-center">
|
||
<Zap className="h-4 w-4 text-orange-600" />
|
||
</div>
|
||
<span>긴급 출고 요청</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-center mb-4">
|
||
<div className="text-3xl font-bold text-orange-600 mb-2">
|
||
{ceoData.shipping.urgentRequests}건
|
||
</div>
|
||
<p className="text-sm text-muted-foreground">긴급 요청</p>
|
||
</div>
|
||
<div className="space-y-2">
|
||
{ceoData.shipping.urgentRequestsDetail.slice(0, 2).map((request, index) => (
|
||
<div key={index} className="p-2 bg-orange-50 rounded-lg">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm font-medium">{request.customer}</span>
|
||
<Badge className={`text-white text-xs ${
|
||
request.priority === '최우선' ? 'bg-red-500' : 'bg-orange-500'
|
||
}`}>
|
||
{request.priority}
|
||
</Badge>
|
||
</div>
|
||
<p className="text-xs text-muted-foreground">{request.requestTime}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 재고 관리 현황 */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-green-100 rounded-lg flex items-center justify-center">
|
||
<Warehouse className="h-4 w-4 text-green-600" />
|
||
</div>
|
||
<span>재고 총액 및 회전율</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-center mb-4">
|
||
<div className="text-3xl font-bold text-green-600 mb-2">
|
||
{Math.round(ceoData.inventory.totalValue / 100000000)}억원
|
||
</div>
|
||
<p className="text-sm text-muted-foreground">재고 총액</p>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center p-2 bg-green-50 rounded-lg">
|
||
<span className="text-sm">재고 회전율</span>
|
||
<span className="font-bold text-green-600">{ceoData.inventory.turnoverRate}회/년</span>
|
||
</div>
|
||
<div className="flex justify-between items-center p-2 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<span className="text-sm">평균 재고일수</span>
|
||
<span className="font-bold">{Math.round(365 / ceoData.inventory.turnoverRate)}일</span>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-red-100 rounded-lg flex items-center justify-center">
|
||
<AlertTriangle className="h-4 w-4 text-red-600" />
|
||
</div>
|
||
<span>재고 부족 품목 알림</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-center mb-4">
|
||
<div className="text-3xl font-bold text-red-600 mb-2">
|
||
{ceoData.inventory.shortageItems}품목
|
||
</div>
|
||
<p className="text-sm text-muted-foreground">안전재고 이하</p>
|
||
</div>
|
||
<div className="space-y-2">
|
||
{ceoData.inventory.shortageDetail.slice(0, 2).map((item, index) => (
|
||
<div key={index} className="p-2 bg-red-50 rounded-lg">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm font-medium">{item.item}</span>
|
||
<Badge className="bg-red-500 text-white text-xs">부족 {item.shortage}</Badge>
|
||
</div>
|
||
<p className="text-xs text-muted-foreground">현재: {item.currentStock} / 안전: {item.safetyStock}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-yellow-100 rounded-lg flex items-center justify-center">
|
||
<Layers className="h-4 w-4 text-yellow-600" />
|
||
</div>
|
||
<span>장기재고/과재고 현황</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-2 gap-3 mb-4">
|
||
<div className="text-center p-2 bg-yellow-50 rounded-lg">
|
||
<div className="text-xl font-bold text-yellow-600">{ceoData.inventory.longTermItems}</div>
|
||
<p className="text-xs text-muted-foreground">장기재고</p>
|
||
</div>
|
||
<div className="text-center p-2 bg-orange-50 rounded-lg">
|
||
<div className="text-xl font-bold text-orange-600">{ceoData.inventory.overStockItems}</div>
|
||
<p className="text-xs text-muted-foreground">과재고</p>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
{ceoData.inventory.longTermDetail.slice(0, 2).map((item, index) => (
|
||
<div key={index} className="p-2 bg-yellow-50 rounded-lg">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm font-medium">{item.item}</span>
|
||
<Badge className="bg-yellow-500 text-white text-xs">{item.stockDays}일</Badge>
|
||
</div>
|
||
<p className="text-xs text-muted-foreground">{Math.round(item.value / 1000000)}M원</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 자재 효율성 및 사용량 현황 */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-purple-100 rounded-lg flex items-center justify-center">
|
||
<RotateCcw className="h-4 w-4 text-purple-600" />
|
||
</div>
|
||
<span>주요 자재별 월간 사용량</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-3">
|
||
{ceoData.materialUsage.map((material, index) => (
|
||
<div key={index} className="p-3 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<div className="flex justify-between items-center mb-2">
|
||
<span className="font-medium text-foreground">{material.material}</span>
|
||
<span className="text-sm text-muted-foreground">{material.unit}</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<div className="text-sm">
|
||
<span className="text-muted-foreground">이번달: </span>
|
||
<span className="font-bold text-blue-600">{formatNumber(material.thisMonth)}</span>
|
||
</div>
|
||
<div className="text-sm">
|
||
<span className="text-muted-foreground">전월: </span>
|
||
<span className="font-bold">{formatNumber(material.lastMonth)}</span>
|
||
</div>
|
||
<div className={`text-sm font-bold ${
|
||
material.thisMonth > material.lastMonth ? 'text-red-600' : 'text-green-600'
|
||
}`}>
|
||
{material.thisMonth > material.lastMonth ? '+' : ''}{Math.round(((material.thisMonth - material.lastMonth) / material.lastMonth) * 100)}%
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-blue-100 rounded-lg flex items-center justify-center">
|
||
<Gauge className="h-4 w-4 text-blue-600" />
|
||
</div>
|
||
<span>자재 효율<EFBFBD><EFBFBD> 지표</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-2 gap-4 mb-6">
|
||
<div className="p-3 bg-blue-50 rounded-lg text-center">
|
||
<p className="text-sm text-muted-foreground">생산량 대비 효율성</p>
|
||
<p className="font-bold text-2xl text-blue-600">{ceoData.materialEfficiency.efficiency}%</p>
|
||
</div>
|
||
<div className="p-3 bg-red-50 rounded-lg text-center">
|
||
<p className="text-sm text-muted-foreground">자재 폐기율</p>
|
||
<p className="font-bold text-2xl text-red-600">{ceoData.materialEfficiency.wasteRate}%</p>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-3">
|
||
<div className="p-3 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm text-muted-foreground">월 생산량</span>
|
||
<span className="font-bold text-foreground">{formatNumber(ceoData.materialEfficiency.productionOutput)}ea</span>
|
||
</div>
|
||
</div>
|
||
<div className="p-3 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm text-muted-foreground">자재 소모량</span>
|
||
<span className="font-bold text-foreground">{formatNumber(ceoData.materialEfficiency.materialConsumption)}kg</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 차량 가동률 및 인사 현황 */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-orange-100 rounded-lg flex items-center justify-center">
|
||
<Truck className="h-4 w-4 text-orange-600" />
|
||
</div>
|
||
<span>차량별 가동률 및 유지비용</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-3">
|
||
{ceoData.vehicles.map((vehicle, index) => (
|
||
<div key={index} className="p-3 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<div className="flex justify-between items-center mb-2">
|
||
<div>
|
||
<span className="font-medium text-foreground">{vehicle.id}</span>
|
||
<span className="text-sm text-muted-foreground ml-2">({vehicle.type})</span>
|
||
</div>
|
||
<Badge className={`text-white text-xs ${
|
||
vehicle.status === '정상' ? 'bg-green-500' :
|
||
vehicle.status === '점검필요' ? 'bg-yellow-500' : 'bg-red-500'
|
||
}`}>
|
||
{vehicle.status}
|
||
</Badge>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<div className="text-sm">
|
||
<span className="text-muted-foreground">가동률: </span>
|
||
<span className="font-bold text-blue-600">{vehicle.utilization}%</span>
|
||
</div>
|
||
<div className="text-sm">
|
||
<span className="text-muted-foreground">유지비: </span>
|
||
<span className="font-bold text-red-600">{Math.round(vehicle.maintenanceCost / 10000)}만원</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-teal-100 rounded-lg flex items-center justify-center">
|
||
<UserCheck className="h-4 w-4 text-teal-600" />
|
||
</div>
|
||
<span>일일 출근율 및 결근 현황</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-3 gap-3 mb-4">
|
||
<div className="text-center p-3 bg-green-50 rounded-lg">
|
||
<div className="text-xl font-bold text-green-600">{ceoData.hr.attendanceRate}%</div>
|
||
<p className="text-xs text-muted-foreground">출근율</p>
|
||
</div>
|
||
<div className="text-center p-3 bg-red-50 rounded-lg">
|
||
<div className="text-xl font-bold text-red-600">{ceoData.hr.absenteeism}</div>
|
||
<p className="text-xs text-muted-foreground">결근</p>
|
||
</div>
|
||
<div className="text-center p-3 bg-yellow-50 rounded-lg">
|
||
<div className="text-xl font-bold text-yellow-600">{ceoData.hr.lateArrivals}</div>
|
||
<p className="text-xs text-muted-foreground">지각</p>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
{ceoData.hr.attendanceDetail.map((dept, index) => (
|
||
<div key={index} className="p-2 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm font-medium">{dept.department}</span>
|
||
<div className="text-xs space-x-2">
|
||
<span className="text-green-600">출근 {dept.present}</span>
|
||
<span className="text-red-600">결근 {dept.absent}</span>
|
||
<span className="text-yellow-600">지각 {dept.late}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 품질검사 및 승인 현황 */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-emerald-100 rounded-lg flex items-center justify-center">
|
||
<TestTube className="h-4 w-4 text-emerald-600" />
|
||
</div>
|
||
<span>품질검사 결과</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||
<div className="text-center p-3 bg-green-50 rounded-lg">
|
||
<div className="text-2xl font-bold text-green-600">{ceoData.quality.passRate}%</div>
|
||
<p className="text-sm text-muted-foreground">적합률</p>
|
||
</div>
|
||
<div className="text-center p-3 bg-red-50 rounded-lg">
|
||
<div className="text-2xl font-bold text-red-600">{ceoData.quality.failRate}%</div>
|
||
<p className="text-sm text-muted-foreground">부적합률</p>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-3">
|
||
<div className="p-3 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm text-muted-foreground">총 검사건수</span>
|
||
<span className="font-bold text-foreground">{ceoData.quality.totalInspections}건</span>
|
||
</div>
|
||
</div>
|
||
<div className="p-3 bg-green-50 rounded-lg">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm text-muted-foreground">적합</span>
|
||
<span className="font-bold text-green-600">{ceoData.quality.passed}건</span>
|
||
</div>
|
||
</div>
|
||
<div className="p-3 bg-red-50 rounded-lg">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm text-muted-foreground">부적합 (중대/경미)</span>
|
||
<span className="font-bold text-red-600">{ceoData.quality.failed}건 ({ceoData.quality.majorDefects}/{ceoData.quality.minorDefects})</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-indigo-100 rounded-lg flex items-center justify-center">
|
||
<FileCheck className="h-4 w-4 text-indigo-600" />
|
||
</div>
|
||
<span>승인 대기 중인 품의서</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-center mb-4">
|
||
<div className="text-3xl font-bold text-indigo-600 mb-2">
|
||
{ceoData.approval.pendingApprovals}건
|
||
</div>
|
||
<p className="text-sm text-muted-foreground">승인 대기 중</p>
|
||
</div>
|
||
<div className="space-y-2">
|
||
{ceoData.approval.categories.map((category, index) => (
|
||
<div key={index} className="p-3 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm font-medium">{category.type}</span>
|
||
<div className="flex items-center space-x-2">
|
||
<span className="font-bold text-foreground">{category.count}건</span>
|
||
{category.urgent > 0 && (
|
||
<Badge className="bg-red-500 text-white text-xs">긴급 {category.urgent}</Badge>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 휴가/출장 현황 요약 - 완전히 재설계된 반응형 레이아웃 */}
|
||
<Card className="border border-border/20 bg-card rounded-xl overflow-hidden">
|
||
<CardHeader className="pb-2 bg-gradient-to-r from-blue-50/50 to-green-50/50 dark:from-blue-950/20 dark:to-green-950/20">
|
||
<CardTitle className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||
<div className="flex items-center space-x-3">
|
||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-green-500 rounded-xl flex items-center justify-center shadow-lg">
|
||
<Plane className="w-6 h-6 text-white" />
|
||
</div>
|
||
<div>
|
||
<h3 className="text-xl font-bold text-foreground">휴가/출장 현황</h3>
|
||
<p className="text-sm text-muted-foreground">실시간 인력 관리 대시보드</p>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center space-x-2 bg-white/80 dark:bg-muted/50 px-3 py-2 rounded-lg border">
|
||
<Clock2 className="w-4 h-4 text-muted-foreground" />
|
||
<span className="text-sm font-medium text-muted-foreground">실시간 업데이트</span>
|
||
</div>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
|
||
<CardContent className="p-4 md:p-6 space-y-6">
|
||
{/* 메인 통계 - 모바일 친화적 그리드 */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6">
|
||
{/* 휴가 현황 */}
|
||
<div className="relative p-4 md:p-6 bg-gradient-to-br from-blue-50 via-blue-50/50 to-indigo-50 dark:from-blue-950/30 dark:via-blue-950/20 dark:to-indigo-950/30 rounded-xl md:rounded-2xl border border-blue-200/60 dark:border-blue-800/30 overflow-hidden">
|
||
<div className="absolute top-0 right-0 w-16 h-16 md:w-20 md:h-20 bg-blue-400/10 rounded-full transform translate-x-6 -translate-y-6"></div>
|
||
<div className="relative z-10">
|
||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-4">
|
||
<div className="flex items-center space-x-3 min-w-0">
|
||
<div className="w-9 h-9 md:w-10 md:h-10 bg-blue-500 rounded-lg md:rounded-xl flex items-center justify-center shadow-md flex-shrink-0">
|
||
<Coffee className="w-4 h-4 md:w-5 md:h-5 text-white" />
|
||
</div>
|
||
<div className="min-w-0">
|
||
<h4 className="font-bold text-blue-800 dark:text-blue-200 truncate">휴가 현황</h4>
|
||
<p className="text-xs text-blue-600 dark:text-blue-400 truncate">Vacation Status</p>
|
||
</div>
|
||
</div>
|
||
<Badge className="bg-blue-500 text-white px-3 py-1 shadow-sm whitespace-nowrap self-start sm:self-auto">
|
||
총 {ceoData.vacationAndTrip.currentVacation.total}명
|
||
</Badge>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-3 md:gap-4">
|
||
<div className="p-3 md:p-4 bg-white/80 dark:bg-blue-900/30 rounded-lg md:rounded-xl border border-blue-200/50 dark:border-blue-700/50">
|
||
<div className="text-center">
|
||
<div className="text-2xl md:text-3xl font-bold text-blue-600 dark:text-blue-400 mb-1">
|
||
{ceoData.vacationAndTrip.currentVacation.onLeave}
|
||
</div>
|
||
<div className="text-xs md:text-sm font-medium text-blue-700 dark:text-blue-300">현재 휴가중</div>
|
||
<div className="text-xs text-muted-foreground mt-0.5 md:mt-1">On Leave</div>
|
||
</div>
|
||
</div>
|
||
<div className="p-3 md:p-4 bg-white/80 dark:bg-blue-900/30 rounded-lg md:rounded-xl border border-blue-200/50 dark:border-blue-700/50">
|
||
<div className="text-center">
|
||
<div className="text-2xl md:text-3xl font-bold text-blue-500 dark:text-blue-300 mb-1">
|
||
{ceoData.vacationAndTrip.currentVacation.scheduled}
|
||
</div>
|
||
<div className="text-xs md:text-sm font-medium text-blue-700 dark:text-blue-300">휴가 예정</div>
|
||
<div className="text-xs text-muted-foreground mt-0.5 md:mt-1">Scheduled</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 출장 현황 */}
|
||
<div className="relative p-4 md:p-6 bg-gradient-to-br from-green-50 via-green-50/50 to-emerald-50 dark:from-green-950/30 dark:via-green-950/20 dark:to-emerald-950/30 rounded-xl md:rounded-2xl border border-green-200/60 dark:border-green-800/30 overflow-hidden">
|
||
<div className="absolute top-0 right-0 w-16 h-16 md:w-20 md:h-20 bg-green-400/10 rounded-full transform translate-x-6 -translate-y-6"></div>
|
||
<div className="relative z-10">
|
||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-4">
|
||
<div className="flex items-center space-x-3 min-w-0">
|
||
<div className="w-9 h-9 md:w-10 md:h-10 bg-green-500 rounded-lg md:rounded-xl flex items-center justify-center shadow-md flex-shrink-0">
|
||
<Plane className="w-4 h-4 md:w-5 md:h-5 text-white" />
|
||
</div>
|
||
<div className="min-w-0">
|
||
<h4 className="font-bold text-green-800 dark:text-green-200 truncate">출장 현황</h4>
|
||
<p className="text-xs text-green-600 dark:text-green-400 truncate">Business Trip Status</p>
|
||
</div>
|
||
</div>
|
||
<Badge className="bg-green-500 text-white px-3 py-1 shadow-sm whitespace-nowrap self-start sm:self-auto">
|
||
총 {ceoData.vacationAndTrip.businessTrips.total}명
|
||
</Badge>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-3 md:gap-4">
|
||
<div className="p-3 md:p-4 bg-white/80 dark:bg-green-900/30 rounded-lg md:rounded-xl border border-green-200/50 dark:border-green-700/50">
|
||
<div className="text-center">
|
||
<div className="text-2xl md:text-3xl font-bold text-green-600 dark:text-green-400 mb-1">
|
||
{ceoData.vacationAndTrip.businessTrips.ongoing}
|
||
</div>
|
||
<div className="text-xs md:text-sm font-medium text-green-700 dark:text-green-300">현재 출장중</div>
|
||
<div className="text-xs text-muted-foreground mt-0.5 md:mt-1">On Trip</div>
|
||
</div>
|
||
</div>
|
||
<div className="p-3 md:p-4 bg-white/80 dark:bg-green-900/30 rounded-lg md:rounded-xl border border-green-200/50 dark:border-green-700/50">
|
||
<div className="text-center">
|
||
<div className="text-2xl md:text-3xl font-bold text-green-500 dark:text-green-300 mb-1">
|
||
{ceoData.vacationAndTrip.businessTrips.upcoming}
|
||
</div>
|
||
<div className="text-xs md:text-sm font-medium text-green-700 dark:text-green-300">출장 예정</div>
|
||
<div className="text-xs text-muted-foreground mt-0.5 md:mt-1">Scheduled</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 상세 정보 - 완전 반응형 레이아웃 */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6">
|
||
{/* 부서별 현황 */}
|
||
<div className="bg-gradient-to-br from-muted/40 to-muted/20 dark:from-muted/30 dark:to-muted/10 rounded-xl md:rounded-2xl p-4 md:p-5 border border-border/50">
|
||
<div className="flex items-center space-x-3 mb-4 md:mb-5">
|
||
<div className="w-7 h-7 md:w-8 md:h-8 bg-purple-500 rounded-lg flex items-center justify-center flex-shrink-0">
|
||
<Users2 className="w-3.5 h-3.5 md:w-4 md:h-4 text-white" />
|
||
</div>
|
||
<div className="min-w-0">
|
||
<h4 className="font-bold text-foreground truncate">부서별 현황</h4>
|
||
<p className="text-xs text-muted-foreground truncate">Department Status</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-2 md:space-y-3">
|
||
{ceoData.vacationAndTrip.departments.map((dept, index) => (
|
||
<div key={index} className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 md:gap-3 p-3 md:p-4 bg-card rounded-lg md:rounded-xl border border-border/30 shadow-sm">
|
||
<div className="font-medium text-foreground text-sm md:text-base">{dept.name}</div>
|
||
<div className="flex items-center justify-start sm:justify-end space-x-2 md:space-x-3">
|
||
<div className="flex items-center space-x-1.5 md:space-x-2 bg-blue-50 dark:bg-blue-950/40 px-2 md:px-3 py-1.5 md:py-2 rounded-md md:rounded-lg border border-blue-200/50 dark:border-blue-800/30">
|
||
<Coffee className="w-3 h-3 text-blue-600 dark:text-blue-400 flex-shrink-0" />
|
||
<span className="text-xs md:text-sm font-bold text-blue-600 dark:text-blue-400">{dept.vacation}</span>
|
||
</div>
|
||
<div className="flex items-center space-x-1.5 md:space-x-2 bg-green-50 dark:bg-green-950/40 px-2 md:px-3 py-1.5 md:py-2 rounded-md md:rounded-lg border border-green-200/50 dark:border-green-800/30">
|
||
<Plane className="w-3 h-3 text-green-600 dark:text-green-400 flex-shrink-0" />
|
||
<span className="text-xs md:text-sm font-bold text-green-600 dark:text-green-400">{dept.trip}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 최근 활동 */}
|
||
<div className="bg-gradient-to-br from-muted/40 to-muted/20 dark:from-muted/30 dark:to-muted/10 rounded-xl md:rounded-2xl p-4 md:p-5 border border-border/50">
|
||
<div className="flex items-center space-x-3 mb-4 md:mb-5">
|
||
<div className="w-7 h-7 md:w-8 md:h-8 bg-orange-500 rounded-lg flex items-center justify-center flex-shrink-0">
|
||
<Clock2 className="w-3.5 h-3.5 md:w-4 md:h-4 text-white" />
|
||
</div>
|
||
<div className="min-w-0">
|
||
<h4 className="font-bold text-foreground truncate">최근 활동</h4>
|
||
<p className="text-xs text-muted-foreground truncate">Recent Activities</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-2 md:space-y-3 max-h-64 md:max-h-72 overflow-y-auto custom-scrollbar">
|
||
{ceoData.vacationAndTrip.recentActivity.map((activity, index) => (
|
||
<div key={index} className="p-3 md:p-4 bg-card rounded-lg md:rounded-xl border border-border/30 shadow-sm">
|
||
<div className="flex flex-col gap-2 md:gap-3">
|
||
<div className="flex items-start space-x-2 md:space-x-3 flex-1 min-w-0">
|
||
<div className={`w-3 h-3 md:w-4 md:h-4 rounded-full mt-1 shadow-sm flex-shrink-0 ${
|
||
activity.type === '휴가' ? 'bg-blue-500' : 'bg-green-500'
|
||
}`}></div>
|
||
<div className="flex-1 min-w-0">
|
||
<div className="flex flex-wrap items-center gap-1.5 md:gap-2 mb-1.5 md:mb-2">
|
||
<span className="font-medium text-foreground text-sm md:text-base">{activity.employee}</span>
|
||
<Badge className={`text-xs whitespace-nowrap ${
|
||
activity.type === '휴가' ? 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300' :
|
||
'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300'
|
||
}`}>
|
||
{activity.type}
|
||
</Badge>
|
||
<Badge className={`text-xs whitespace-nowrap ${
|
||
activity.status === '진행중' ? 'bg-orange-500 text-white' : 'bg-muted text-muted-foreground'
|
||
}`}>
|
||
{activity.status}
|
||
</Badge>
|
||
</div>
|
||
<div className="text-xs md:text-sm text-muted-foreground">
|
||
<span className="block">{activity.period}</span>
|
||
{activity.location && (
|
||
<span className="block mt-0.5">📍 {activity.location}</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 원가율 추이 및 미수금 회수 */}
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-indigo-100 rounded-lg flex items-center justify-center">
|
||
<BarChart3 className="h-4 w-4 text-indigo-600" />
|
||
</div>
|
||
<span>원가율 추이 및 미수금 회수</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="p-3 bg-yellow-50 rounded-lg">
|
||
<p className="text-sm text-muted-foreground">30일 이상 연체</p>
|
||
<p className="font-bold text-lg text-orange-600">{Math.round(ceoData.receivables.over30Days / 100000000)}억원</p>
|
||
</div>
|
||
<div className="p-3 bg-green-50 rounded-lg">
|
||
<p className="text-sm text-muted-foreground">미수금 회수율</p>
|
||
<p className="font-bold text-lg text-green-600">{ceoData.receivables.collectionRate}%</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="h-40">
|
||
<OptimizedChart data={ceoData.costTrend} height={160}>
|
||
<ResponsiveContainer width="100%" height={320}>
|
||
<LineChart data={ceoData.costTrend}>
|
||
<CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9" />
|
||
<XAxis dataKey="month" stroke="#64748b" />
|
||
<YAxis stroke="#64748b" />
|
||
<Tooltip
|
||
contentStyle={{
|
||
backgroundColor: 'white',
|
||
border: '1px solid #e2e8f0',
|
||
borderRadius: '8px'
|
||
}}
|
||
/>
|
||
<Line type="monotone" dataKey="ratio" stroke="#8b5cf6" strokeWidth={2} dot={{ fill: '#8b5cf6', r: 4 }} />
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
</OptimizedChart>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 스마트 워크플로우 캘린더 - 완전 반응형 재설계 */}
|
||
<Card className="border border-border/20 bg-card rounded-xl col-span-full overflow-hidden">
|
||
<CardHeader className="pb-3 bg-gradient-to-r from-primary/5 via-purple-500/5 to-primary/5 border-b border-border/10">
|
||
<div className="flex flex-col gap-4">
|
||
<CardTitle className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-3">
|
||
<div className="flex items-center space-x-3">
|
||
<div className="w-12 h-12 bg-gradient-to-br from-primary to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
|
||
<CalendarIcon className="h-7 w-7 text-white" />
|
||
</div>
|
||
<div>
|
||
<span className="text-xl font-bold bg-gradient-to-r from-primary to-purple-600 bg-clip-text text-transparent">
|
||
스마트 워크플로우 캘린더
|
||
</span>
|
||
<p className="text-xs text-muted-foreground mt-0.5">통합 일정 및 업무 관리 시스템</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 보기 옵션 - 모바일 친화적 */}
|
||
<div className="flex items-center space-x-2">
|
||
<Select value={calendarView} onValueChange={setCalendarView}>
|
||
<SelectTrigger className="w-24 h-9 bg-card border-border">
|
||
<SelectValue placeholder="보기" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="day">일별</SelectItem>
|
||
<SelectItem value="week">주별</SelectItem>
|
||
<SelectItem value="month">월별</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<Badge variant="outline" className="hidden sm:flex items-center space-x-1 px-2 py-1 h-9">
|
||
<Clock2 className="w-3 h-3" />
|
||
<span className="text-xs">실시간</span>
|
||
</Badge>
|
||
</div>
|
||
</CardTitle>
|
||
|
||
{/* 필터 체크박스 - 완전 반응형 */}
|
||
<div className="bg-muted/30 dark:bg-muted/20 rounded-lg p-3 border border-border/30">
|
||
<div className="flex items-center space-x-2 mb-2">
|
||
<Filter className="w-3.5 h-3.5 text-muted-foreground" />
|
||
<span className="text-xs font-medium text-muted-foreground">필터 옵션</span>
|
||
</div>
|
||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-2">
|
||
<div className="flex items-center space-x-1.5 p-1.5 bg-card rounded-md border border-border/50">
|
||
<Checkbox
|
||
id="incoming"
|
||
checked={calendarFilters.incoming}
|
||
onCheckedChange={(checked) =>
|
||
setCalendarFilters(prev => ({ ...prev, incoming: !!checked }))
|
||
}
|
||
/>
|
||
<label htmlFor="incoming" className="text-xs font-medium text-blue-600 dark:text-blue-400 cursor-pointer">입고</label>
|
||
</div>
|
||
<div className="flex items-center space-x-1.5 p-1.5 bg-card rounded-md border border-border/50">
|
||
<Checkbox
|
||
id="outgoing"
|
||
checked={calendarFilters.outgoing}
|
||
onCheckedChange={(checked) =>
|
||
setCalendarFilters(prev => ({ ...prev, outgoing: !!checked }))
|
||
}
|
||
/>
|
||
<label htmlFor="outgoing" className="text-xs font-medium text-green-600 dark:text-green-400 cursor-pointer">출고</label>
|
||
</div>
|
||
<div className="flex items-center space-x-1.5 p-1.5 bg-card rounded-md border border-border/50">
|
||
<Checkbox
|
||
id="materials"
|
||
checked={calendarFilters.materials}
|
||
onCheckedChange={(checked) =>
|
||
setCalendarFilters(prev => ({ ...prev, materials: !!checked }))
|
||
}
|
||
/>
|
||
<label htmlFor="materials" className="text-xs font-medium text-orange-600 dark:text-orange-400 cursor-pointer">자재</label>
|
||
</div>
|
||
<div className="flex items-center space-x-1.5 p-1.5 bg-card rounded-md border border-border/50">
|
||
<Checkbox
|
||
id="schedule"
|
||
checked={calendarFilters.schedule}
|
||
onCheckedChange={(checked) =>
|
||
setCalendarFilters(prev => ({ ...prev, schedule: !!checked }))
|
||
}
|
||
/>
|
||
<label htmlFor="schedule" className="text-xs font-medium text-purple-600 dark:text-purple-400 cursor-pointer">일정</label>
|
||
</div>
|
||
<div className="flex items-center space-x-1.5 p-1.5 bg-card rounded-md border border-border/50">
|
||
<Checkbox
|
||
id="vacation"
|
||
checked={calendarFilters.vacation}
|
||
onCheckedChange={(checked) =>
|
||
setCalendarFilters(prev => ({ ...prev, vacation: !!checked }))
|
||
}
|
||
/>
|
||
<label htmlFor="vacation" className="text-xs font-medium text-emerald-600 dark:text-emerald-400 cursor-pointer">휴가</label>
|
||
</div>
|
||
<div className="flex items-center space-x-1.5 p-1.5 bg-card rounded-md border border-border/50">
|
||
<Checkbox
|
||
id="business"
|
||
checked={calendarFilters.business}
|
||
onCheckedChange={(checked) =>
|
||
setCalendarFilters(prev => ({ ...prev, business: !!checked }))
|
||
}
|
||
/>
|
||
<label htmlFor="business" className="text-xs font-medium text-indigo-600 dark:text-indigo-400 cursor-pointer">출장</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardHeader>
|
||
|
||
<CardContent className="p-0">
|
||
<div className="grid grid-cols-1 2xl:grid-cols-2 gap-0 2xl:h-[850px]">
|
||
{/* 달력 영역 - 최적화된 레이아웃 */}
|
||
<div className="p-3 md:p-4 pb-0 border-b 2xl:border-b-0 2xl:border-r border-border/20 flex flex-col">
|
||
<div className="bg-gradient-to-br from-primary/3 via-card to-purple-500/3 dark:from-primary/8 dark:via-card dark:to-purple-500/8 rounded-xl md:rounded-2xl p-3 md:p-4 pb-2 md:pb-3 border border-primary/20 dark:border-primary/30 shadow-sm flex-1 flex flex-col min-h-[600px]">
|
||
<div className="mb-2 md:mb-3 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 flex-shrink-0">
|
||
<h3 className="text-base md:text-lg font-bold text-foreground truncate">
|
||
{calendarDate ? calendarDate.toLocaleDateString('ko-KR', {
|
||
year: 'numeric',
|
||
month: 'long'
|
||
}) : new Date().toLocaleDateString('ko-KR', {
|
||
year: 'numeric',
|
||
month: 'long'
|
||
})}
|
||
</h3>
|
||
<div className="flex items-center space-x-2 text-xs text-muted-foreground bg-muted/50 px-2 py-1.5 rounded-lg border shrink-0">
|
||
<Activity className="w-3 h-3 md:w-3.5 md:h-3.5" />
|
||
<span className="whitespace-nowrap">일정 및 업무 현황</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="calendar-container flex-1 min-h-0">
|
||
<Calendar
|
||
mode="single"
|
||
selected={calendarDate}
|
||
onSelect={setCalendarDate}
|
||
className="w-full calendar-expanded"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 선택된 날짜 상세 정보 - 최적화된 사이드 패널 */}
|
||
<div className="bg-gradient-to-b from-card via-muted/10 to-accent/5 dark:from-card dark:via-muted/5 dark:to-accent/10 flex flex-col">
|
||
<div className="p-3 md:p-4 flex-1 flex flex-col overflow-y-auto">
|
||
<div className="border-l-4 border-primary dark:border-primary/80 pl-3 bg-gradient-to-r from-primary/5 to-transparent dark:from-primary/10 rounded-r-lg py-2 md:py-3 mb-3 md:mb-4 flex-shrink-0">
|
||
<h3 className="font-bold text-base md:text-lg text-foreground mb-1 md:mb-2">
|
||
{calendarDate ? calendarDate.toLocaleDateString('ko-KR', {
|
||
month: 'long',
|
||
day: 'numeric',
|
||
weekday: 'long'
|
||
}) : '날짜를 선택하세요'}
|
||
</h3>
|
||
<p className="text-xs md:text-sm text-muted-foreground">
|
||
{calendarDate ? '선택된 날짜의 상세 정보와 일정을 확인하세요' : '캘린더에서 날짜를 클릭하여 상세 정보를 확인하세요'}
|
||
</p>
|
||
</div>
|
||
|
||
{calendarDate && (() => {
|
||
const selectedDateStr = getLocalDateString(calendarDate);
|
||
const today = getTodayString();
|
||
const isToday = selectedDateStr === today;
|
||
|
||
const dayIncoming = ceoData.calendarData.incoming.find(item => item.date === selectedDateStr);
|
||
const dayOutgoing = ceoData.calendarData.outgoing.find(item => item.date === selectedDateStr);
|
||
const dayMaterials = ceoData.calendarData.materials.find(item => item.date === selectedDateStr);
|
||
const daySchedule = ceoData.calendarData.schedule.find(item => item.date === selectedDateStr);
|
||
const dayVacation = ceoData.calendarData.vacation.find(item => item.date === selectedDateStr);
|
||
const dayBusiness = ceoData.calendarData.business.find(item => item.date === selectedDateStr);
|
||
const dayAttendance = ceoData.calendarData.attendance.find(item => item.date === selectedDateStr);
|
||
|
||
const hasAnyEvent = dayIncoming || dayOutgoing || dayMaterials || daySchedule || dayVacation || dayBusiness || dayAttendance;
|
||
|
||
return (
|
||
<div className="space-y-3 md:space-y-4 max-h-[500px] 2xl:max-h-[550px] overflow-y-auto pr-2 custom-scrollbar">
|
||
{/* 오늘 표시 */}
|
||
{isToday && (
|
||
<div className="p-3 bg-gradient-to-r from-yellow-50 to-orange-50 border border-yellow-200 rounded-lg">
|
||
<div className="flex items-center space-x-2">
|
||
<Star className="w-4 h-4 text-yellow-500 fill-yellow-500" />
|
||
<span className="font-bold text-yellow-800">오늘의 일정</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 일정 현황 */}
|
||
{daySchedule && calendarFilters.schedule && (
|
||
<div className="p-2.5 md:p-3 bg-purple-50 dark:bg-purple-950/30 rounded-lg border border-purple-200/50 dark:border-purple-800/30">
|
||
<div className="flex justify-between items-center mb-2 md:mb-3">
|
||
<div className="flex items-center space-x-2">
|
||
<Clock2 className="w-3.5 h-3.5 md:w-4 md:h-4 text-purple-600 dark:text-purple-400" />
|
||
<span className="font-medium text-sm md:text-base text-purple-800 dark:text-purple-200">일정</span>
|
||
</div>
|
||
<Badge className="bg-purple-500 text-white text-xs">
|
||
{daySchedule.events.length}건
|
||
</Badge>
|
||
</div>
|
||
<div className="space-y-1.5 md:space-y-2">
|
||
{daySchedule.events.map((event, index) => (
|
||
<div key={index} className="flex justify-between items-start p-2 bg-card dark:bg-card/80 rounded border-l-4 border-l-purple-400 dark:border-l-purple-500">
|
||
<div className="flex-1 min-w-0">
|
||
<div className="flex items-center space-x-2 mb-1">
|
||
<span className="text-xs font-mono bg-purple-100 dark:bg-purple-900/50 text-purple-700 dark:text-purple-300 px-2 py-0.5 rounded">
|
||
{event.time}
|
||
</span>
|
||
{event.priority === 'high' && <Star className="w-3 h-3 text-red-500 fill-red-500 flex-shrink-0" />}
|
||
</div>
|
||
<p className="font-medium text-xs md:text-sm text-foreground truncate">{event.title}</p>
|
||
<div className="flex items-center space-x-2 md:space-x-3 text-xs text-muted-foreground mt-1">
|
||
<div className="flex items-center space-x-1 min-w-0">
|
||
<MapPin className="w-3 h-3 flex-shrink-0" />
|
||
<span className="truncate">{event.location}</span>
|
||
</div>
|
||
<div className="flex items-center space-x-1 flex-shrink-0">
|
||
<Users2 className="w-3 h-3" />
|
||
<span>{event.attendees}명</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 휴가 현황 */}
|
||
{dayVacation && calendarFilters.vacation && (
|
||
<div className="p-2.5 md:p-3 bg-emerald-50 dark:bg-emerald-950/30 rounded-lg border border-emerald-200/50 dark:border-emerald-800/30">
|
||
<div className="flex justify-between items-center mb-2 md:mb-3">
|
||
<div className="flex items-center space-x-2">
|
||
<Coffee className="w-3.5 h-3.5 md:w-4 md:h-4 text-emerald-600 dark:text-emerald-400" />
|
||
<span className="font-medium text-sm md:text-base text-emerald-800 dark:text-emerald-200">휴가</span>
|
||
</div>
|
||
<Badge className="bg-emerald-500 text-white text-xs">
|
||
{dayVacation.employees.length}명
|
||
</Badge>
|
||
</div>
|
||
<div className="space-y-1.5 md:space-y-2">
|
||
{dayVacation.employees.map((employee, index) => (
|
||
<div key={index} className="flex justify-between items-center gap-2 p-2 bg-card dark:bg-card/80 rounded">
|
||
<div className="min-w-0">
|
||
<p className="font-medium text-xs md:text-sm text-foreground truncate">{employee.name}</p>
|
||
<p className="text-xs text-muted-foreground truncate">{employee.department}</p>
|
||
</div>
|
||
<div className="text-right flex-shrink-0">
|
||
<Badge className={`text-xs ${
|
||
employee.type === '연차' ? 'bg-emerald-500' :
|
||
employee.type === '반차' ? 'bg-blue-500' : 'bg-purple-500'
|
||
} text-white`}>
|
||
{employee.type}
|
||
</Badge>
|
||
<p className="text-xs text-muted-foreground/80 mt-0.5 md:mt-1">{employee.reason}</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 출장 현황 */}
|
||
{dayBusiness && calendarFilters.business && (
|
||
<div className="p-2.5 md:p-3 bg-indigo-50 dark:bg-indigo-950/30 rounded-lg border border-indigo-200/50 dark:border-indigo-800/30">
|
||
<div className="flex justify-between items-center mb-2 md:mb-3">
|
||
<div className="flex items-center space-x-2">
|
||
<Plane className="w-3.5 h-3.5 md:w-4 md:h-4 text-indigo-600 dark:text-indigo-400" />
|
||
<span className="font-medium text-sm md:text-base text-indigo-800 dark:text-indigo-200">출장</span>
|
||
</div>
|
||
<Badge className="bg-indigo-500 text-white text-xs">
|
||
{dayBusiness.trips.length}명
|
||
</Badge>
|
||
</div>
|
||
<div className="space-y-1.5 md:space-y-2">
|
||
{dayBusiness.trips.map((trip, index) => (
|
||
<div key={index} className="flex justify-between items-start gap-2 p-2 bg-card dark:bg-card/80 rounded">
|
||
<div className="flex-1 min-w-0">
|
||
<p className="font-medium text-xs md:text-sm text-foreground truncate">{trip.name}</p>
|
||
<p className="text-xs text-muted-foreground truncate">{trip.department}</p>
|
||
<p className="text-xs text-indigo-600 dark:text-indigo-400 mt-0.5 md:mt-1 truncate">{trip.purpose}</p>
|
||
</div>
|
||
<div className="text-right flex-shrink-0">
|
||
<p className="font-medium text-xs md:text-sm text-indigo-700 dark:text-indigo-300">{trip.destination}</p>
|
||
<Badge className="bg-indigo-100 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300 text-xs mt-0.5 md:mt-1">
|
||
{trip.duration}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 근태 현황 */}
|
||
{dayAttendance && (
|
||
<div className="p-2.5 md:p-3 bg-teal-50 dark:bg-teal-950/30 rounded-lg border border-teal-200/50 dark:border-teal-800/30">
|
||
<div className="flex justify-between items-center mb-2">
|
||
<div className="flex items-center space-x-2">
|
||
<UserCheck className="w-3.5 h-3.5 md:w-4 md:h-4 text-teal-600 dark:text-teal-400" />
|
||
<span className="font-medium text-sm md:text-base text-teal-800 dark:text-teal-200">근태 현황</span>
|
||
</div>
|
||
<Badge className={`${
|
||
dayAttendance.status === '정상' ? 'bg-green-500' : 'bg-yellow-500'
|
||
} text-white text-xs`}>
|
||
{dayAttendance.status}
|
||
</Badge>
|
||
</div>
|
||
<p className="text-sm text-teal-600">출근: {dayAttendance.employees}명</p>
|
||
<div className="flex space-x-2 mt-2">
|
||
<Button size="sm" variant="outline" className="text-xs">
|
||
<Plus className="h-3 w-3 mr-1" />
|
||
출근 추가
|
||
</Button>
|
||
<Button size="sm" variant="outline" className="text-xs">
|
||
<Minus className="h-3 w-3 mr-1" />
|
||
결근 처리
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 입고 현황 */}
|
||
{dayIncoming && calendarFilters.incoming && (
|
||
<div className="p-3 bg-blue-50 rounded-lg">
|
||
<div className="flex justify-between items-center mb-2">
|
||
<div className="flex items-center space-x-2">
|
||
<Package className="w-4 h-4 text-blue-600" />
|
||
<span className="font-medium text-blue-800">입고 현황</span>
|
||
</div>
|
||
<Badge className="bg-blue-500 text-white text-xs">
|
||
{Math.round(dayIncoming.value / 1000000)}M원
|
||
</Badge>
|
||
</div>
|
||
<div className="space-y-1">
|
||
{dayIncoming.items.map((item, index) => (
|
||
<div key={index} className="text-sm text-blue-600 p-1 bg-white rounded">
|
||
• {item}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 출고 현황 */}
|
||
{dayOutgoing && calendarFilters.outgoing && (
|
||
<div className="p-3 bg-green-50 rounded-lg">
|
||
<div className="flex justify-between items-center mb-2">
|
||
<div className="flex items-center space-x-2">
|
||
<Truck className="w-4 h-4 text-green-600" />
|
||
<span className="font-medium text-green-800">출고 현황</span>
|
||
</div>
|
||
<Badge className="bg-green-500 text-white text-xs">
|
||
{Math.round(dayOutgoing.value / 1000000)}M원
|
||
</Badge>
|
||
</div>
|
||
<p className="text-sm text-green-600 mb-2">고객: {dayOutgoing.customer}</p>
|
||
<div className="space-y-1">
|
||
{dayOutgoing.orders.map((order, index) => (
|
||
<div key={index} className="text-sm text-green-600 p-1 bg-white rounded">
|
||
• {order}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 자재관리 현황 */}
|
||
{dayMaterials && calendarFilters.materials && (
|
||
<div className="p-3 bg-orange-50 rounded-lg">
|
||
<div className="flex justify-between items-center mb-2">
|
||
<div className="flex items-center space-x-2">
|
||
<Package className="w-4 h-4 text-orange-600" />
|
||
<span className="font-medium text-orange-800">자재관리</span>
|
||
</div>
|
||
<Badge className={`text-xs ${
|
||
dayMaterials.status === '완료' ? 'bg-green-500' :
|
||
dayMaterials.status === '진행중' ? 'bg-blue-500' : 'bg-muted'
|
||
} ${dayMaterials.status === '완료' || dayMaterials.status === '진행중' ? 'text-white' : 'text-muted-foreground'}`}>
|
||
{dayMaterials.status}
|
||
</Badge>
|
||
</div>
|
||
<p className="text-sm text-orange-600 mb-1">작업: {dayMaterials.activity}</p>
|
||
<p className="text-sm text-orange-600">대상: {dayMaterials.items}품목</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* 이벤트가 없는 경우 */}
|
||
{!hasAnyEvent && (
|
||
<div className="p-4 text-center text-muted-foreground">
|
||
<CalendarIcon className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
||
<p className="text-sm">해당 날짜에 등록된 일정이 없습니다.</p>
|
||
<Button size="sm" className="mt-3" variant="outline">
|
||
<Plus className="h-3 w-3 mr-1" />
|
||
일정 추가
|
||
</Button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
})()}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 범례 */}
|
||
<div className="mt-6 p-4 bg-muted/50 dark:bg-muted/20 rounded-xl border border-border/20 dark:border-border/30">
|
||
<h4 className="font-semibold text-foreground mb-4 flex items-center space-x-2">
|
||
<Filter className="w-4 h-4 text-muted-foreground" />
|
||
<span>범례 및 안내</span>
|
||
</h4>
|
||
<div className="grid grid-cols-3 lg:grid-cols-7 gap-4 mb-4">
|
||
<div className="flex items-center space-x-2">
|
||
<div className="w-3 h-3 bg-blue-500 rounded-full shadow-sm"></div>
|
||
<span className="text-sm font-medium text-muted-foreground">입고</span>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<div className="w-3 h-3 bg-green-500 rounded-full shadow-sm"></div>
|
||
<span className="text-sm font-medium text-muted-foreground">출고</span>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<div className="w-3 h-3 bg-orange-500 rounded-full shadow-sm"></div>
|
||
<span className="text-sm font-medium text-muted-foreground">자재</span>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<div className="w-3 h-3 bg-purple-500 rounded-full shadow-sm"></div>
|
||
<span className="text-sm font-medium text-muted-foreground">일정</span>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<div className="w-3 h-3 bg-emerald-500 rounded-full shadow-sm"></div>
|
||
<span className="text-sm font-medium text-muted-foreground">휴가</span>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<div className="w-3 h-3 bg-indigo-500 rounded-full shadow-sm"></div>
|
||
<span className="text-sm font-medium text-muted-foreground">출장</span>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<div className="w-3 h-3 bg-teal-500 rounded-full text-white text-xs flex items-center justify-center shadow-sm">✓</div>
|
||
<span className="text-sm font-medium text-muted-foreground">근태</span>
|
||
</div>
|
||
</div>
|
||
<div className="pt-3 border-t border-border/30">
|
||
<div className="flex items-center justify-center space-x-6">
|
||
<div className="flex items-center space-x-2">
|
||
<Star className="w-4 h-4 text-yellow-500 fill-yellow-500" />
|
||
<span className="text-sm font-medium text-muted-foreground">오늘 날짜</span>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<div className="w-4 h-4 bg-primary/20 rounded border-2 border-primary"></div>
|
||
<span className="text-sm font-medium text-muted-foreground">선택된 날짜</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* TOP 5 고객별 매출 현황 */}
|
||
<Card className="border border-border/20 bg-white rounded-xl">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-3">
|
||
<div className="w-6 h-6 bg-blue-100 rounded-lg flex items-center justify-center">
|
||
<Building2 className="h-4 w-4 text-blue-600" />
|
||
</div>
|
||
<span>TOP 5 고객별 매출 현황</span>
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
{ceoData.topCustomers.map((customer, index) => (
|
||
<div key={index} className="flex items-center justify-between p-4 bg-muted/50 dark:bg-muted/20 rounded-lg">
|
||
<div className="flex items-center space-x-4">
|
||
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center text-white text-sm font-bold">
|
||
{customer.rank}
|
||
</div>
|
||
<div>
|
||
<p className="font-medium text-foreground">{customer.name}</p>
|
||
<p className="text-sm text-muted-foreground">{formatNumber(customer.amount)}원</p>
|
||
</div>
|
||
</div>
|
||
<div className="text-right">
|
||
<div className={`flex items-center space-x-1 text-sm font-medium ${
|
||
customer.growth > 0 ? 'text-green-600' : 'text-red-600'
|
||
}`}>
|
||
{customer.growth > 0 ? (
|
||
<ArrowUpRight className="h-3 w-3" />
|
||
) : (
|
||
<ArrowDownRight className="h-3 w-3" />
|
||
)}
|
||
<span>{Math.abs(customer.growth)}%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|