446 lines
18 KiB
TypeScript
446 lines
18 KiB
TypeScript
|
|
import { useState } from "react";
|
|||
|
|
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
|
|||
|
|
import { Button } from "./ui/button";
|
|||
|
|
import { Badge } from "./ui/badge";
|
|||
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
|
|||
|
|
import {
|
|||
|
|
DollarSign,
|
|||
|
|
TrendingUp,
|
|||
|
|
TrendingDown,
|
|||
|
|
CreditCard,
|
|||
|
|
Banknote,
|
|||
|
|
PieChart,
|
|||
|
|
Calculator,
|
|||
|
|
FileText,
|
|||
|
|
AlertCircle,
|
|||
|
|
CheckCircle,
|
|||
|
|
ArrowUpRight,
|
|||
|
|
ArrowDownRight,
|
|||
|
|
Building2,
|
|||
|
|
Calendar
|
|||
|
|
} from "lucide-react";
|
|||
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./ui/table";
|
|||
|
|
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line, PieChart as RechartsPieChart, Cell, Pie } from "recharts";
|
|||
|
|
|
|||
|
|
export function AccountingManagement() {
|
|||
|
|
const [selectedPeriod, setSelectedPeriod] = useState("month");
|
|||
|
|
|
|||
|
|
// 매출/매입 데이터
|
|||
|
|
const salesPurchaseData = [
|
|||
|
|
{ month: "1월", sales: 450, purchase: 280, profit: 170 },
|
|||
|
|
{ month: "2월", sales: 520, purchase: 310, profit: 210 },
|
|||
|
|
{ month: "3월", sales: 480, purchase: 295, profit: 185 },
|
|||
|
|
{ month: "4월", sales: 610, purchase: 350, profit: 260 },
|
|||
|
|
{ month: "5월", sales: 580, purchase: 340, profit: 240 },
|
|||
|
|
{ month: "6월", sales: 650, purchase: 380, profit: 270 }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 거래처별 미수금
|
|||
|
|
const receivables = [
|
|||
|
|
{ company: "삼성전자", amount: 45000000, days: 45, status: "위험" },
|
|||
|
|
{ company: "LG전자", amount: 32000000, days: 28, status: "주의" },
|
|||
|
|
{ company: "현대자동차", amount: 28000000, days: 15, status: "정상" },
|
|||
|
|
{ company: "SK하이닉스", amount: 25000000, days: 52, status: "위험" },
|
|||
|
|
{ company: "네이버", amount: 18000000, days: 22, status: "정상" }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 건별 원가 분석
|
|||
|
|
const costAnalysis = [
|
|||
|
|
{
|
|||
|
|
orderNo: "ORD-2024-001",
|
|||
|
|
product: "방화셔터 3000×3000",
|
|||
|
|
salesAmount: 15000000,
|
|||
|
|
materialCost: 6500000,
|
|||
|
|
laborCost: 3500000,
|
|||
|
|
overheadCost: 2000000,
|
|||
|
|
totalCost: 12000000,
|
|||
|
|
profit: 3000000,
|
|||
|
|
profitRate: 20.0
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
orderNo: "ORD-2024-002",
|
|||
|
|
product: "일반셔터 2500×2500",
|
|||
|
|
salesAmount: 8500000,
|
|||
|
|
materialCost: 3200000,
|
|||
|
|
laborCost: 2100000,
|
|||
|
|
overheadCost: 1200000,
|
|||
|
|
totalCost: 6500000,
|
|||
|
|
profit: 2000000,
|
|||
|
|
profitRate: 23.5
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
orderNo: "ORD-2024-003",
|
|||
|
|
product: "특수셔터 4000×3500",
|
|||
|
|
salesAmount: 22000000,
|
|||
|
|
materialCost: 9500000,
|
|||
|
|
laborCost: 5200000,
|
|||
|
|
overheadCost: 2800000,
|
|||
|
|
totalCost: 17500000,
|
|||
|
|
profit: 4500000,
|
|||
|
|
profitRate: 20.5
|
|||
|
|
}
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 원가 구성 비율
|
|||
|
|
const costComposition = [
|
|||
|
|
{ name: "자재비", value: 54, color: "#3B82F6" },
|
|||
|
|
{ name: "인건비", value: 29, color: "#10B981" },
|
|||
|
|
{ name: "경비", value: 17, color: "#F59E0B" }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="p-4 md:p-6 space-y-6">
|
|||
|
|
{/* 헤더 */}
|
|||
|
|
<div className="bg-card border border-border/20 rounded-xl p-6">
|
|||
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
|||
|
|
<div>
|
|||
|
|
<h1 className="text-3xl font-bold text-foreground mb-2">회계 관리</h1>
|
|||
|
|
<p className="text-muted-foreground">매출/매입, 미수금, 원가 분석 및 손익 현황</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex space-x-2">
|
|||
|
|
<Button variant="outline">
|
|||
|
|
<FileText className="h-4 w-4 mr-2" />
|
|||
|
|
월마감
|
|||
|
|
</Button>
|
|||
|
|
<Button className="bg-primary">
|
|||
|
|
<Calculator className="h-4 w-4 mr-2" />
|
|||
|
|
재무제표
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 주요 지표 */}
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|||
|
|
<Card className="border border-border/20">
|
|||
|
|
<CardHeader className="pb-3">
|
|||
|
|
<CardTitle className="text-sm text-muted-foreground flex items-center justify-between">
|
|||
|
|
<span>당월 매출</span>
|
|||
|
|
<DollarSign className="h-4 w-4 text-green-600" />
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="text-2xl font-bold text-foreground mb-2">6,500만원</div>
|
|||
|
|
<div className="flex items-center space-x-1 text-sm text-green-600">
|
|||
|
|
<ArrowUpRight className="h-3 w-3" />
|
|||
|
|
<span>전월 대비 +12.1%</span>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card className="border border-border/20">
|
|||
|
|
<CardHeader className="pb-3">
|
|||
|
|
<CardTitle className="text-sm text-muted-foreground flex items-center justify-between">
|
|||
|
|
<span>당월 매입</span>
|
|||
|
|
<CreditCard className="h-4 w-4 text-orange-600" />
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="text-2xl font-bold text-foreground mb-2">3,800만원</div>
|
|||
|
|
<div className="flex items-center space-x-1 text-sm text-orange-600">
|
|||
|
|
<ArrowUpRight className="h-3 w-3" />
|
|||
|
|
<span>전월 대비 +11.8%</span>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card className="border border-border/20">
|
|||
|
|
<CardHeader className="pb-3">
|
|||
|
|
<CardTitle className="text-sm text-muted-foreground flex items-center justify-between">
|
|||
|
|
<span>당월 순이익</span>
|
|||
|
|
<TrendingUp className="h-4 w-4 text-blue-600" />
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="text-2xl font-bold text-foreground mb-2">2,700만원</div>
|
|||
|
|
<div className="flex items-center space-x-1 text-sm text-blue-600">
|
|||
|
|
<span>이익률: 41.5%</span>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card className="border border-border/20">
|
|||
|
|
<CardHeader className="pb-3">
|
|||
|
|
<CardTitle className="text-sm text-muted-foreground flex items-center justify-between">
|
|||
|
|
<span>총 미수금</span>
|
|||
|
|
<AlertCircle className="h-4 w-4 text-red-600" />
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="text-2xl font-bold text-foreground mb-2">1,480만원</div>
|
|||
|
|
<div className="flex items-center space-x-1 text-sm text-red-600">
|
|||
|
|
<span>30일 초과: 700만원</span>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 탭 메뉴 */}
|
|||
|
|
<Tabs defaultValue="sales" className="space-y-4">
|
|||
|
|
<TabsList className="grid w-full grid-cols-5">
|
|||
|
|
<TabsTrigger value="sales">매출/매입</TabsTrigger>
|
|||
|
|
<TabsTrigger value="receivables">미수금 관리</TabsTrigger>
|
|||
|
|
<TabsTrigger value="cost">원가 분석</TabsTrigger>
|
|||
|
|
<TabsTrigger value="profit">손익 현황</TabsTrigger>
|
|||
|
|
<TabsTrigger value="transactions">입출금</TabsTrigger>
|
|||
|
|
</TabsList>
|
|||
|
|
|
|||
|
|
{/* 매출/매입 관리 */}
|
|||
|
|
<TabsContent value="sales" className="space-y-4">
|
|||
|
|
<Card className="border border-border/20">
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle className="flex items-center space-x-3">
|
|||
|
|
<TrendingUp className="h-6 w-6 text-primary" />
|
|||
|
|
<span>매출/매입 추이</span>
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="h-80 min-h-[20rem]">
|
|||
|
|
<ResponsiveContainer width="100%" height="100%" minHeight={320}>
|
|||
|
|
<LineChart data={salesPurchaseData}>
|
|||
|
|
<CartesianGrid strokeDasharray="3 3" />
|
|||
|
|
<XAxis dataKey="month" />
|
|||
|
|
<YAxis />
|
|||
|
|
<Tooltip />
|
|||
|
|
<Line type="monotone" dataKey="sales" stroke="#10B981" strokeWidth={2} name="매출" />
|
|||
|
|
<Line type="monotone" dataKey="purchase" stroke="#F59E0B" strokeWidth={2} name="매입" />
|
|||
|
|
<Line type="monotone" dataKey="profit" stroke="#3B82F6" strokeWidth={2} name="이익" />
|
|||
|
|
</LineChart>
|
|||
|
|
</ResponsiveContainer>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</TabsContent>
|
|||
|
|
|
|||
|
|
{/* 미수금 관리 */}
|
|||
|
|
<TabsContent value="receivables" className="space-y-4">
|
|||
|
|
<Card className="border border-border/20">
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle className="flex items-center space-x-3">
|
|||
|
|
<CreditCard className="h-6 w-6 text-red-600" />
|
|||
|
|
<span>거래처별 미수금 현황</span>
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<Table>
|
|||
|
|
<TableHeader>
|
|||
|
|
<TableRow>
|
|||
|
|
<TableHead>거래처</TableHead>
|
|||
|
|
<TableHead className="text-right">미수금액</TableHead>
|
|||
|
|
<TableHead className="text-center">경과일수</TableHead>
|
|||
|
|
<TableHead className="text-center">상태</TableHead>
|
|||
|
|
<TableHead className="text-center">관리</TableHead>
|
|||
|
|
</TableRow>
|
|||
|
|
</TableHeader>
|
|||
|
|
<TableBody>
|
|||
|
|
{receivables.map((item, index) => (
|
|||
|
|
<TableRow key={index}>
|
|||
|
|
<TableCell className="font-medium">{item.company}</TableCell>
|
|||
|
|
<TableCell className="text-right font-bold">
|
|||
|
|
{item.amount.toLocaleString()}원
|
|||
|
|
</TableCell>
|
|||
|
|
<TableCell className="text-center">{item.days}일</TableCell>
|
|||
|
|
<TableCell className="text-center">
|
|||
|
|
<Badge
|
|||
|
|
className={
|
|||
|
|
item.status === "위험"
|
|||
|
|
? "bg-red-500 text-white"
|
|||
|
|
: item.status === "주의"
|
|||
|
|
? "bg-yellow-500 text-white"
|
|||
|
|
: "bg-green-500 text-white"
|
|||
|
|
}
|
|||
|
|
>
|
|||
|
|
{item.status}
|
|||
|
|
</Badge>
|
|||
|
|
</TableCell>
|
|||
|
|
<TableCell className="text-center">
|
|||
|
|
<Button size="sm" variant="outline">
|
|||
|
|
독촉
|
|||
|
|
</Button>
|
|||
|
|
</TableCell>
|
|||
|
|
</TableRow>
|
|||
|
|
))}
|
|||
|
|
</TableBody>
|
|||
|
|
</Table>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</TabsContent>
|
|||
|
|
|
|||
|
|
{/* 원가 분석 */}
|
|||
|
|
<TabsContent value="cost" className="space-y-4">
|
|||
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|||
|
|
<Card className="border border-border/20">
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle className="flex items-center space-x-3">
|
|||
|
|
<PieChart className="h-6 w-6 text-primary" />
|
|||
|
|
<span>원가 구성 비율</span>
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="h-64 min-h-[16rem]">
|
|||
|
|
<ResponsiveContainer width="100%" height="100%" minHeight={256}>
|
|||
|
|
<RechartsPieChart>
|
|||
|
|
<Pie
|
|||
|
|
data={costComposition}
|
|||
|
|
cx="50%"
|
|||
|
|
cy="50%"
|
|||
|
|
labelLine={false}
|
|||
|
|
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
|||
|
|
outerRadius={80}
|
|||
|
|
fill="#8884d8"
|
|||
|
|
dataKey="value"
|
|||
|
|
>
|
|||
|
|
{costComposition.map((entry, index) => (
|
|||
|
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
|||
|
|
))}
|
|||
|
|
</Pie>
|
|||
|
|
<Tooltip />
|
|||
|
|
</RechartsPieChart>
|
|||
|
|
</ResponsiveContainer>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2 mt-4">
|
|||
|
|
{costComposition.map((item, index) => (
|
|||
|
|
<div key={index} className="flex items-center justify-between">
|
|||
|
|
<div className="flex items-center space-x-2">
|
|||
|
|
<div
|
|||
|
|
className="w-4 h-4 rounded"
|
|||
|
|
style={{ backgroundColor: item.color }}
|
|||
|
|
/>
|
|||
|
|
<span className="text-sm font-medium">{item.name}</span>
|
|||
|
|
</div>
|
|||
|
|
<span className="font-bold">{item.value}%</span>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card className="border border-border/20">
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle className="flex items-center space-x-3">
|
|||
|
|
<Calculator className="h-6 w-6 text-primary" />
|
|||
|
|
<span>건별 원가 분석</span>
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="space-y-3">
|
|||
|
|
{costAnalysis.slice(0, 3).map((item, index) => (
|
|||
|
|
<div
|
|||
|
|
key={index}
|
|||
|
|
className="p-3 bg-muted/50 rounded-lg border border-border/50"
|
|||
|
|
>
|
|||
|
|
<div className="flex justify-between items-start mb-2">
|
|||
|
|
<div>
|
|||
|
|
<span className="text-xs text-muted-foreground">{item.orderNo}</span>
|
|||
|
|
<p className="font-bold text-sm">{item.product}</p>
|
|||
|
|
</div>
|
|||
|
|
<Badge
|
|||
|
|
className={
|
|||
|
|
item.profitRate >= 25
|
|||
|
|
? "bg-green-500 text-white"
|
|||
|
|
: item.profitRate >= 20
|
|||
|
|
? "bg-blue-500 text-white"
|
|||
|
|
: "bg-yellow-500 text-white"
|
|||
|
|
}
|
|||
|
|
>
|
|||
|
|
{item.profitRate}%
|
|||
|
|
</Badge>
|
|||
|
|
</div>
|
|||
|
|
<div className="grid grid-cols-2 gap-2 text-xs">
|
|||
|
|
<div>
|
|||
|
|
<span className="text-muted-foreground">매출: </span>
|
|||
|
|
<span className="font-bold">
|
|||
|
|
{(item.salesAmount / 1000000).toFixed(1)}M
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-muted-foreground">원가: </span>
|
|||
|
|
<span className="font-bold">
|
|||
|
|
{(item.totalCost / 1000000).toFixed(1)}M
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="col-span-2">
|
|||
|
|
<span className="text-muted-foreground">이익: </span>
|
|||
|
|
<span className="font-bold text-green-600">
|
|||
|
|
{(item.profit / 1000000).toFixed(1)}M
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</div>
|
|||
|
|
</TabsContent>
|
|||
|
|
|
|||
|
|
{/* 손익 현황 */}
|
|||
|
|
<TabsContent value="profit" className="space-y-4">
|
|||
|
|
<Card className="border border-border/20">
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle className="flex items-center space-x-3">
|
|||
|
|
<Banknote className="h-6 w-6 text-primary" />
|
|||
|
|
<span>월별 손익 현황</span>
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="h-80 min-h-[20rem]">
|
|||
|
|
<ResponsiveContainer width="100%" height="100%">
|
|||
|
|
<LineChart data={salesPurchaseData}>
|
|||
|
|
<CartesianGrid strokeDasharray="3 3" />
|
|||
|
|
<XAxis dataKey="month" />
|
|||
|
|
<YAxis />
|
|||
|
|
<Tooltip />
|
|||
|
|
<Line type="monotone" dataKey="profit" stroke="#10B981" strokeWidth={2} name="순이익" />
|
|||
|
|
</LineChart>
|
|||
|
|
</ResponsiveContainer>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</TabsContent>
|
|||
|
|
|
|||
|
|
{/* 입출금 내역 */}
|
|||
|
|
<TabsContent value="transactions" className="space-y-4">
|
|||
|
|
<Card className="border border-border/20">
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle className="flex items-center space-x-3">
|
|||
|
|
<Building2 className="h-6 w-6 text-primary" />
|
|||
|
|
<span>거래처 입출금 내역</span>
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="space-y-3">
|
|||
|
|
<div className="p-4 bg-green-50 dark:bg-green-950/20 rounded-lg border border-green-200">
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<div>
|
|||
|
|
<Badge className="bg-green-600 text-white mb-2">입금</Badge>
|
|||
|
|
<p className="font-bold">삼성전자</p>
|
|||
|
|
<p className="text-sm text-muted-foreground">2024-10-13 14:30</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-right">
|
|||
|
|
<p className="text-2xl font-bold text-green-600">+25,000,000원</p>
|
|||
|
|
<p className="text-xs text-muted-foreground">제품 출하대금</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="p-4 bg-red-50 dark:bg-red-950/20 rounded-lg border border-red-200">
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<div>
|
|||
|
|
<Badge className="bg-red-600 text-white mb-2">출금</Badge>
|
|||
|
|
<p className="font-bold">포스코</p>
|
|||
|
|
<p className="text-sm text-muted-foreground">2024-10-13 11:20</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-right">
|
|||
|
|
<p className="text-2xl font-bold text-red-600">-15,000,000원</p>
|
|||
|
|
<p className="text-xs text-muted-foreground">원자재 구매비</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</TabsContent>
|
|||
|
|
</Tabs>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|