From 79615de536af22ab4f1a9463b0a7b5788e7b21a4 Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Sun, 29 Jun 2025 01:35:08 +0900 Subject: [PATCH 01/15] feat : install `jotai` and create constants and define types for modal --- package.json | 1 + pnpm-lock.yaml | 20 ++++++++++++++++++++ src/shared/atom/index.ts | 1 + src/shared/atom/modal.ts | 26 ++++++++++++++++++++++++++ src/shared/constants/index.ts | 1 + src/shared/constants/modal.ts | 3 +++ src/shared/types/index.ts | 1 + src/shared/types/modal.ts | 3 +++ 8 files changed, 56 insertions(+) create mode 100644 src/shared/atom/index.ts create mode 100644 src/shared/atom/modal.ts create mode 100644 src/shared/constants/modal.ts create mode 100644 src/shared/types/modal.ts diff --git a/package.json b/package.json index d53ab45..27b496c 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "clsx": "^2.1.1", "daisyui": "^5.0.43", "framer-motion": "^12.19.2", + "jotai": "^2.12.5", "prettier": "^3.6.2", "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc13b00..c487a17 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: framer-motion: specifier: ^12.19.2 version: 12.19.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + jotai: + specifier: ^2.12.5 + version: 2.12.5(@types/react@19.1.8)(react@19.1.0) prettier: specifier: ^3.6.2 version: 3.6.2 @@ -1138,6 +1141,18 @@ packages: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true + jotai@2.12.5: + resolution: {integrity: sha512-G8m32HW3lSmcz/4mbqx0hgJIQ0ekndKWiYP7kWVKi0p6saLXdSoye+FZiOFyonnd7Q482LCzm8sMDl7Ar1NWDw==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -2503,6 +2518,11 @@ snapshots: jiti@2.4.2: {} + jotai@2.12.5(@types/react@19.1.8)(react@19.1.0): + optionalDependencies: + '@types/react': 19.1.8 + react: 19.1.0 + js-yaml@4.1.0: dependencies: argparse: 2.0.1 diff --git a/src/shared/atom/index.ts b/src/shared/atom/index.ts new file mode 100644 index 0000000..133aa74 --- /dev/null +++ b/src/shared/atom/index.ts @@ -0,0 +1 @@ +export * from './modal'; diff --git a/src/shared/atom/modal.ts b/src/shared/atom/modal.ts new file mode 100644 index 0000000..7182f2b --- /dev/null +++ b/src/shared/atom/modal.ts @@ -0,0 +1,26 @@ +import { atom } from 'jotai'; + +import type { ModalItem } from '@/shared/types'; +import { MODAL } from '@/shared/constants'; + +type ModalInfo = { + [key in ModalItem]: { isOpen: boolean }; +}; + +const modals = Object.fromEntries( + Object.keys(MODAL).map(key => [key, { isOpen: false }]), +) as ModalInfo; + +export const modalAtom = atom(modals); + +export const updateModal = atom( + null, + (get, set, update: { key: ModalItem; isOpen: boolean }) => { + const currentModal = get(modalAtom); + const updatedModal = { + ...currentModal, + [update.key]: { isOpen: update.isOpen }, + }; + set(modalAtom, updatedModal); + }, +); diff --git a/src/shared/constants/index.ts b/src/shared/constants/index.ts index a9ba2d6..0c93df6 100644 --- a/src/shared/constants/index.ts +++ b/src/shared/constants/index.ts @@ -1,2 +1,3 @@ export * from './path'; export * from './dock'; +export * from './modal'; diff --git a/src/shared/constants/modal.ts b/src/shared/constants/modal.ts new file mode 100644 index 0000000..4532ee0 --- /dev/null +++ b/src/shared/constants/modal.ts @@ -0,0 +1,3 @@ +export const MODAL = { + TOOL_SEARCH: 'toolSearchModal', +} as const; diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts index b7a5106..0d82db9 100644 --- a/src/shared/types/index.ts +++ b/src/shared/types/index.ts @@ -3,3 +3,4 @@ export * from './path'; export * from './reservation'; export * from './user'; export * from './tools'; +export * from './modal'; diff --git a/src/shared/types/modal.ts b/src/shared/types/modal.ts new file mode 100644 index 0000000..e7c11a7 --- /dev/null +++ b/src/shared/types/modal.ts @@ -0,0 +1,3 @@ +import { MODAL } from '@/shared/constants'; + +export type ModalItem = (typeof MODAL)[keyof typeof MODAL]; From e9c29119f604233d41ad6693230a674224cdd9c1 Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Sun, 29 Jun 2025 03:03:24 +0900 Subject: [PATCH 02/15] feat : add icons for customer, user page --- src/assets/icons/icon-customer.svg | 9 +++++++++ src/assets/icons/icon-logout.svg | 5 +++++ src/assets/icons/index.ts | 4 ++++ 3 files changed, 18 insertions(+) create mode 100644 src/assets/icons/icon-customer.svg create mode 100644 src/assets/icons/icon-logout.svg diff --git a/src/assets/icons/icon-customer.svg b/src/assets/icons/icon-customer.svg new file mode 100644 index 0000000..2091b15 --- /dev/null +++ b/src/assets/icons/icon-customer.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/icons/icon-logout.svg b/src/assets/icons/icon-logout.svg new file mode 100644 index 0000000..ef2715e --- /dev/null +++ b/src/assets/icons/icon-logout.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts index bd52db4..81af845 100644 --- a/src/assets/icons/index.ts +++ b/src/assets/icons/index.ts @@ -15,8 +15,11 @@ import HelpIcon from './icon-help.svg'; import SearchIcon from './icon-search.svg'; import CheckIcon from './icon-checked.svg'; import UnCheckedIcon from './icon-unchecked.svg'; +import LogoutIcon from './icon-logout.svg'; +import CustomerIcon from './icon-customer.svg'; export { + CustomerIcon, Logo, CameraIcon, TractorBlackIcon, @@ -34,4 +37,5 @@ export { SearchIcon, CheckIcon, UnCheckedIcon, + LogoutIcon, }; From 98a0ab863d5cbdeab207f8628248003a42e4a108 Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Sun, 29 Jun 2025 03:03:54 +0900 Subject: [PATCH 03/15] feat : implement `UserScreen` --- src/app/global.css | 2 +- src/screen/user/ui/UserScreen.tsx | 50 +++++++++++++++++++++++++++++++ src/screen/user/ui/index.ts | 1 + 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/screen/user/ui/UserScreen.tsx create mode 100644 src/screen/user/ui/index.ts diff --git a/src/app/global.css b/src/app/global.css index f67e06e..98652f5 100644 --- a/src/app/global.css +++ b/src/app/global.css @@ -13,7 +13,7 @@ --color-mxl: #ecf5fe; --color-s: #191919; - --color-sd: #333; + --color-sd: #919090; --color-sl: #d9d9d9; --color-sxl: #dadada; diff --git a/src/screen/user/ui/UserScreen.tsx b/src/screen/user/ui/UserScreen.tsx new file mode 100644 index 0000000..37ddf6c --- /dev/null +++ b/src/screen/user/ui/UserScreen.tsx @@ -0,0 +1,50 @@ +import { AppScreen } from '@stackflow/plugin-basic-ui'; +import { CenteredAppBar, Dock } from '@/shared/ui'; +import { fetchSessionData, removeSessionData } from '@/shared/utils'; +import type { User } from '@/shared/types'; +import { CustomerIcon, LogoutIcon } from '@/assets/icons'; + +export default function UserScreen() { + const { name, jumin } = fetchSessionData('userInfo') as User; + return ( + <> + +
+
+ +
+

