diff --git a/frontend/src/assets/Icons/chevron-back.svg b/frontend/src/assets/Icons/chevron-back.svg index 084d2b7a..61faf93a 100644 --- a/frontend/src/assets/Icons/chevron-back.svg +++ b/frontend/src/assets/Icons/chevron-back.svg @@ -1,3 +1,3 @@ - + diff --git a/frontend/src/components/data-table/DataTable.tsx b/frontend/src/components/data-table/DataTable.tsx index d83b1fbd..7ce57443 100644 --- a/frontend/src/components/data-table/DataTable.tsx +++ b/frontend/src/components/data-table/DataTable.tsx @@ -15,13 +15,13 @@ import { interface DataTableProps { groupBy?: (row: TData) => string; enableGroupSelection?: boolean; - height?: number; + blankFallbackText?: string; } const DataTable = ({ groupBy, - height, enableGroupSelection = true, + blankFallbackText, }: DataTableProps) => { const { table, dispatch } = useDataTable(); const rows = table.getRowModel().rows as Row[]; @@ -60,8 +60,8 @@ const DataTable = ({ ); // dispatch가 바뀌지 않는 한 함수 재사용 return ( - - +
+ {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( @@ -151,12 +151,14 @@ const DataTable = ({ ); }) ) : ( - + - No results. +

+ {blankFallbackText || '데이터가 없습니다'} +

)} diff --git a/frontend/src/components/data-table/DataTableProvider.tsx b/frontend/src/components/data-table/DataTableProvider.tsx index 886fa87a..efc95283 100644 --- a/frontend/src/components/data-table/DataTableProvider.tsx +++ b/frontend/src/components/data-table/DataTableProvider.tsx @@ -93,7 +93,7 @@ const DataTableProvider = ({ } as DataTableContextType } > - {children} +
{children}
); }; diff --git a/frontend/src/components/data-table/ImportToFolderBar.tsx b/frontend/src/components/data-table/ImportToFolderBar.tsx new file mode 100644 index 00000000..7f2bccc7 --- /dev/null +++ b/frontend/src/components/data-table/ImportToFolderBar.tsx @@ -0,0 +1,32 @@ +import { useDataTable } from '@/components/data-table/context'; + +import { Icons } from '@/assets'; + +const Divider = () =>
; + +const ImportToFolderBar = () => { + const { table, tableState } = useDataTable(); + const selectedRows = table.getFilteredSelectedRowModel().rows; + + if (!tableState.selectionMode) return null; + + const handleImportToFolder = () => {}; + + return ( +
+
+ {selectedRows.length}개 선택됨 + + + +
+
+ ); +}; + +export default ImportToFolderBar; diff --git a/frontend/src/components/data-table/SelectionActionProvider.tsx b/frontend/src/components/data-table/SelectionActionBar.tsx similarity index 95% rename from frontend/src/components/data-table/SelectionActionProvider.tsx rename to frontend/src/components/data-table/SelectionActionBar.tsx index e198e724..c1537ceb 100644 --- a/frontend/src/components/data-table/SelectionActionProvider.tsx +++ b/frontend/src/components/data-table/SelectionActionBar.tsx @@ -19,7 +19,7 @@ const SelectionActionButton = ({ ); }; -const SelectionActionProvider = () => { +const SelectionActionBar = () => { const { table, tableState } = useDataTable(); const selectedRows = table.getFilteredSelectedRowModel().rows; @@ -57,4 +57,4 @@ const SelectionActionProvider = () => { ); }; -export default SelectionActionProvider; +export default SelectionActionBar; diff --git a/frontend/src/components/layout/BottomSheet.tsx b/frontend/src/components/layout/BottomSheet.tsx index 8ba48e98..57e8def7 100644 --- a/frontend/src/components/layout/BottomSheet.tsx +++ b/frontend/src/components/layout/BottomSheet.tsx @@ -36,10 +36,10 @@ const BottomSheet = ({ exit={{ y: '100%' }} transition={{ type: 'spring', damping: 25, stiffness: 200 }} className={cn( - `bg-background-normal fixed right-[12vh] bottom-0 left-[18vh] z-50 h-[80vh] rounded-t-2xl ${className}`, + `bg-background-normal fixed right-[12vh] bottom-0 left-[18vh] z-50 flex h-[80vh] flex-col rounded-t-2xl px-2 ${className}`, )} > -
+
{children} diff --git a/frontend/src/components/ui/table.tsx b/frontend/src/components/ui/table.tsx index 87bbe913..d938f7b0 100644 --- a/frontend/src/components/ui/table.tsx +++ b/frontend/src/components/ui/table.tsx @@ -2,16 +2,11 @@ import * as React from 'react'; import { cn } from '@/lib/utils'; -function Table({ - className, - height, - ...props -}: React.ComponentProps<'table'> & { height?: number }) { +function Table({ ...props }: React.ComponentProps<'table'>) { return (
@@ -23,7 +18,7 @@ function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) { {
new Date(row.date).toLocaleDateString('ko-KR', { @@ -211,7 +210,6 @@ const DemoSection = () => {
new Date(row.date).toLocaleDateString('ko-KR', { diff --git a/frontend/src/pages/TravelDetailPage.tsx b/frontend/src/pages/TravelDetailPage.tsx new file mode 100644 index 00000000..36ff1bf6 --- /dev/null +++ b/frontend/src/pages/TravelDetailPage.tsx @@ -0,0 +1,126 @@ +import { useState } from 'react'; +import { Link } from '@tanstack/react-router'; + +import Button from '@/components/common/Button'; +import Divider from '@/components/common/Divider'; +import { DataTable } from '@/components/data-table/DataTable'; +import { DataTableFilterProvider } from '@/components/data-table/DataTableFilter'; +import DataTableProvider from '@/components/data-table/DataTableProvider'; +import CategoryFilter from '@/components/data-table/filters/CategoryFilter'; +import DateFilter from '@/components/data-table/filters/DateFilter'; +import MerchantFilter from '@/components/data-table/filters/MerchantFilter'; +import MethodFilter from '@/components/data-table/filters/MethodFilter'; +import SortDropdown from '@/components/data-table/filters/SortDropdown'; +import ImportToFolderBar from '@/components/data-table/ImportToFolderBar'; +import SelectionActionBar from '@/components/data-table/SelectionActionBar'; +import { columns } from '@/components/home-page/columns'; +import ExpenseCard from '@/components/home-page/ExpenseCard'; +import { type Expense, getData } from '@/components/landing-page/dummy'; +import BottomSheet from '@/components/layout/BottomSheet'; + +import { Icons } from '@/assets'; + +const TripSummary = () => { + return ( +
+
+ + + + 뉴욕 보스턴 +
+
+ + 2026.01.21 - 2026.01.26 + + + 5박 6일 + +
+
+ ); +}; + +const TravelDetailPage = () => { + const [isBottomSheetOpen, setBottomSheetOpen] = useState(false); + + const data = getData(); + return ( +
+
+ + + +
+ +
+ +
+ + + + + + +
+ + + + + new Date(row.date).toLocaleDateString('ko-KR', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + }) + } + blankFallbackText={'여행 지출 내역을 추가해주세요'} + /> + + +
+ setBottomSheetOpen(false)} + > + + + + + + +
+ + + + new Date(row.date).toLocaleDateString('ko-KR', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + }) + } + blankFallbackText={'여행 지출 내역을 추가해주세요'} + /> + + + +
+ ); +}; + +export default TravelDetailPage; diff --git a/frontend/src/pages/TravelPage.tsx b/frontend/src/pages/TravelPage.tsx index 7985681b..64a0cc8e 100644 --- a/frontend/src/pages/TravelPage.tsx +++ b/frontend/src/pages/TravelPage.tsx @@ -1,3 +1,5 @@ +import { Link } from '@tanstack/react-router'; + import Button from '@/components/common/Button'; import FolderCard from '@/components/travel-page/FolderCard'; @@ -6,6 +8,7 @@ import { COUNTRY_CODE } from '@/data/countryCode'; const folderMap = [ { + travelId: 0, label: '뉴욕 보스턴', dateRange: '2026.01.21 - 2026.01.26', localCountryCode: COUNTRY_CODE.US, @@ -15,6 +18,7 @@ const folderMap = [ imageUrl: SeoulImage, }, { + travelId: 1, label: '뉴욕 보스턴', dateRange: '2026.01.21 - 2026.01.26', localCountryCode: COUNTRY_CODE.KR, @@ -41,17 +45,22 @@ const TravelPage = () => {
- {folderMap.map((folder, index) => ( - + {folderMap.map((folder) => ( + + + ))}
diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index e6c3ac0c..36167501 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -13,11 +13,12 @@ import { Route as AuthRouteImport } from './routes/_auth' import { Route as AppRouteImport } from './routes/_app' import { Route as AuthIndexRouteImport } from './routes/_auth/index' import { Route as AuthLoginRouteImport } from './routes/_auth/login' -import { Route as AppTravelRouteImport } from './routes/_app/travel' import { Route as AppSettingRouteImport } from './routes/_app/setting' import { Route as AppReportRouteImport } from './routes/_app/report' import { Route as AppInitRouteImport } from './routes/_app/init' import { Route as AppHomeRouteImport } from './routes/_app/home' +import { Route as AppTravelIndexRouteImport } from './routes/_app/travel/index' +import { Route as AppTravelTravelIdRouteImport } from './routes/_app/travel/$travelId' const AuthRoute = AuthRouteImport.update({ id: '/_auth', @@ -37,11 +38,6 @@ const AuthLoginRoute = AuthLoginRouteImport.update({ path: '/login', getParentRoute: () => AuthRoute, } as any) -const AppTravelRoute = AppTravelRouteImport.update({ - id: '/travel', - path: '/travel', - getParentRoute: () => AppRoute, -} as any) const AppSettingRoute = AppSettingRouteImport.update({ id: '/setting', path: '/setting', @@ -62,6 +58,16 @@ const AppHomeRoute = AppHomeRouteImport.update({ path: '/home', getParentRoute: () => AppRoute, } as any) +const AppTravelIndexRoute = AppTravelIndexRouteImport.update({ + id: '/travel/', + path: '/travel/', + getParentRoute: () => AppRoute, +} as any) +const AppTravelTravelIdRoute = AppTravelTravelIdRouteImport.update({ + id: '/travel/$travelId', + path: '/travel/$travelId', + getParentRoute: () => AppRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof AuthIndexRoute @@ -69,8 +75,9 @@ export interface FileRoutesByFullPath { '/init': typeof AppInitRoute '/report': typeof AppReportRoute '/setting': typeof AppSettingRoute - '/travel': typeof AppTravelRoute '/login': typeof AuthLoginRoute + '/travel/$travelId': typeof AppTravelTravelIdRoute + '/travel/': typeof AppTravelIndexRoute } export interface FileRoutesByTo { '/': typeof AuthIndexRoute @@ -78,8 +85,9 @@ export interface FileRoutesByTo { '/init': typeof AppInitRoute '/report': typeof AppReportRoute '/setting': typeof AppSettingRoute - '/travel': typeof AppTravelRoute '/login': typeof AuthLoginRoute + '/travel/$travelId': typeof AppTravelTravelIdRoute + '/travel': typeof AppTravelIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -89,9 +97,10 @@ export interface FileRoutesById { '/_app/init': typeof AppInitRoute '/_app/report': typeof AppReportRoute '/_app/setting': typeof AppSettingRoute - '/_app/travel': typeof AppTravelRoute '/_auth/login': typeof AuthLoginRoute '/_auth/': typeof AuthIndexRoute + '/_app/travel/$travelId': typeof AppTravelTravelIdRoute + '/_app/travel/': typeof AppTravelIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -101,10 +110,19 @@ export interface FileRouteTypes { | '/init' | '/report' | '/setting' - | '/travel' | '/login' + | '/travel/$travelId' + | '/travel/' fileRoutesByTo: FileRoutesByTo - to: '/' | '/home' | '/init' | '/report' | '/setting' | '/travel' | '/login' + to: + | '/' + | '/home' + | '/init' + | '/report' + | '/setting' + | '/login' + | '/travel/$travelId' + | '/travel' id: | '__root__' | '/_app' @@ -113,9 +131,10 @@ export interface FileRouteTypes { | '/_app/init' | '/_app/report' | '/_app/setting' - | '/_app/travel' | '/_auth/login' | '/_auth/' + | '/_app/travel/$travelId' + | '/_app/travel/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -153,13 +172,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthLoginRouteImport parentRoute: typeof AuthRoute } - '/_app/travel': { - id: '/_app/travel' - path: '/travel' - fullPath: '/travel' - preLoaderRoute: typeof AppTravelRouteImport - parentRoute: typeof AppRoute - } '/_app/setting': { id: '/_app/setting' path: '/setting' @@ -188,6 +200,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppHomeRouteImport parentRoute: typeof AppRoute } + '/_app/travel/': { + id: '/_app/travel/' + path: '/travel' + fullPath: '/travel/' + preLoaderRoute: typeof AppTravelIndexRouteImport + parentRoute: typeof AppRoute + } + '/_app/travel/$travelId': { + id: '/_app/travel/$travelId' + path: '/travel/$travelId' + fullPath: '/travel/$travelId' + preLoaderRoute: typeof AppTravelTravelIdRouteImport + parentRoute: typeof AppRoute + } } } @@ -196,7 +222,8 @@ interface AppRouteChildren { AppInitRoute: typeof AppInitRoute AppReportRoute: typeof AppReportRoute AppSettingRoute: typeof AppSettingRoute - AppTravelRoute: typeof AppTravelRoute + AppTravelTravelIdRoute: typeof AppTravelTravelIdRoute + AppTravelIndexRoute: typeof AppTravelIndexRoute } const AppRouteChildren: AppRouteChildren = { @@ -204,7 +231,8 @@ const AppRouteChildren: AppRouteChildren = { AppInitRoute: AppInitRoute, AppReportRoute: AppReportRoute, AppSettingRoute: AppSettingRoute, - AppTravelRoute: AppTravelRoute, + AppTravelTravelIdRoute: AppTravelTravelIdRoute, + AppTravelIndexRoute: AppTravelIndexRoute, } const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren) diff --git a/frontend/src/routes/_app/travel/$travelId.tsx b/frontend/src/routes/_app/travel/$travelId.tsx new file mode 100644 index 00000000..7ea92810 --- /dev/null +++ b/frontend/src/routes/_app/travel/$travelId.tsx @@ -0,0 +1,11 @@ +import { createFileRoute } from '@tanstack/react-router'; + +import TravelDetailPage from '@/pages/TravelDetailPage'; + +export const Route = createFileRoute('/_app/travel/$travelId')({ + component: RouteComponent, +}); + +function RouteComponent() { + return ; +} diff --git a/frontend/src/routes/_app/travel.tsx b/frontend/src/routes/_app/travel/index.tsx similarity index 77% rename from frontend/src/routes/_app/travel.tsx rename to frontend/src/routes/_app/travel/index.tsx index 2b71b33c..7700d118 100644 --- a/frontend/src/routes/_app/travel.tsx +++ b/frontend/src/routes/_app/travel/index.tsx @@ -2,7 +2,7 @@ import { createFileRoute } from '@tanstack/react-router'; import TravelPage from '@/pages/TravelPage'; -export const Route = createFileRoute('/_app/travel')({ +export const Route = createFileRoute('/_app/travel/')({ component: RouteComponent, }); diff --git a/frontend/src/styles/objectStyle.css b/frontend/src/styles/objectStyle.css index 90f8b67e..7440627e 100644 --- a/frontend/src/styles/objectStyle.css +++ b/frontend/src/styles/objectStyle.css @@ -71,6 +71,13 @@ box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.08); } +@utility shadow-semantic-strong { + box-shadow: + 0 6px 12px 0 rgba(0, 0, 0, 0.12), + 0 4px 8px 0 rgba(0, 0, 0, 0.08), + 0 0 4px 0 rgba(0, 0, 0, 0.08); +} + @utility shadow-popover { box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.06); }