- API: fetch-wrapper/proxy/refresh-token 리팩토링, authenticated-fetch 신규 추가 - CEO 대시보드: EnhancedSections 현황판 기능 개선, dashboard transformers/types 확장 - 문서 시스템: ApprovalLine/DocumentHeader/DocumentToolbar/DocumentViewer 개선 - 작업지시서: 검사보고서/작업일지 문서 컴포넌트 개선 (벤딩/스크린/슬랫) - 레이아웃: Sidebar/AuthenticatedLayout 수정 - 작업자화면: WorkerScreen 수정 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
539 lines
23 KiB
TypeScript
539 lines
23 KiB
TypeScript
'use client';
|
|
|
|
import {
|
|
Wallet,
|
|
DollarSign,
|
|
TrendingUp,
|
|
TrendingDown,
|
|
AlertTriangle,
|
|
CheckCircle2,
|
|
Clock,
|
|
Users,
|
|
FileText,
|
|
ShoppingCart,
|
|
Building2,
|
|
Calendar,
|
|
CreditCard,
|
|
Receipt,
|
|
Briefcase,
|
|
AlertCircle,
|
|
ArrowUpRight,
|
|
ArrowDownRight,
|
|
Banknote,
|
|
CircleDollarSign,
|
|
} from 'lucide-react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import type { DailyReportData, MonthlyExpenseData, TodayIssueItem, TodayIssueSettings } from '../types';
|
|
|
|
// ============================================================
|
|
// 유틸리티 함수
|
|
// ============================================================
|
|
|
|
const formatBillion = (amount: number): string => {
|
|
const billion = amount / 100000000;
|
|
if (billion >= 1) {
|
|
return billion.toFixed(1) + '억원';
|
|
}
|
|
const man = amount / 10000;
|
|
if (man >= 1) {
|
|
return Math.floor(man).toLocaleString() + '만원';
|
|
}
|
|
return amount.toLocaleString() + '원';
|
|
};
|
|
|
|
const formatUSD = (amount: number): string => {
|
|
return '$ ' + new Intl.NumberFormat('en-US').format(amount);
|
|
};
|
|
|
|
// ============================================================
|
|
// 강화된 일일 일보 섹션
|
|
// ============================================================
|
|
|
|
interface EnhancedDailyReportSectionProps {
|
|
data: DailyReportData;
|
|
onClick?: () => void;
|
|
}
|
|
|
|
export function EnhancedDailyReportSection({ data, onClick }: EnhancedDailyReportSectionProps) {
|
|
return (
|
|
<div className="rounded-xl border overflow-hidden">
|
|
{/* 다크 헤더 - 인라인 스타일로 확실하게 적용 */}
|
|
<div
|
|
style={{ backgroundColor: '#1e293b' }}
|
|
className="px-6 py-4"
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div
|
|
style={{ backgroundColor: 'rgba(255,255,255,0.1)' }}
|
|
className="p-2 rounded-lg"
|
|
>
|
|
<FileText style={{ color: '#ffffff' }} className="h-5 w-5" />
|
|
</div>
|
|
<div>
|
|
<h3 style={{ color: '#ffffff' }} className="text-lg font-semibold">일일 일보</h3>
|
|
<p style={{ color: '#cbd5e1' }} className="text-sm">{data.date}</p>
|
|
</div>
|
|
</div>
|
|
<Badge
|
|
style={{ backgroundColor: '#10b981', color: '#ffffff', border: 'none' }}
|
|
className="hover:opacity-90"
|
|
>
|
|
실시간
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 카드 내용 - 흰색 배경 */}
|
|
<div style={{ backgroundColor: '#ffffff' }} className="p-6">
|
|
{/* 카드 그리드 */}
|
|
<div className="grid grid-cols-1 xs:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
|
{/* 카드 1: 현금성 자산 */}
|
|
<div
|
|
style={{ backgroundColor: '#ecfdf5', borderColor: '#a7f3d0' }}
|
|
className="rounded-xl p-4 cursor-pointer transition-all hover:shadow-lg border h-[110px] flex flex-col"
|
|
onClick={onClick}
|
|
>
|
|
<div className="flex items-center gap-2 mb-3">
|
|
<div style={{ backgroundColor: '#10b981' }} className="p-1.5 rounded-lg">
|
|
<Wallet style={{ color: '#ffffff' }} className="h-4 w-4" />
|
|
</div>
|
|
<span style={{ color: '#047857' }} className="text-sm font-medium">
|
|
{data.cards[0]?.label || '현금성 자산 합계'}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-end gap-2">
|
|
<span style={{ color: '#0f172a' }} className="text-2xl font-bold">
|
|
{formatBillion(data.cards[0]?.amount || 0)}
|
|
</span>
|
|
{data.cards[0]?.changeRate && (
|
|
<span
|
|
style={{ color: data.cards[0].changeDirection === 'up' ? '#ef4444' : '#3b82f6' }}
|
|
className="flex items-center text-xs font-medium mb-1"
|
|
>
|
|
{data.cards[0].changeDirection === 'up'
|
|
? <ArrowUpRight className="h-3 w-3" />
|
|
: <ArrowDownRight className="h-3 w-3" />}
|
|
{data.cards[0].changeRate}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 카드 2: 외국환 */}
|
|
<div
|
|
style={{ backgroundColor: '#eff6ff', borderColor: '#bfdbfe' }}
|
|
className="rounded-xl p-4 cursor-pointer transition-all hover:shadow-lg border h-[110px] flex flex-col"
|
|
onClick={onClick}
|
|
>
|
|
<div className="flex items-center gap-2 mb-3">
|
|
<div style={{ backgroundColor: '#3b82f6' }} className="p-1.5 rounded-lg">
|
|
<DollarSign style={{ color: '#ffffff' }} className="h-4 w-4" />
|
|
</div>
|
|
<span style={{ color: '#1d4ed8' }} className="text-sm font-medium">
|
|
{data.cards[1]?.label || '외국환(USD) 합계'}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-end gap-2">
|
|
<span style={{ color: '#0f172a' }} className="text-2xl font-bold">
|
|
{data.cards[1]?.currency === 'USD'
|
|
? formatUSD(data.cards[1]?.amount || 0)
|
|
: formatBillion(data.cards[1]?.amount || 0)}
|
|
</span>
|
|
{data.cards[1]?.changeRate && (
|
|
<span
|
|
style={{ color: data.cards[1].changeDirection === 'up' ? '#ef4444' : '#3b82f6' }}
|
|
className="flex items-center text-xs font-medium mb-1"
|
|
>
|
|
{data.cards[1].changeDirection === 'up'
|
|
? <ArrowUpRight className="h-3 w-3" />
|
|
: <ArrowDownRight className="h-3 w-3" />}
|
|
{data.cards[1].changeRate}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 카드 3: 입금 */}
|
|
<div
|
|
style={{ backgroundColor: '#f0fdf4', borderColor: '#bbf7d0' }}
|
|
className="rounded-xl p-4 cursor-pointer transition-all hover:shadow-lg border h-[110px] flex flex-col"
|
|
onClick={onClick}
|
|
>
|
|
<div className="flex items-center gap-2 mb-3">
|
|
<div style={{ backgroundColor: '#22c55e' }} className="p-1.5 rounded-lg">
|
|
<TrendingUp style={{ color: '#ffffff' }} className="h-4 w-4" />
|
|
</div>
|
|
<span style={{ color: '#15803d' }} className="text-sm font-medium">
|
|
{data.cards[2]?.label || '입금 합계'}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-end gap-2">
|
|
<span style={{ color: '#0f172a' }} className="text-2xl font-bold">
|
|
{formatBillion(data.cards[2]?.amount || 0)}
|
|
</span>
|
|
{data.cards[2]?.changeRate && (
|
|
<span
|
|
style={{ color: data.cards[2].changeDirection === 'up' ? '#ef4444' : '#3b82f6' }}
|
|
className="flex items-center text-xs font-medium mb-1"
|
|
>
|
|
{data.cards[2].changeDirection === 'up'
|
|
? <ArrowUpRight className="h-3 w-3" />
|
|
: <ArrowDownRight className="h-3 w-3" />}
|
|
{data.cards[2].changeRate}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 카드 4: 출금 */}
|
|
<div
|
|
style={{ backgroundColor: '#fff1f2', borderColor: '#fecdd3' }}
|
|
className="rounded-xl p-4 cursor-pointer transition-all hover:shadow-lg border h-[110px] flex flex-col"
|
|
onClick={onClick}
|
|
>
|
|
<div className="flex items-center gap-2 mb-3">
|
|
<div style={{ backgroundColor: '#f43f5e' }} className="p-1.5 rounded-lg">
|
|
<TrendingDown style={{ color: '#ffffff' }} className="h-4 w-4" />
|
|
</div>
|
|
<span style={{ color: '#be123c' }} className="text-sm font-medium">
|
|
{data.cards[3]?.label || '출금 합계'}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-end gap-2">
|
|
<span style={{ color: '#0f172a' }} className="text-2xl font-bold">
|
|
{formatBillion(data.cards[3]?.amount || 0)}
|
|
</span>
|
|
{data.cards[3]?.changeRate && (
|
|
<span
|
|
style={{ color: data.cards[3].changeDirection === 'up' ? '#ef4444' : '#3b82f6' }}
|
|
className="flex items-center text-xs font-medium mb-1"
|
|
>
|
|
{data.cards[3].changeDirection === 'up'
|
|
? <ArrowUpRight className="h-3 w-3" />
|
|
: <ArrowDownRight className="h-3 w-3" />}
|
|
{data.cards[3].changeRate}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 체크포인트 */}
|
|
{data.checkPoints.length > 0 && (
|
|
<div className="space-y-2">
|
|
<p style={{ color: '#64748b' }} className="text-xs font-medium uppercase tracking-wider mb-3">주요 알림</p>
|
|
{data.checkPoints.map((cp, idx) => (
|
|
<div
|
|
key={cp.id}
|
|
style={{
|
|
backgroundColor: idx === 0 ? '#fffbeb' : '#f8fafc',
|
|
borderColor: idx === 0 ? '#fde68a' : '#e2e8f0'
|
|
}}
|
|
className="flex items-start gap-3 p-3 rounded-lg border"
|
|
>
|
|
<div
|
|
style={{ backgroundColor: idx === 0 ? '#fef3c7' : '#f1f5f9' }}
|
|
className="p-1 rounded-full shrink-0"
|
|
>
|
|
{idx === 0 ? (
|
|
<AlertTriangle style={{ color: '#d97706' }} className="h-4 w-4" />
|
|
) : (
|
|
<CheckCircle2 style={{ color: '#16a34a' }} className="h-4 w-4" />
|
|
)}
|
|
</div>
|
|
<p style={{ color: '#475569' }} className="text-sm flex-1">{cp.message}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 강화된 현황판 섹션
|
|
// ============================================================
|
|
|
|
// 라벨 → 설정키 매핑
|
|
const LABEL_TO_SETTING_KEY: Record<string, keyof TodayIssueSettings> = {
|
|
'수주': 'orders',
|
|
'채권 추심': 'debtCollection',
|
|
'안전 재고': 'safetyStock',
|
|
'세금 신고': 'taxReport',
|
|
'신규 업체 등록': 'newVendor',
|
|
'연차': 'annualLeave',
|
|
'지각': 'lateness',
|
|
'결근': 'absence',
|
|
'발주': 'purchase',
|
|
'결재 요청': 'approvalRequest',
|
|
};
|
|
|
|
// 라벨별 스타일 매핑 (인라인 스타일용)
|
|
const ITEM_STYLES: Record<string, { bg: string; border: string; iconBg: string; labelColor: string; Icon: React.ComponentType<{ style?: React.CSSProperties; className?: string }> }> = {
|
|
'수주': { bg: '#eff6ff', border: '#bfdbfe', iconBg: '#3b82f6', labelColor: '#1d4ed8', Icon: ShoppingCart },
|
|
'채권 추심': { bg: '#fef2f2', border: '#fecaca', iconBg: '#ef4444', labelColor: '#dc2626', Icon: AlertCircle },
|
|
'안전 재고': { bg: '#fff7ed', border: '#fed7aa', iconBg: '#f97316', labelColor: '#ea580c', Icon: Receipt },
|
|
'세금 신고': { bg: '#faf5ff', border: '#e9d5ff', iconBg: '#a855f7', labelColor: '#9333ea', Icon: FileText },
|
|
'신규 업체 등록': { bg: '#ecfdf5', border: '#a7f3d0', iconBg: '#10b981', labelColor: '#059669', Icon: Building2 },
|
|
'연차': { bg: '#ecfeff', border: '#a5f3fc', iconBg: '#06b6d4', labelColor: '#0891b2', Icon: Calendar },
|
|
'지각': { bg: '#fffbeb', border: '#fde68a', iconBg: '#f59e0b', labelColor: '#d97706', Icon: Clock },
|
|
'결근': { bg: '#fff1f2', border: '#fecdd3', iconBg: '#f43f5e', labelColor: '#e11d48', Icon: Users },
|
|
'발주': { bg: '#eef2ff', border: '#c7d2fe', iconBg: '#6366f1', labelColor: '#4f46e5', Icon: Briefcase },
|
|
'결재 요청': { bg: '#fdf2f8', border: '#fbcfe8', iconBg: '#ec4899', labelColor: '#db2777', Icon: CheckCircle2 },
|
|
};
|
|
|
|
const DEFAULT_STYLE = { bg: '#f8fafc', border: '#e2e8f0', iconBg: '#64748b', labelColor: '#475569', Icon: FileText };
|
|
|
|
interface EnhancedStatusBoardSectionProps {
|
|
items: TodayIssueItem[];
|
|
itemSettings?: TodayIssueSettings;
|
|
}
|
|
|
|
export function EnhancedStatusBoardSection({ items, itemSettings }: EnhancedStatusBoardSectionProps) {
|
|
const router = useRouter();
|
|
|
|
const handleItemClick = (path: string) => {
|
|
router.push(path);
|
|
};
|
|
|
|
// 설정에 따라 항목 필터링
|
|
const filteredItems = itemSettings
|
|
? items.filter((item) => {
|
|
const settingKey = LABEL_TO_SETTING_KEY[item.label];
|
|
return settingKey ? itemSettings[settingKey] : true;
|
|
})
|
|
: items;
|
|
|
|
return (
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
{/* 헤더 */}
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<div style={{ backgroundColor: '#f59e0b' }} className="w-1.5 h-6 rounded-full" />
|
|
<h3 style={{ color: '#0f172a' }} className="text-lg font-semibold">현황판</h3>
|
|
<Badge
|
|
style={{ backgroundColor: '#fef3c7', color: '#b45309', borderColor: '#fde68a' }}
|
|
>
|
|
{filteredItems.length}개 항목
|
|
</Badge>
|
|
</div>
|
|
|
|
{/* 카드 그리드 */}
|
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3">
|
|
{filteredItems.map((item) => {
|
|
const isHighlighted = item.isHighlighted;
|
|
const style = ITEM_STYLES[item.label] || DEFAULT_STYLE;
|
|
const Icon = style.Icon;
|
|
|
|
// 긴급 항목은 빨간 배경
|
|
const bgColor = isHighlighted ? '#ef4444' : style.bg;
|
|
const borderColor = isHighlighted ? '#ef4444' : style.border;
|
|
const iconBgColor = isHighlighted ? 'rgba(255,255,255,0.2)' : style.iconBg;
|
|
const labelColor = isHighlighted ? '#ffffff' : style.labelColor;
|
|
const countColor = isHighlighted ? '#ffffff' : '#0f172a';
|
|
const subColor = isHighlighted ? '#fecaca' : '#64748b';
|
|
|
|
return (
|
|
<div
|
|
key={item.id}
|
|
style={{ backgroundColor: bgColor, borderColor: borderColor }}
|
|
className="relative p-4 rounded-xl border cursor-pointer transition-all hover:scale-[1.02] hover:shadow-md h-[110px] flex flex-col"
|
|
onClick={() => handleItemClick(item.path)}
|
|
>
|
|
{/* 아이콘 + 라벨 */}
|
|
<div className="flex items-center gap-2 mb-3 min-w-0">
|
|
<div style={{ backgroundColor: iconBgColor }} className="p-1.5 rounded-lg shrink-0">
|
|
<Icon style={{ color: '#ffffff' }} className="h-4 w-4" />
|
|
</div>
|
|
<span style={{ color: labelColor }} className="text-sm font-medium truncate flex-1 min-w-0">
|
|
{item.label}
|
|
</span>
|
|
</div>
|
|
|
|
{/* 숫자 */}
|
|
<div style={{ color: countColor }} className="text-2xl font-bold">
|
|
{typeof item.count === 'number' ? `${item.count}건` : item.count}
|
|
</div>
|
|
|
|
{/* 부가 정보 */}
|
|
{item.subLabel && (
|
|
<span style={{ color: subColor }} className="text-xs mt-auto">
|
|
{item.subLabel}
|
|
</span>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 강화된 당월 예상 지출 섹션
|
|
// ============================================================
|
|
|
|
interface EnhancedMonthlyExpenseSectionProps {
|
|
data: MonthlyExpenseData;
|
|
onCardClick?: (cardId: string) => void;
|
|
}
|
|
|
|
export function EnhancedMonthlyExpenseSection({ data, onCardClick }: EnhancedMonthlyExpenseSectionProps) {
|
|
// 총 예상 지출 계산 (API에서 문자열로 올 수 있으므로 Number로 변환)
|
|
const totalAmount = data.cards.reduce((sum, card) => sum + (Number(card?.amount) || 0), 0);
|
|
|
|
return (
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
{/* 헤더 */}
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<div style={{ backgroundColor: '#f97316' }} className="w-1.5 h-6 rounded-full" />
|
|
<h3 style={{ color: '#0f172a' }} className="text-lg font-semibold">당월 예상 지출 내역</h3>
|
|
<Badge
|
|
style={{ backgroundColor: '#ffedd5', color: '#c2410c', borderColor: '#fed7aa' }}
|
|
>
|
|
전월 대비 +15%
|
|
</Badge>
|
|
</div>
|
|
|
|
{/* 카드 그리드 */}
|
|
<div className="grid grid-cols-1 xs:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
|
{/* 카드 1: 매입 */}
|
|
<div
|
|
style={{ backgroundColor: '#f5f3ff', borderColor: '#ddd6fe' }}
|
|
className="rounded-xl p-4 cursor-pointer transition-all hover:scale-[1.02] hover:shadow-lg border h-[130px] flex flex-col"
|
|
onClick={() => onCardClick?.(data.cards[0]?.id || 'me1')}
|
|
>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<div style={{ backgroundColor: '#8b5cf6' }} className="p-1.5 rounded-lg">
|
|
<Receipt style={{ color: '#ffffff' }} className="h-4 w-4" />
|
|
</div>
|
|
<span style={{ color: '#6d28d9' }} className="text-sm font-medium">
|
|
{data.cards[0]?.label || '매입'}
|
|
</span>
|
|
</div>
|
|
<div style={{ color: '#0f172a' }} className="text-2xl font-bold">
|
|
{formatBillion(data.cards[0]?.amount || 0)}
|
|
</div>
|
|
{data.cards[0]?.previousLabel && (
|
|
<div style={{ backgroundColor: '#dcfce7', color: '#16a34a' }} className="flex items-center gap-1 px-2 py-1 rounded-full text-xs font-semibold mt-auto w-fit">
|
|
<TrendingUp className="h-3 w-3" />
|
|
{data.cards[0].previousLabel}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 카드 2: 카드 */}
|
|
<div
|
|
style={{ backgroundColor: '#eff6ff', borderColor: '#bfdbfe' }}
|
|
className="rounded-xl p-4 cursor-pointer transition-all hover:scale-[1.02] hover:shadow-lg border h-[130px] flex flex-col"
|
|
onClick={() => onCardClick?.(data.cards[1]?.id || 'me2')}
|
|
>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<div style={{ backgroundColor: '#3b82f6' }} className="p-1.5 rounded-lg">
|
|
<CreditCard style={{ color: '#ffffff' }} className="h-4 w-4" />
|
|
</div>
|
|
<span style={{ color: '#1d4ed8' }} className="text-sm font-medium">
|
|
{data.cards[1]?.label || '카드'}
|
|
</span>
|
|
</div>
|
|
<div style={{ color: '#0f172a' }} className="text-2xl font-bold">
|
|
{formatBillion(data.cards[1]?.amount || 0)}
|
|
</div>
|
|
{data.cards[1]?.previousLabel && (
|
|
<div style={{ backgroundColor: '#dcfce7', color: '#16a34a' }} className="flex items-center gap-1 px-2 py-1 rounded-full text-xs font-semibold mt-auto w-fit">
|
|
<TrendingUp className="h-3 w-3" />
|
|
{data.cards[1].previousLabel}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 카드 3: 발행어음 */}
|
|
<div
|
|
style={{ backgroundColor: '#fffbeb', borderColor: '#fde68a' }}
|
|
className="rounded-xl p-4 cursor-pointer transition-all hover:scale-[1.02] hover:shadow-lg border h-[130px] flex flex-col"
|
|
onClick={() => onCardClick?.(data.cards[2]?.id || 'me3')}
|
|
>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<div style={{ backgroundColor: '#f59e0b' }} className="p-1.5 rounded-lg">
|
|
<Banknote style={{ color: '#ffffff' }} className="h-4 w-4" />
|
|
</div>
|
|
<span style={{ color: '#b45309' }} className="text-sm font-medium">
|
|
{data.cards[2]?.label || '발행어음'}
|
|
</span>
|
|
</div>
|
|
<div style={{ color: '#0f172a' }} className="text-2xl font-bold">
|
|
{formatBillion(data.cards[2]?.amount || 0)}
|
|
</div>
|
|
{data.cards[2]?.previousLabel && (
|
|
<div style={{ backgroundColor: '#dcfce7', color: '#16a34a' }} className="flex items-center gap-1 px-2 py-1 rounded-full text-xs font-semibold mt-auto w-fit">
|
|
<TrendingUp className="h-3 w-3" />
|
|
{data.cards[2].previousLabel}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 카드 4: 총 예상 지출 합계 (강조 - 인라인 스타일) */}
|
|
<div
|
|
style={{ backgroundColor: '#f43f5e', borderColor: '#f43f5e' }}
|
|
className="rounded-xl p-4 cursor-pointer transition-all hover:scale-[1.02] hover:shadow-lg border h-[130px] flex flex-col"
|
|
onClick={() => onCardClick?.(data.cards[3]?.id || 'me4')}
|
|
>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<div style={{ backgroundColor: 'rgba(255,255,255,0.2)' }} className="p-1.5 rounded-lg">
|
|
<CircleDollarSign style={{ color: '#ffffff' }} className="h-4 w-4" />
|
|
</div>
|
|
<span style={{ color: '#ffe4e6' }} className="text-sm font-medium">
|
|
총 예상 지출 합계
|
|
</span>
|
|
</div>
|
|
<div style={{ color: '#ffffff' }} className="text-2xl font-bold">
|
|
{formatBillion(totalAmount)}
|
|
</div>
|
|
<div style={{ backgroundColor: 'rgba(255,255,255,0.2)', color: '#ffffff' }} className="flex items-center gap-1 px-2 py-1 rounded-full text-xs font-semibold mt-auto w-fit">
|
|
<TrendingUp className="h-3 w-3" />
|
|
전월 대비 +10.5%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 체크포인트 */}
|
|
{data.checkPoints.length > 0 && (
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
{data.checkPoints.map((cp, idx) => {
|
|
const colors = [
|
|
{ bg: '#fef2f2', border: '#fecaca', iconColor: '#ef4444' },
|
|
{ bg: '#fffbeb', border: '#fde68a', iconColor: '#f59e0b' },
|
|
{ bg: '#f0fdf4', border: '#bbf7d0', iconColor: '#22c55e' },
|
|
];
|
|
const color = colors[idx] || colors[2];
|
|
|
|
return (
|
|
<div
|
|
key={cp.id}
|
|
style={{ backgroundColor: color.bg, borderColor: color.border }}
|
|
className="p-3 rounded-lg border flex items-start gap-2"
|
|
>
|
|
{idx === 0 ? (
|
|
<AlertTriangle style={{ color: color.iconColor }} className="h-4 w-4 mt-0.5 shrink-0" />
|
|
) : idx === 1 ? (
|
|
<AlertCircle style={{ color: color.iconColor }} className="h-4 w-4 mt-0.5 shrink-0" />
|
|
) : (
|
|
<CheckCircle2 style={{ color: color.iconColor }} className="h-4 w-4 mt-0.5 shrink-0" />
|
|
)}
|
|
<p style={{ color: '#475569' }} className="text-sm">{cp.message}</p>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|