diff --git a/package-lock.json b/package-lock.json index 36f879b..7095622 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-progress": "^1.1.0", - "@radix-ui/react-radio-group": "^1.2.0", + "@radix-ui/react-radio-group": "^1.2.3", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.0", @@ -2058,18 +2058,18 @@ } }, "node_modules/@radix-ui/react-radio-group": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.1.tgz", - "integrity": "sha512-kdbv54g4vfRjja9DNWPMxKvXblzqbpEC8kspEkZ6dVP7kQksGCn+iZHkcCz2nb00+lPdRvxrqy4WrvvV1cNqrQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.3.tgz", + "integrity": "sha512-xtCsqt8Rp09FK50ItqEqTJ7Sxanz8EM8dnkVIhJrc/wkMMomSmXHvYbhv3E7Zx4oXh98aaLt9W679SUYXg4IDA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" @@ -2089,6 +2089,149 @@ } } }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-collection": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz", + "integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz", + "integrity": "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", diff --git a/package.json b/package.json index a571fb1..89958cd 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-progress": "^1.1.0", - "@radix-ui/react-radio-group": "^1.2.0", + "@radix-ui/react-radio-group": "^1.2.3", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.0", diff --git a/src/components/AddTransactionButton.tsx b/src/components/AddTransactionButton.tsx index fe2da16..3009986 100644 --- a/src/components/AddTransactionButton.tsx +++ b/src/components/AddTransactionButton.tsx @@ -1,3 +1,4 @@ + import React, { useState, useEffect } from 'react'; import { PlusIcon } from 'lucide-react'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog'; @@ -53,7 +54,8 @@ const AddTransactionButton = () => { amount: parseInt(numericAmount), date: formattedDate, category: data.category, - type: 'expense' + type: 'expense', + paymentMethod: data.paymentMethod // 추가된 필드 }; console.log('새 지출 추가:', newExpense); @@ -75,7 +77,8 @@ const AddTransactionButton = () => { date: isoDate, // ISO 형식 사용 category: data.category, type: 'expense', - transaction_id: newExpense.id + transaction_id: newExpense.id, + payment_method: data.paymentMethod // Supabase에 필드 추가 }); if (error) throw error; diff --git a/src/components/analytics/PaymentMethodChart.tsx b/src/components/analytics/PaymentMethodChart.tsx new file mode 100644 index 0000000..d7fdd01 --- /dev/null +++ b/src/components/analytics/PaymentMethodChart.tsx @@ -0,0 +1,67 @@ + +import React from 'react'; +import { PieChart, Pie, Cell, ResponsiveContainer, Legend } from 'recharts'; + +interface PaymentMethodData { + method: string; + amount: number; + percentage: number; +} + +interface PaymentMethodChartProps { + data: PaymentMethodData[]; + isEmpty: boolean; +} + +const COLORS = ['#9b87f5', '#6E59A5']; // 신용카드, 현금 색상 + +const PaymentMethodChart: React.FC = ({ data, isEmpty }) => { + if (isEmpty) { + return ( +
+

데이터가 없습니다

+
+ ); + } + + const chartData = data.map(item => ({ + name: item.method, + value: item.amount + })); + + return ( +
+ + + `${name} ${(percent * 100).toFixed(0)}%`} + fontSize={12} + > + {chartData.map((entry, index) => ( + + ))} + + { + const item = data.find(d => d.method === value); + return item ? `${value} (${item.percentage.toFixed(0)}%)` : value; + }} + /> + + +
+ ); +}; + +export default PaymentMethodChart; diff --git a/src/components/expenses/ExpenseForm.tsx b/src/components/expenses/ExpenseForm.tsx index 17608f1..0784cb9 100644 --- a/src/components/expenses/ExpenseForm.tsx +++ b/src/components/expenses/ExpenseForm.tsx @@ -8,11 +8,14 @@ import { Loader2 } from 'lucide-react'; import ExpenseCategorySelector from './ExpenseCategorySelector'; import { Badge } from '@/components/ui/badge'; import { getPersonalizedTitleSuggestions } from '@/utils/userTitlePreferences'; +import { Separator } from '@/components/ui/separator'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; export interface ExpenseFormValues { title: string; amount: string; category: string; + paymentMethod: '신용카드' | '현금'; } interface ExpenseFormProps { @@ -27,6 +30,7 @@ const ExpenseForm: React.FC = ({ onSubmit, onCancel, isSubmitt title: '', amount: '', category: '음식', + paymentMethod: '신용카드' } }); @@ -113,7 +117,7 @@ const ExpenseForm: React.FC = ({ onSubmit, onCancel, isSubmitt )} /> - {/* 금액 필드를 마지막으로 배치 */} + {/* 금액 필드를 세 번째로 배치 */} = ({ onSubmit, onCancel, isSubmitt )} /> + {/* 구분선 추가 */} + + + {/* 지출 방법 필드 추가 */} + ( + + 지출 방법 + +
+ + +
+
+ + +
+
+
+ )} + /> +
} + {/* 결제 방법 차트 추가 */} +

결제 방법별 지출

+ + {/* Top Spending Categories */}

주요 지출 카테고리