{name}

+

+ {jumin.slice(0, 6)}-{jumin[7]}****** +

+
+
+
+ + +
+
+
+ + + ); +} diff --git a/src/screen/user/ui/index.ts b/src/screen/user/ui/index.ts new file mode 100644 index 0000000..598423a --- /dev/null +++ b/src/screen/user/ui/index.ts @@ -0,0 +1 @@ +export { default as UserScreen } from './UserScreen'; From b36246cc4ab155ba1f150755ec6dcd5ef81ab9eb Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Sun, 29 Jun 2025 03:07:58 +0900 Subject: [PATCH 04/15] feat : add `ReservationScreen` and related functionalities --- .../reservation/ui/ReservationScreen.tsx | 18 ++++++ src/screen/reservation/ui/index.ts | 1 + src/widgets/reservation/api/index.ts | 1 + src/widgets/reservation/api/reservation.ts | 14 +++++ .../reservation/ui/ReservationContainer.tsx | 43 ++++++++++++++ .../reservation/ui/ReservationItem.tsx | 59 +++++++++++++++++++ src/widgets/reservation/ui/index.ts | 2 + 7 files changed, 138 insertions(+) create mode 100644 src/screen/reservation/ui/ReservationScreen.tsx create mode 100644 src/screen/reservation/ui/index.ts create mode 100644 src/widgets/reservation/api/index.ts create mode 100644 src/widgets/reservation/api/reservation.ts create mode 100644 src/widgets/reservation/ui/ReservationContainer.tsx create mode 100644 src/widgets/reservation/ui/ReservationItem.tsx create mode 100644 src/widgets/reservation/ui/index.ts diff --git a/src/screen/reservation/ui/ReservationScreen.tsx b/src/screen/reservation/ui/ReservationScreen.tsx new file mode 100644 index 0000000..ad757fe --- /dev/null +++ b/src/screen/reservation/ui/ReservationScreen.tsx @@ -0,0 +1,18 @@ +import { AppScreen } from '@stackflow/plugin-basic-ui'; +import { CenteredAppBar, Dock } from '@/shared/ui'; +import { ReservationContainer } from '@/widgets/reservation/ui'; + +export default function ReservationScreen() { + return ( + <> + + + + + + ); +} diff --git a/src/screen/reservation/ui/index.ts b/src/screen/reservation/ui/index.ts new file mode 100644 index 0000000..148f80b --- /dev/null +++ b/src/screen/reservation/ui/index.ts @@ -0,0 +1 @@ +export { default as ReservationScreen } from './ReservationScreen'; diff --git a/src/widgets/reservation/api/index.ts b/src/widgets/reservation/api/index.ts new file mode 100644 index 0000000..382270c --- /dev/null +++ b/src/widgets/reservation/api/index.ts @@ -0,0 +1 @@ +export * from './reservation'; diff --git a/src/widgets/reservation/api/reservation.ts b/src/widgets/reservation/api/reservation.ts new file mode 100644 index 0000000..e89f9b5 --- /dev/null +++ b/src/widgets/reservation/api/reservation.ts @@ -0,0 +1,14 @@ +import { get, REQUEST } from '@/shared/api'; +import type { Reservation } from '@/shared/types'; +import { useQuery } from '@tanstack/react-query'; + +const fetchReservation = async () => { + const response = await get({ + request: REQUEST.RESERVATION_SEARCH, + }); + return response.data; +}; + +export const useFetchReservation = () => { + return useQuery({ queryKey: ['reservation'], queryFn: fetchReservation }); +}; diff --git a/src/widgets/reservation/ui/ReservationContainer.tsx b/src/widgets/reservation/ui/ReservationContainer.tsx new file mode 100644 index 0000000..eb2c75c --- /dev/null +++ b/src/widgets/reservation/ui/ReservationContainer.tsx @@ -0,0 +1,43 @@ +import { useFetchReservation } from '../api'; +import ReservationItem from './ReservationItem'; + +export default function ReservationContainer() { + const { data, isError } = useFetchReservation(); + + const renderReservation = () => { + if (isError) + return ( +
+

데이터를 불러오던 중 오류가 발생했어요.

+
+ ); + if (!data) + return ( +
+

데이터를 가져오는 중...

+
+ ); + + if (data.length === 0) { + return ( +
+

예약 내역이 없습니다.

+
+ ); + } + + return ( +
+ {data.map(reservation => ( + + ))} +
+ ); + }; + + return ( +
+ {renderReservation()} +
+ ); +} diff --git a/src/widgets/reservation/ui/ReservationItem.tsx b/src/widgets/reservation/ui/ReservationItem.tsx new file mode 100644 index 0000000..9d9a2c9 --- /dev/null +++ b/src/widgets/reservation/ui/ReservationItem.tsx @@ -0,0 +1,59 @@ +import type { Reservation } from '@/shared/types'; + +interface InfoRowProps { + label: string; + value: string; +} + +const InfoRow = ({ label, value }: InfoRowProps) => ( +
+

{label}

+

{value}

+
+); + +export default function ReservationItem({ + id, + image, + tool, + description, + startDate, + endDate, + location, +}: Reservation) { + const infoItems = [ + { label: '신청 일자', value: startDate }, + { label: '반납 일자', value: endDate }, + { label: '대여소', value: location }, + { label: '예약 현황', value: status || '진행중' }, + ]; + + return ( +
+
+ +
+

{tool}

+

+ {description || ''} +

+
+
+
+
+ {infoItems.map(({ label, value }) => ( + + ))} +
+ +
+ ); +} diff --git a/src/widgets/reservation/ui/index.ts b/src/widgets/reservation/ui/index.ts new file mode 100644 index 0000000..e375fa1 --- /dev/null +++ b/src/widgets/reservation/ui/index.ts @@ -0,0 +1,2 @@ +export { default as ReservationContainer } from './ReservationContainer'; +export { default as ReservationItem } from './ReservationItem'; From bc963bb9b3f0fb52621e507473cc5a5871b87302 Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Sun, 29 Jun 2025 03:08:21 +0900 Subject: [PATCH 05/15] feat : add endpoint fetching reservations --- src/shared/api/request.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared/api/request.ts b/src/shared/api/request.ts index 3440d87..96153c8 100644 --- a/src/shared/api/request.ts +++ b/src/shared/api/request.ts @@ -1,4 +1,5 @@ export const REQUEST = { JOIN: '/api/ocr/idcard', PHOTO_UPLOAD: '/chat/image', + RESERVATION_SEARCH: '/api/reserve', }; From f6a3b00f817b3b3fb4418222e2bb47d011932e04 Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Sun, 29 Jun 2025 03:09:02 +0900 Subject: [PATCH 06/15] feat : add `ReservationScreen` and `UserScreen` at stackflow config --- src/app/stackflow/Stack.tsx | 5 +++++ src/shared/constants/dock.tsx | 28 ++++++++++++++-------------- src/shared/constants/path.ts | 2 ++ src/shared/types/reservation.ts | 3 +++ src/shared/ui/AppBar.tsx | 6 ++++++ src/shared/ui/Dock.tsx | 5 ++++- src/shared/ui/ToolButton.tsx | 4 ++-- 7 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/app/stackflow/Stack.tsx b/src/app/stackflow/Stack.tsx index 4e021a2..b8ee044 100644 --- a/src/app/stackflow/Stack.tsx +++ b/src/app/stackflow/Stack.tsx @@ -5,6 +5,8 @@ import { JoinScreen } from '@/screen/join/ui'; import { PhotoLoadingScreen } from '@/screen/photo-loading/ui'; import { PhotoResultScreen } from '@/screen/photo-result/ui'; import { PhotoUploadScreen } from '@/screen/photo-upload/ui'; +import { ReservationScreen } from '@/screen/reservation/ui'; +import { UserScreen } from '@/screen/user/ui'; import { fetchSessionData } from '@/shared/utils'; import { basicUIPlugin } from '@stackflow/plugin-basic-ui'; import { basicRendererPlugin } from '@stackflow/plugin-renderer-basic'; @@ -20,11 +22,14 @@ export const { Stack, useFlow } = stackflow({ CompleteScreen, PhotoLoadingScreen, PhotoResultScreen, + ReservationScreen, + UserScreen, }, plugins: [ basicRendererPlugin(), basicUIPlugin({ theme: 'cupertino', + backgroundColor: '#F9F9F9', }), ], initialActivity: () => { diff --git a/src/shared/constants/dock.tsx b/src/shared/constants/dock.tsx index 7b1c649..f6aed85 100644 --- a/src/shared/constants/dock.tsx +++ b/src/shared/constants/dock.tsx @@ -3,28 +3,28 @@ import { PATH } from './path'; import { HomeIcon, HomeSelectedIcon, - // ReservationIcon, - // ReservationSelectedIcon, - // UserIcon, - // UserSelectedIcon, + ReservationIcon, + ReservationSelectedIcon, + UserIcon, + UserSelectedIcon, } from '@/assets/icons'; export const DOCK = { - // ['']: { - // title: '예약 현황', - // icon: , - // selectedIcon: , - // }, + [PATH.RESERVATION]: { + title: '예약 현황', + icon: , + selectedIcon: , + }, [PATH.HOME]: { title: '홈', icon: , selectedIcon: , }, - // ['.']: { - // title: '나의 정보', - // icon: , - // selectedIcon: , - // }, + [PATH.USER]: { + title: '나의 정보', + icon: , + selectedIcon: , + }, }; export const DOCK_ITEMS = Object.keys(DOCK) as Array; diff --git a/src/shared/constants/path.ts b/src/shared/constants/path.ts index bae17f3..8797336 100644 --- a/src/shared/constants/path.ts +++ b/src/shared/constants/path.ts @@ -6,4 +6,6 @@ export const PATH = { PHOTO_LOADING: 'PhotoLoadingScreen', PHOTO_RESULT: 'PhotoResultScreen', FORM: 'FormScreen', + RESERVATION: 'ReservationScreen', + USER: 'UserScreen', } as const; diff --git a/src/shared/types/reservation.ts b/src/shared/types/reservation.ts index 399091a..4e57534 100644 --- a/src/shared/types/reservation.ts +++ b/src/shared/types/reservation.ts @@ -1,7 +1,10 @@ export type Reservation = { + id: number; tool: string; startDate: string; endDate: string; location: string; userName: string; + description: string; + image: string; }; diff --git a/src/shared/ui/AppBar.tsx b/src/shared/ui/AppBar.tsx index 27c8510..05a3ea5 100644 --- a/src/shared/ui/AppBar.tsx +++ b/src/shared/ui/AppBar.tsx @@ -37,3 +37,9 @@ export const NormalAppBar = (title: string, bgImage?: string) => ({ }, ...baseStyle, }); + +export const CenteredAppBar = (title: string) => ({ + ...baseStyle, + title: title, + backgroundColor: '#FFF', +}); diff --git a/src/shared/ui/Dock.tsx b/src/shared/ui/Dock.tsx index 581f2d2..61c3784 100644 --- a/src/shared/ui/Dock.tsx +++ b/src/shared/ui/Dock.tsx @@ -23,7 +23,10 @@ export default function Dock(isLoading: DockProps) { .map(i => i.name) .pop() as PathItem; - const render = current === PATH.HOME; + const render = + current === PATH.HOME || + current === PATH.RESERVATION || + current === PATH.USER; return ( <> diff --git a/src/shared/ui/ToolButton.tsx b/src/shared/ui/ToolButton.tsx index 6bef11e..3dab7ce 100644 --- a/src/shared/ui/ToolButton.tsx +++ b/src/shared/ui/ToolButton.tsx @@ -25,12 +25,12 @@ export default function ToolButton({ {...rest} >

{toolType}

-

+

{description}

From c809a65616cbb942a738d7837720f1b738eb7717 Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Sun, 29 Jun 2025 03:59:35 +0900 Subject: [PATCH 07/15] feat : add icons for `ToolPickModal` --- src/assets/icons/icon-close.svg | 4 ++++ src/assets/icons/icon-search-colored.svg | 4 ++++ src/assets/icons/index.ts | 4 ++++ 3 files changed, 12 insertions(+) create mode 100644 src/assets/icons/icon-close.svg create mode 100644 src/assets/icons/icon-search-colored.svg diff --git a/src/assets/icons/icon-close.svg b/src/assets/icons/icon-close.svg new file mode 100644 index 0000000..7f0b0b0 --- /dev/null +++ b/src/assets/icons/icon-close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/icon-search-colored.svg b/src/assets/icons/icon-search-colored.svg new file mode 100644 index 0000000..183ae17 --- /dev/null +++ b/src/assets/icons/icon-search-colored.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts index 81af845..ba03eed 100644 --- a/src/assets/icons/index.ts +++ b/src/assets/icons/index.ts @@ -17,8 +17,12 @@ import CheckIcon from './icon-checked.svg'; import UnCheckedIcon from './icon-unchecked.svg'; import LogoutIcon from './icon-logout.svg'; import CustomerIcon from './icon-customer.svg'; +import CloseIcon from './icon-close.svg'; +import SearchColoredIcon from './icon-search-colored.svg'; export { + CloseIcon, + SearchColoredIcon, CustomerIcon, Logo, CameraIcon, From 035974de9e09636253f661cf5d3fc24f418be507 Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Sun, 29 Jun 2025 04:00:14 +0900 Subject: [PATCH 08/15] feat : implement location & tool fetching logic --- src/shared/api/request.ts | 1 + src/widgets/form/api/index.ts | 2 ++ src/widgets/form/api/location.ts | 15 +++++++++++++++ src/widgets/form/api/tools.ts | 17 +++++++++++++++++ 4 files changed, 35 insertions(+) create mode 100644 src/widgets/form/api/index.ts create mode 100644 src/widgets/form/api/location.ts create mode 100644 src/widgets/form/api/tools.ts diff --git a/src/shared/api/request.ts b/src/shared/api/request.ts index 96153c8..420ef65 100644 --- a/src/shared/api/request.ts +++ b/src/shared/api/request.ts @@ -2,4 +2,5 @@ export const REQUEST = { JOIN: '/api/ocr/idcard', PHOTO_UPLOAD: '/chat/image', RESERVATION_SEARCH: '/api/reserve', + LOCATION: '/api/locations/', }; diff --git a/src/widgets/form/api/index.ts b/src/widgets/form/api/index.ts new file mode 100644 index 0000000..e5c4243 --- /dev/null +++ b/src/widgets/form/api/index.ts @@ -0,0 +1,2 @@ +export * from './location'; +export * from './tools'; diff --git a/src/widgets/form/api/location.ts b/src/widgets/form/api/location.ts new file mode 100644 index 0000000..4c5d3a6 --- /dev/null +++ b/src/widgets/form/api/location.ts @@ -0,0 +1,15 @@ +import { get, REQUEST } from '@/shared/api'; +import { useQuery } from '@tanstack/react-query'; +import type { Location } from '../types'; + +const fetchLocation = async () => { + const response = await get({ request: REQUEST.LOCATION }); + return response.data; +}; + +export const useFetchLocation = () => { + return useQuery({ + queryKey: ['location'], + queryFn: fetchLocation, + }); +}; diff --git a/src/widgets/form/api/tools.ts b/src/widgets/form/api/tools.ts new file mode 100644 index 0000000..4337e9d --- /dev/null +++ b/src/widgets/form/api/tools.ts @@ -0,0 +1,17 @@ +import { get } from '@/shared/api'; +import { useQuery } from '@tanstack/react-query'; +import type { PickTool } from '../types'; + +const fetchToolsByLocation = async (locationId: number) => { + const response = await get({ + request: `api/locations/${locationId}/tools`, + }); + return response.data; +}; + +export const useFetchToolsByLocation = (locationId: number) => { + return useQuery({ + queryKey: ['tools', locationId], + queryFn: () => fetchToolsByLocation(locationId), + }); +}; From d36b3806f86f804e69de6fd1a6e2c44269f8f42b Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Sun, 29 Jun 2025 04:01:03 +0900 Subject: [PATCH 09/15] feat : implement `ToolPickModal` --- src/shared/constants/modal.ts | 2 +- src/shared/hooks/index.ts | 1 + src/shared/hooks/useModal.ts | 25 ++++++ src/shared/ui/Modal.tsx | 40 +++++++++ src/shared/ui/index.ts | 1 + src/widgets/form/ui/ToolPickModal.tsx | 118 ++++++++++++++++++++++++++ src/widgets/form/ui/index.ts | 1 + 7 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/shared/hooks/useModal.ts create mode 100644 src/shared/ui/Modal.tsx create mode 100644 src/widgets/form/ui/ToolPickModal.tsx diff --git a/src/shared/constants/modal.ts b/src/shared/constants/modal.ts index 4532ee0..7e4752d 100644 --- a/src/shared/constants/modal.ts +++ b/src/shared/constants/modal.ts @@ -1,3 +1,3 @@ export const MODAL = { - TOOL_SEARCH: 'toolSearchModal', + TOOL_PICK: 'toolPickModal', } as const; diff --git a/src/shared/hooks/index.ts b/src/shared/hooks/index.ts index e9dd277..79645e1 100644 --- a/src/shared/hooks/index.ts +++ b/src/shared/hooks/index.ts @@ -1 +1,2 @@ export { default as useImageUpload } from './useImageUpload'; +export { default as useModal } from './useModal'; diff --git a/src/shared/hooks/useModal.ts b/src/shared/hooks/useModal.ts new file mode 100644 index 0000000..0ac8117 --- /dev/null +++ b/src/shared/hooks/useModal.ts @@ -0,0 +1,25 @@ +import { useAtomValue, useSetAtom } from 'jotai'; + +import { modalAtom, updateModal } from '../atom'; +import type { ModalItem } from '../types'; + +export default function useModal() { + const modal = useAtomValue(modalAtom); + const setModal = useSetAtom(updateModal); + + const modalState = (key: ModalItem) => modal[key] || { isOpen: false }; + + const openModal = (key: ModalItem) => { + setModal({ key, isOpen: true }); + }; + + const closeModal = (key: ModalItem) => { + setModal({ key, isOpen: false }); + }; + + return { + modalState, + openModal, + closeModal, + }; +} diff --git a/src/shared/ui/Modal.tsx b/src/shared/ui/Modal.tsx new file mode 100644 index 0000000..6f94c27 --- /dev/null +++ b/src/shared/ui/Modal.tsx @@ -0,0 +1,40 @@ +import { type ReactNode } from 'react'; + +import { cn } from '../utils'; +import type { ModalItem } from '../types'; + +interface ModalProps { + children: ReactNode; + modalKey: ModalItem; + coloredBg?: boolean; + className?: string; +} + +export default function Modal({ + children, + modalKey, + coloredBg = false, + className, +}: ModalProps) { + return ( + <> +
+
+ {children} +
+
+ + ); +} diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts index 383cf2b..7b7df16 100644 --- a/src/shared/ui/index.ts +++ b/src/shared/ui/index.ts @@ -3,3 +3,4 @@ export { default as Dock } from './Dock'; export { default as Button } from './Button'; export { default as Loader } from './Loader'; export { default as ToolButton } from './ToolButton'; +export { default as Modal } from './Modal'; diff --git a/src/widgets/form/ui/ToolPickModal.tsx b/src/widgets/form/ui/ToolPickModal.tsx new file mode 100644 index 0000000..3dbf0ec --- /dev/null +++ b/src/widgets/form/ui/ToolPickModal.tsx @@ -0,0 +1,118 @@ +import { CloseIcon, SearchColoredIcon, SearchIcon } from '@/assets/icons'; +import { MODAL } from '@/shared/constants'; +import { useModal } from '@/shared/hooks'; +import { Button, Modal, ToolButton } from '@/shared/ui'; +import { useState } from 'react'; +import { useFetchLocation, useFetchToolsByLocation } from '../api'; +import { cn } from '@/shared/utils'; + +export default function ToolPickModal() { + const { modalState, closeModal } = useModal(); + const { isOpen } = modalState(MODAL.TOOL_PICK); + + const [search, setSearch] = useState(''); + const [selectedTool, setSelectedTool] = useState(''); + const [selectedLocation, setSelectedLocation] = useState(1); + + const { data: location } = useFetchLocation(); + const { data: tools } = useFetchToolsByLocation(selectedLocation); + + const renderButton = () => { + if (location) { + return location.map(({ id, locationName }) => ( + + )); + } + return <>; + }; + + const renderTools = () => { + if (tools) { + const filteredTools = search.trim() + ? tools.filter( + ({ tool, description }) => + tool.toLowerCase().includes(search.toLowerCase()) || + description.toLowerCase().includes(search.toLowerCase()), + ) + : tools; + + if (filteredTools.length === 0) { + return ( +
+

검색 결과가 없습니다.

+
+ ); + } + + return filteredTools.map(({ tool, quantity, description, image }) => ( + { + if (selectedTool === tool) { + setSelectedTool(''); + } else { + setSelectedTool(tool); + } + }} + /> + )); + } + }; + + return ( + <> + {isOpen && ( + +

농기계 검색 창

+ +
+ setSearch(e.target.value)} + className="focus:border-m w-[244px] rounded-md border-[1px] border-[#e1e1e1] bg-white px-[15px] py-[12px] focus:outline-none" + /> + +
+
+ {renderButton()} +
+
+ {renderTools()} +
+ +
+ )} + + ); +} diff --git a/src/widgets/form/ui/index.ts b/src/widgets/form/ui/index.ts index b7a14c5..534a85b 100644 --- a/src/widgets/form/ui/index.ts +++ b/src/widgets/form/ui/index.ts @@ -1 +1,2 @@ export { default as FormContainer } from './FormContainer'; +export { default as ToolPickModal } from './ToolPickModal'; From 1da7a2ac34f012862be0f5fa49bc84de7cf0e1e8 Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Sun, 29 Jun 2025 04:01:51 +0900 Subject: [PATCH 10/15] feat : add types for location and tool --- src/widgets/form/types/index.ts | 2 ++ src/widgets/form/types/location.ts | 6 ++++++ src/widgets/form/types/tool.ts | 7 +++++++ 3 files changed, 15 insertions(+) create mode 100644 src/widgets/form/types/index.ts create mode 100644 src/widgets/form/types/location.ts create mode 100644 src/widgets/form/types/tool.ts diff --git a/src/widgets/form/types/index.ts b/src/widgets/form/types/index.ts new file mode 100644 index 0000000..8dd0de9 --- /dev/null +++ b/src/widgets/form/types/index.ts @@ -0,0 +1,2 @@ +export * from './location'; +export * from './tool'; diff --git a/src/widgets/form/types/location.ts b/src/widgets/form/types/location.ts new file mode 100644 index 0000000..7209ba5 --- /dev/null +++ b/src/widgets/form/types/location.ts @@ -0,0 +1,6 @@ +export type Location = { + id: number; + locationName: string; + address: string; + phone: string; +}; diff --git a/src/widgets/form/types/tool.ts b/src/widgets/form/types/tool.ts new file mode 100644 index 0000000..c1735c0 --- /dev/null +++ b/src/widgets/form/types/tool.ts @@ -0,0 +1,7 @@ +export type PickTool = { + tool: string; + quantity: number; + description: string; + image: string; + price: number; +}; From 07da01dfaee30af87887aa84150def2737fb7551 Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Sun, 29 Jun 2025 04:01:56 +0900 Subject: [PATCH 11/15] feat : integrate `ToolPickModal` into `FormScreen` and update `FormContainer` to open modal on search click --- src/screen/form/ui/FormScreen.tsx | 22 +++++++++++++--------- src/shared/ui/ToolButton.tsx | 6 ++++-- src/widgets/form/ui/FormContainer.tsx | 17 +++++++++++++++-- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/screen/form/ui/FormScreen.tsx b/src/screen/form/ui/FormScreen.tsx index 26c9dab..2148da6 100644 --- a/src/screen/form/ui/FormScreen.tsx +++ b/src/screen/form/ui/FormScreen.tsx @@ -1,17 +1,21 @@ import { NormalAppBar } from '@/shared/ui'; import { FormContainer } from '@/widgets/form/ui'; +import ToolPickModal from '@/widgets/form/ui/ToolPickModal'; import { AppScreen } from '@stackflow/plugin-basic-ui'; export default function PhotoUploadScreen() { return ( - - - + <> + + + + + ); } diff --git a/src/shared/ui/ToolButton.tsx b/src/shared/ui/ToolButton.tsx index 3dab7ce..f7f63bb 100644 --- a/src/shared/ui/ToolButton.tsx +++ b/src/shared/ui/ToolButton.tsx @@ -7,6 +7,7 @@ interface ToolButtonProps extends ButtonHTMLAttributes { toolType: string; description: string; selected: boolean; + quantity?: number; } export default function ToolButton({ @@ -14,12 +15,13 @@ export default function ToolButton({ toolType, description, selected, + quantity, ...rest }: ToolButtonProps) { return ( - +
+ + 임대요금 + + {selectedPrice}원 +
+
+
- + ); } @@ -46,19 +114,23 @@ const FormItem = ({ label, type, onClick, + value, + onChange, }: { label: string; type: string; onClick?: () => void; + value?: string; + onChange?: (e: React.ChangeEvent) => void; }) => (
{label} {type === 'search' ? ( - + ) : ( - + )}
); diff --git a/src/widgets/form/ui/FormInput.tsx b/src/widgets/form/ui/FormInput.tsx index b27391b..2ff9325 100644 --- a/src/widgets/form/ui/FormInput.tsx +++ b/src/widgets/form/ui/FormInput.tsx @@ -15,9 +15,11 @@ export default function FormInput({ if (isSearch) { return ( @@ -53,23 +66,27 @@ export default function ToolPickModal() { ); } - return filteredTools.map(({ tool, quantity, description, image }) => ( - { - if (selectedTool === tool) { - setSelectedTool(''); - } else { - setSelectedTool(tool); - } - }} - /> - )); + return filteredTools.map( + ({ tool, quantity, description, image, price }) => ( + { + if (selectedTool === tool) { + setSelectedTool(''); + setSelectedPrice(0); + } else { + setSelectedTool(tool); + setSelectedPrice(price); + } + }} + /> + ), + ); } }; @@ -105,9 +122,11 @@ export default function ToolPickModal() { {renderTools()} diff --git a/src/widgets/reservation/ui/ReservationItem.tsx b/src/widgets/reservation/ui/ReservationItem.tsx index 9d9a2c9..ace2fb5 100644 --- a/src/widgets/reservation/ui/ReservationItem.tsx +++ b/src/widgets/reservation/ui/ReservationItem.tsx @@ -35,7 +35,9 @@ export default function ReservationItem({ >