diff --git a/frontend/src/components/report-page/mock.ts b/frontend/src/components/report-page/mock.ts
index 7a10c7b5d..84234f58c 100644
--- a/frontend/src/components/report-page/mock.ts
+++ b/frontend/src/components/report-page/mock.ts
@@ -6,6 +6,11 @@ interface CategoryItem {
averageSpentAmount: string;
}
+interface MyselfItem {
+ date: string;
+ cumulatedAmount: string;
+}
+
const mockData = {
countryCode: 'US',
compareWithAverage: {
@@ -16,70 +21,72 @@ const mockData = {
},
compareWithLastMonth: {
diff: '150',
- thisMonth: '2026-01',
- thisMonthCount: 20,
- lastMonth: '2025-12',
- lastMonthCount: 31,
- totalSpent: {
- thisMonthToDate: '830',
- lastMonthToSameDay: '680',
- lastMonthTotal: '2311.46',
+ thisMonth: {
+ label: '1월',
+ dayCount: 20,
+ totalSpent: '830',
+ items: [
+ { date: '2026-01-01', cumulatedAmount: '12.5' },
+ { date: '2026-01-02', cumulatedAmount: '35.2' },
+ { date: '2026-01-03', cumulatedAmount: '52.8' },
+ { date: '2026-01-04', cumulatedAmount: '78.1' },
+ { date: '2026-01-05', cumulatedAmount: '101' },
+ { date: '2026-01-06', cumulatedAmount: '128.4' },
+ { date: '2026-01-07', cumulatedAmount: '160.9' },
+ { date: '2026-01-08', cumulatedAmount: '190.3' },
+ { date: '2026-01-09', cumulatedAmount: '230.7' },
+ { date: '2026-01-10', cumulatedAmount: '260.1' },
+ { date: '2026-01-11', cumulatedAmount: '300.6' },
+ { date: '2026-01-12', cumulatedAmount: '338.9' },
+ { date: '2026-01-13', cumulatedAmount: '380.2' },
+ { date: '2026-01-14', cumulatedAmount: '420.1' },
+ { date: '2026-01-15', cumulatedAmount: '465' },
+ { date: '2026-01-16', cumulatedAmount: '520.4' },
+ { date: '2026-01-17', cumulatedAmount: '600.9' },
+ { date: '2026-01-18', cumulatedAmount: '700.3' },
+ { date: '2026-01-19', cumulatedAmount: '780.1' },
+ { date: '2026-01-20', cumulatedAmount: '830' },
+ ] as MyselfItem[],
+ },
+
+ lastMonth: {
+ label: '12월',
+ dayCount: 31,
+ totalSpent: '2311.46',
+ items: [
+ { date: '2025-12-01', cumulatedAmount: '18' },
+ { date: '2025-12-02', cumulatedAmount: '26' },
+ { date: '2025-12-03', cumulatedAmount: '41' },
+ { date: '2025-12-04', cumulatedAmount: '58' },
+ { date: '2025-12-05', cumulatedAmount: '82' },
+ { date: '2025-12-06', cumulatedAmount: '103' },
+ { date: '2025-12-07', cumulatedAmount: '121' },
+ { date: '2025-12-08', cumulatedAmount: '150' },
+ { date: '2025-12-09', cumulatedAmount: '190' },
+ { date: '2025-12-10', cumulatedAmount: '220' },
+ { date: '2025-12-11', cumulatedAmount: '260' },
+ { date: '2025-12-12', cumulatedAmount: '300' },
+ { date: '2025-12-13', cumulatedAmount: '330' },
+ { date: '2025-12-14', cumulatedAmount: '370' },
+ { date: '2025-12-15', cumulatedAmount: '420' },
+ { date: '2025-12-16', cumulatedAmount: '480' },
+ { date: '2025-12-17', cumulatedAmount: '540' },
+ { date: '2025-12-18', cumulatedAmount: '610' },
+ { date: '2025-12-19', cumulatedAmount: '650' },
+ { date: '2025-12-20', cumulatedAmount: '680' },
+ { date: '2025-12-21', cumulatedAmount: '710' },
+ { date: '2025-12-22', cumulatedAmount: '760' },
+ { date: '2025-12-23', cumulatedAmount: '810' },
+ { date: '2025-12-24', cumulatedAmount: '860' },
+ { date: '2025-12-25', cumulatedAmount: '930' },
+ { date: '2025-12-26', cumulatedAmount: '990' },
+ { date: '2025-12-27', cumulatedAmount: '1100' },
+ { date: '2025-12-28', cumulatedAmount: '1300' },
+ { date: '2025-12-29', cumulatedAmount: '1600' },
+ { date: '2025-12-30', cumulatedAmount: '2000' },
+ { date: '2025-12-31', cumulatedAmount: '2311.46' },
+ ] as MyselfItem[],
},
- thisMonthItem: [
- { date: '2026-01-01', cumulatedAmount: '12.5' },
- { date: '2026-01-02', cumulatedAmount: '35.2' },
- { date: '2026-01-03', cumulatedAmount: '52.8' },
- { date: '2026-01-04', cumulatedAmount: '78.1' },
- { date: '2026-01-05', cumulatedAmount: '101' },
- { date: '2026-01-06', cumulatedAmount: '128.4' },
- { date: '2026-01-07', cumulatedAmount: '160.9' },
- { date: '2026-01-08', cumulatedAmount: '190.3' },
- { date: '2026-01-09', cumulatedAmount: '230.7' },
- { date: '2026-01-10', cumulatedAmount: '260.1' },
- { date: '2026-01-11', cumulatedAmount: '300.6' },
- { date: '2026-01-12', cumulatedAmount: '338.9' },
- { date: '2026-01-13', cumulatedAmount: '380.2' },
- { date: '2026-01-14', cumulatedAmount: '420.1' },
- { date: '2026-01-15', cumulatedAmount: '465' },
- { date: '2026-01-16', cumulatedAmount: '520.4' },
- { date: '2026-01-17', cumulatedAmount: '600.9' },
- { date: '2026-01-18', cumulatedAmount: '700.3' },
- { date: '2026-01-19', cumulatedAmount: '780.1' },
- { date: '2026-01-20', cumulatedAmount: '830' },
- ],
- prevMonthItem: [
- { date: '2025-12-01', cumulatedAmount: '18' },
- { date: '2025-12-02', cumulatedAmount: '26' },
- { date: '2025-12-03', cumulatedAmount: '41' },
- { date: '2025-12-04', cumulatedAmount: '58' },
- { date: '2025-12-05', cumulatedAmount: '82' },
- { date: '2025-12-06', cumulatedAmount: '103' },
- { date: '2025-12-07', cumulatedAmount: '121' },
- { date: '2025-12-08', cumulatedAmount: '150' },
- { date: '2025-12-09', cumulatedAmount: '190' },
- { date: '2025-12-10', cumulatedAmount: '220' },
- { date: '2025-12-11', cumulatedAmount: '260' },
- { date: '2025-12-12', cumulatedAmount: '300' },
- { date: '2025-12-13', cumulatedAmount: '330' },
- { date: '2025-12-14', cumulatedAmount: '370' },
- { date: '2025-12-15', cumulatedAmount: '420' },
- { date: '2025-12-16', cumulatedAmount: '480' },
- { date: '2025-12-17', cumulatedAmount: '540' },
- { date: '2025-12-18', cumulatedAmount: '610' },
- { date: '2025-12-19', cumulatedAmount: '650' },
- { date: '2025-12-20', cumulatedAmount: '680' },
- { date: '2025-12-21', cumulatedAmount: '710' },
- { date: '2025-12-22', cumulatedAmount: '760' },
- { date: '2025-12-23', cumulatedAmount: '810' },
- { date: '2025-12-24', cumulatedAmount: '860' },
- { date: '2025-12-25', cumulatedAmount: '930' },
- { date: '2025-12-26', cumulatedAmount: '990' },
- { date: '2025-12-27', cumulatedAmount: '1100' },
- { date: '2025-12-28', cumulatedAmount: '1300' },
- { date: '2025-12-29', cumulatedAmount: '1600' },
- { date: '2025-12-30', cumulatedAmount: '2000' },
- { date: '2025-12-31', cumulatedAmount: '2311.46' },
- ],
},
compareByCategory: {
maxDiffCategoryIndex: 4,
@@ -88,45 +95,21 @@ const mockData = {
items: [
{
categoryIndex: 1,
- mySpentAmount: '302.62',
- averageSpentAmount: '379.21',
+ mySpentAmount: '302',
+ averageSpentAmount: '379',
},
{
categoryIndex: 3,
- mySpentAmount: '210.1',
- averageSpentAmount: '180.05',
- },
- {
- categoryIndex: 4,
- mySpentAmount: '480',
- averageSpentAmount: '300',
- },
- {
- categoryIndex: 6,
- mySpentAmount: '150',
- averageSpentAmount: '220',
- },
- {
- categoryIndex: 2,
- mySpentAmount: '600',
- averageSpentAmount: '650',
- },
- {
- categoryIndex: 5,
- mySpentAmount: '280',
- averageSpentAmount: '240',
- },
- {
- categoryIndex: 7,
- mySpentAmount: '60',
- averageSpentAmount: '55',
- },
- {
- categoryIndex: 8,
- mySpentAmount: '120',
- averageSpentAmount: '130',
+ mySpentAmount: '210',
+ averageSpentAmount: '180',
},
- ] as Array
,
+ { categoryIndex: 4, mySpentAmount: '480', averageSpentAmount: '300' },
+ { categoryIndex: 6, mySpentAmount: '150', averageSpentAmount: '220' },
+ { categoryIndex: 2, mySpentAmount: '600', averageSpentAmount: '650' },
+ { categoryIndex: 5, mySpentAmount: '280', averageSpentAmount: '240' },
+ { categoryIndex: 7, mySpentAmount: '60', averageSpentAmount: '55' },
+ { categoryIndex: 8, mySpentAmount: '120', averageSpentAmount: '130' },
+ ] as CategoryItem[],
},
} as const;
diff --git a/frontend/src/components/report-page/monthly/ReportMonthly.tsx b/frontend/src/components/report-page/monthly/ReportMonthly.tsx
new file mode 100644
index 000000000..4c2a2bdd7
--- /dev/null
+++ b/frontend/src/components/report-page/monthly/ReportMonthly.tsx
@@ -0,0 +1,17 @@
+import ReportContainer from '@/components/report-page/layout/ReportContainer';
+import ReportContent from '@/components/report-page/layout/ReportContent';
+
+const ReportMonthly = () => {
+ return (
+
+
+
+ 나랑 같은 국가의 교환학생보다
+ 234달러 덜 썼어요
+
+
+
+ );
+};
+
+export default ReportMonthly;
diff --git a/frontend/src/components/report-page/myself/ReportLineChart.tsx b/frontend/src/components/report-page/myself/ReportLineChart.tsx
new file mode 100644
index 000000000..6a8185509
--- /dev/null
+++ b/frontend/src/components/report-page/myself/ReportLineChart.tsx
@@ -0,0 +1,79 @@
+import {
+ buildAreaPath,
+ buildLinePath,
+} from '@/components/report-page/myself/buildPath';
+import { type ChartItem } from '@/components/report-page/reportType';
+
+type MonthlyChartData = {
+ dayCount: number;
+ items: ChartItem[];
+};
+
+interface ReportLineChartProps {
+ thisMonth: MonthlyChartData;
+ lastMonth: MonthlyChartData;
+ maxValue: number;
+ width?: number;
+ height?: number;
+}
+
+const ReportLineChart = ({
+ thisMonth,
+ lastMonth,
+ maxValue,
+ width = 348,
+ height = 140,
+}: ReportLineChartProps) => {
+ const maxDay = Math.max(thisMonth.dayCount, lastMonth.dayCount);
+
+ const thisPath = buildLinePath(
+ thisMonth.items,
+ width,
+ height,
+ maxValue,
+ maxDay,
+ );
+ const lastLinePath = buildLinePath(
+ lastMonth.items,
+ width,
+ height,
+ maxValue,
+ maxDay,
+ );
+ const lastAreaPath = buildAreaPath(
+ lastMonth.items,
+ width,
+ height,
+ maxValue,
+ maxDay,
+ );
+
+ const thisMonthWidth = (thisMonth.items.length / maxDay) * width;
+
+ return (
+
+ );
+};
+
+export default ReportLineChart;
diff --git a/frontend/src/components/report-page/myself/ReportLineGraph.tsx b/frontend/src/components/report-page/myself/ReportLineGraph.tsx
new file mode 100644
index 000000000..ab76f020d
--- /dev/null
+++ b/frontend/src/components/report-page/myself/ReportLineGraph.tsx
@@ -0,0 +1,52 @@
+import ReportLineChart from '@/components/report-page/myself/ReportLineChart';
+import ReportLegend from '@/components/report-page/ReportLegend';
+import { type ChartItem } from '@/components/report-page/reportType';
+import VerticalGrid from '@/components/report-page/VerticalGrid';
+
+type MonthlyGraphData = {
+ label: string;
+ dayCount: number;
+ items: ChartItem[];
+};
+
+interface ReportLineGraphProps {
+ thisMonth: MonthlyGraphData;
+ lastMonth: MonthlyGraphData;
+ maxValue: number;
+}
+
+const ReportLineGraph = ({
+ thisMonth,
+ lastMonth,
+ maxValue,
+}: ReportLineGraphProps) => {
+ const maxDays = Math.max(thisMonth.dayCount, lastMonth.dayCount);
+
+ const positions = [0, ((thisMonth.dayCount - 1) / (maxDays - 1)) * 100, 100];
+ const labels = ['1일', `${thisMonth.dayCount}일`, `${maxDays}일`];
+
+ return (
+
+ );
+};
+
+export default ReportLineGraph;
diff --git a/frontend/src/components/report-page/myself/ReportMyself.tsx b/frontend/src/components/report-page/myself/ReportMyself.tsx
new file mode 100644
index 000000000..e9d869989
--- /dev/null
+++ b/frontend/src/components/report-page/myself/ReportMyself.tsx
@@ -0,0 +1,68 @@
+import ReportContainer from '@/components/report-page/layout/ReportContainer';
+import ReportContent from '@/components/report-page/layout/ReportContent';
+import ReportLineGraph from '@/components/report-page/myself/ReportLineGraph';
+import { useReportContext } from '@/components/report-page/ReportContext';
+import { type ChartItem } from '@/components/report-page/reportType';
+
+import { type CountryCode } from '@/data/countryCode';
+import { getCountryInfo } from '@/lib/country';
+import { useAccountBookStore } from '@/stores/useAccountBookStore';
+
+type MonthlyData = {
+ label: string;
+ dayCount: number;
+ totalSpent: string;
+ items: ChartItem[];
+};
+
+interface ReportMyselfProps {
+ data: {
+ diff: string;
+ thisMonth: MonthlyData;
+ lastMonth: MonthlyData;
+ };
+}
+
+const ReportMyself = ({ data }: ReportMyselfProps) => {
+ const { currencyType } = useReportContext();
+ const countryCode = useAccountBookStore((state) =>
+ currencyType === 'LOCAL'
+ ? state.accountBook?.localCountryCode
+ : state.accountBook?.baseCountryCode,
+ ) as CountryCode;
+
+ const unit = getCountryInfo(countryCode)?.currencyUnitKor || '';
+
+ const maxValue = Math.max(
+ Number(data.lastMonth.totalSpent),
+ Number(data.thisMonth.totalSpent),
+ );
+
+ return (
+
+
+
+
+ 지난달보다{' '}
+
+ {data.diff}
+ {unit} 더
+ {' '}
+ 쓰는 중이에요
+
+
+ 오늘까지 {data.thisMonth.totalSpent}
+ {unit} 썼어요
+
+
+
+
+
+ );
+};
+
+export default ReportMyself;
diff --git a/frontend/src/components/report-page/myself/buildPath.ts b/frontend/src/components/report-page/myself/buildPath.ts
new file mode 100644
index 000000000..67e4f4cb0
--- /dev/null
+++ b/frontend/src/components/report-page/myself/buildPath.ts
@@ -0,0 +1,46 @@
+import { type ChartItem } from '@/components/report-page/reportType';
+
+const buildLineSegments = (
+ data: ChartItem[],
+ stepX: number,
+ height: number,
+ maxValue: number,
+) =>
+ data
+ .map((item, index) => {
+ const x = stepX * index;
+ const value = Number(item.cumulatedAmount);
+ const y = height - (value / maxValue) * height;
+
+ return `${index === 0 ? 'M' : 'L'} ${x} ${y}`;
+ })
+ .join(' ');
+
+export const buildLinePath = (
+ data: ChartItem[],
+ width: number,
+ height: number,
+ maxValue: number,
+ maxDay: number,
+) => {
+ const stepX = width / (maxDay - 1);
+
+ return buildLineSegments(data, stepX, height, maxValue);
+};
+
+export const buildAreaPath = (
+ data: ChartItem[],
+ width: number,
+ height: number,
+ maxValue: number,
+ maxDay: number,
+) => {
+ const stepX = width / (maxDay - 1);
+ const linePath = buildLineSegments(data, stepX, height, maxValue);
+
+ const lastIndex = data.length - 1;
+ const lastX = stepX * lastIndex;
+ const closingPath = `L ${lastX} ${height} L 0 ${height} Z`;
+
+ return linePath + ' ' + closingPath;
+};
diff --git a/frontend/src/components/report-page/reportType.ts b/frontend/src/components/report-page/reportType.ts
new file mode 100644
index 000000000..50d8d72ea
--- /dev/null
+++ b/frontend/src/components/report-page/reportType.ts
@@ -0,0 +1,4 @@
+export type ChartItem = {
+ date: string;
+ cumulatedAmount: string;
+};
diff --git a/frontend/src/pages/ReportPage.tsx b/frontend/src/pages/ReportPage.tsx
index c4c479048..78637cb5c 100644
--- a/frontend/src/pages/ReportPage.tsx
+++ b/frontend/src/pages/ReportPage.tsx
@@ -2,20 +2,28 @@ import { useState } from 'react';
import ReportCategory from '@/components/report-page/category/ReportCategory';
import mockData from '@/components/report-page/mock';
+import ReportMonthly from '@/components/report-page/monthly/ReportMonthly';
+import ReportMyself from '@/components/report-page/myself/ReportMyself';
import ReportProvider from '@/components/report-page/ReportProvider';
import { type CurrencyType } from '@/types/currency';
const ReportPage = () => {
const categoryData = mockData.compareByCategory;
+ const myselfData = mockData.compareWithLastMonth;
const [currencyType, setCurrencyType] = useState('LOCAL'); // @TODO: 드롭다운 추가 예정
return (
-
+
diff --git a/frontend/src/styles/animation.css b/frontend/src/styles/animation.css
index 2d40f21bb..6fb56b163 100644
--- a/frontend/src/styles/animation.css
+++ b/frontend/src/styles/animation.css
@@ -56,10 +56,20 @@
}
}
+ @keyframes draw-path {
+ from {
+ stroke-dashoffset: 1000;
+ }
+ to {
+ stroke-dashoffset: 0;
+ }
+ }
+
--animate-float: float 2s ease-in-out infinite;
--animate-marquee: marquee 30s linear infinite;
--animate-marquee-reverse: marquee-reverse 30s linear infinite;
--animate-ripple: ripple 2s ease-in-out infinite;
--animate-grow: scale-up 0.2s ease-in-out forwards;
--animate-expand-width: expand-width ease-out forwards;
+ --animate-draw-path: draw-path 2s ease-in-out forwards;
}