Files
sam-react-prod/src/components/business/CEODashboard/sections/PurchaseStatusSection.tsx

269 lines
11 KiB
TypeScript
Raw Normal View History

'use client';
import { useState } from 'react';
import {
TrendingDown,
ArrowDownRight,
ArrowUpRight,
ShoppingCart,
DollarSign,
AlertCircle,
} from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { MultiSelectCombobox } from '@/components/ui/multi-select-combobox';
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
PieChart,
Pie,
Cell,
Legend,
} from 'recharts';
import { formatKoreanAmount } from '@/lib/utils/amount';
import type { PurchaseStatusData } from '../types';
interface PurchaseStatusSectionProps {
data: PurchaseStatusData;
showDailyDetail?: boolean;
}
const formatAmount = (value: number) => {
if (value >= 100000000) return `${(value / 100000000).toFixed(1)}`;
if (value >= 10000) return `${(value / 10000).toFixed(0)}`;
return value.toLocaleString();
};
export function PurchaseStatusSection({ data, showDailyDetail = true }: PurchaseStatusSectionProps) {
const [supplierFilter, setSupplierFilter] = useState<string[]>([]);
const [sortOrder, setSortOrder] = useState('date-desc');
const filteredItems = data.dailyItems
.filter((item) => supplierFilter.length === 0 || supplierFilter.includes(item.supplier))
.sort((a, b) => {
if (sortOrder === 'date-desc') return b.date.localeCompare(a.date);
if (sortOrder === 'date-asc') return a.date.localeCompare(b.date);
if (sortOrder === 'amount-desc') return b.amount - a.amount;
return a.amount - b.amount;
});
const suppliers = [...new Set(data.dailyItems.map((item) => item.supplier))];
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">
<ShoppingCart 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"> </p>
</div>
</div>
<Badge
style={{ backgroundColor: '#f59e0b', color: '#ffffff', border: 'none' }}
className="hover:opacity-90"
>
</Badge>
</div>
</div>
<div style={{ backgroundColor: '#ffffff' }} className="p-6">
{/* 통계카드 3개 */}
<div className="grid grid-cols-1 xs:grid-cols-3 gap-4 mb-6">
{/* 누적 매입 */}
<div
style={{ backgroundColor: '#fff7ed', borderColor: '#fed7aa' }}
className="rounded-xl p-4 border"
>
<div className="flex items-center gap-2 mb-2">
<div style={{ backgroundColor: '#f59e0b' }} className="p-1.5 rounded-lg">
<DollarSign style={{ color: '#ffffff' }} className="h-4 w-4" />
</div>
<span style={{ color: '#b45309' }} className="text-sm font-medium"> </span>
</div>
<span style={{ color: '#0f172a' }} className="text-xl font-bold">
{formatKoreanAmount(data.cumulativePurchase)}
</span>
</div>
{/* 미결제 금액 */}
<div
style={{ backgroundColor: '#fef2f2', borderColor: '#fecaca' }}
className="rounded-xl p-4 border"
>
<div className="flex items-center gap-2 mb-2">
<div style={{ backgroundColor: '#ef4444' }} className="p-1.5 rounded-lg">
<AlertCircle style={{ color: '#ffffff' }} className="h-4 w-4" />
</div>
<span style={{ color: '#dc2626' }} className="text-sm font-medium"> </span>
</div>
<span style={{ color: '#0f172a' }} className="text-xl font-bold">
{formatKoreanAmount(data.unpaidAmount)}
</span>
</div>
{/* 전년 동기 대비 */}
<div
style={{
backgroundColor: data.yoyChange >= 0 ? '#fef2f2' : '#eff6ff',
borderColor: data.yoyChange >= 0 ? '#fecaca' : '#bfdbfe',
}}
className="rounded-xl p-4 border"
>
<div className="flex items-center gap-2 mb-2">
<div style={{ backgroundColor: data.yoyChange >= 0 ? '#ef4444' : '#3b82f6' }} className="p-1.5 rounded-lg">
<TrendingDown style={{ color: '#ffffff' }} className="h-4 w-4" />
</div>
<span style={{ color: data.yoyChange >= 0 ? '#dc2626' : '#1d4ed8' }} className="text-sm font-medium"> </span>
</div>
<div className="flex items-center gap-1">
<span style={{ color: '#0f172a' }} className="text-xl font-bold">
{data.yoyChange >= 0 ? '+' : ''}{data.yoyChange}%
</span>
{data.yoyChange >= 0
? <ArrowUpRight className="h-4 w-4 text-red-500" />
: <ArrowDownRight className="h-4 w-4 text-blue-500" />}
</div>
</div>
</div>
{/* 차트 2열 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
{/* 월별 매입 추이 */}
<div className="border rounded-lg p-4">
<h4 className="text-sm font-semibold text-gray-700 mb-3"> </h4>
<ResponsiveContainer width="100%" height={200}>
<BarChart data={data.monthlyTrend}>
<CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9" />
<XAxis dataKey="month" tick={{ fontSize: 12 }} />
<YAxis tickFormatter={formatAmount} tick={{ fontSize: 11 }} />
<Tooltip
formatter={(value: number | undefined) => [formatKoreanAmount(value ?? 0), '매입']}
/>
<Bar dataKey="amount" fill="#f59e0b" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
{/* 자재 유형별 비율 (Donut) */}
<div className="border rounded-lg p-4">
<h4 className="text-sm font-semibold text-gray-700 mb-3"> </h4>
<ResponsiveContainer width="100%" height={200}>
<PieChart>
<Pie
data={data.materialRatio.map((r) => ({ name: r.name, value: r.value, percentage: r.percentage, color: r.color }) as Record<string, unknown>)}
cx="50%"
cy="50%"
innerRadius={50}
outerRadius={80}
dataKey="value"
nameKey="name"
label={({ name, payload }) => `${name} ${(payload as { percentage?: number })?.percentage ?? 0}%`}
>
{data.materialRatio.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
</div>
{/* 당월 매입 내역 테이블 */}
{showDailyDetail && (
<div className="border rounded-lg overflow-hidden">
<div className="flex items-center justify-between p-4 bg-gray-50 border-b">
<h4 className="text-sm font-semibold text-gray-700"> </h4>
<div className="flex gap-2">
<MultiSelectCombobox
options={suppliers.map((s) => ({ value: s, label: s }))}
value={supplierFilter}
onChange={setSupplierFilter}
placeholder="전체 공급처"
className="w-[160px] h-8 text-xs"
/>
<Select value={sortOrder} onValueChange={setSortOrder}>
<SelectTrigger className="w-[120px] h-8 text-xs">
<SelectValue placeholder="정렬" />
</SelectTrigger>
<SelectContent>
<SelectItem value="date-desc"></SelectItem>
<SelectItem value="date-asc"></SelectItem>
<SelectItem value="amount-desc"> </SelectItem>
<SelectItem value="amount-asc"> </SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-gray-50 border-b">
<th className="px-4 py-2 text-left text-gray-600 font-medium"></th>
<th className="px-4 py-2 text-left text-gray-600 font-medium"></th>
<th className="px-4 py-2 text-left text-gray-600 font-medium"></th>
<th className="px-4 py-2 text-right text-gray-600 font-medium"></th>
<th className="px-4 py-2 text-center text-gray-600 font-medium"></th>
</tr>
</thead>
<tbody>
{filteredItems.map((item, idx) => (
<tr key={idx} className="border-b last:border-b-0 hover:bg-gray-50">
<td className="px-4 py-2 text-gray-700">{item.date}</td>
<td className="px-4 py-2 text-gray-700">{item.supplier}</td>
<td className="px-4 py-2 text-gray-700">{item.item}</td>
<td className="px-4 py-2 text-right text-gray-900 font-medium">
{item.amount.toLocaleString()}
</td>
<td className="px-4 py-2 text-center">
<Badge
variant="outline"
className={
item.status === '결제완료'
? 'text-green-600 border-green-200 bg-green-50'
: item.status === '미결제'
? 'text-red-600 border-red-200 bg-red-50'
: 'text-orange-600 border-orange-200 bg-orange-50'
}
>
{item.status}
</Badge>
</td>
</tr>
))}
</tbody>
<tfoot>
<tr className="bg-gray-100 font-semibold">
<td className="px-4 py-2 text-gray-700" colSpan={3}></td>
<td className="px-4 py-2 text-right text-gray-900">
{filteredItems.reduce((sum, item) => sum + item.amount, 0).toLocaleString()}
</td>
<td />
</tr>
</tfoot>
</table>
</div>
</div>
)}
</div>
</div>
);
}