wms_web은 창고관리 시스템용 프론트엔드 애플리케이션입니다. 탭 기반 작업 흐름과 인증/토큰 관리, Keep-Alive 캐싱, 안정적인 네트워크 계층을 중심으로 설계되었습니다. React + TypeScript + Vite 환경에서 동작하며, @tanstack/react-query와 Zustand를 중심으로 데이터/상태를 관리합니다.
- 🔄 탭 기반 작업 흐름: 복수 페이지를 탭 단위로 관리하여 빠른 전환과 상태 보존을 지원
- 🔐 인증/토큰 관리: Axios 인터셉터로 액세스 토큰 자동 주입 및 refresh token 재발급 처리
- 💾 Keep-Alive 캐시: 탭 이동 시 컴포넌트 상태를 보존하여 사용자 경험 향상
- ⛔ Dirty State 보호: 미저장 변경 시 브라우저 새로고침/탭 이동 경고
- 🎯 타입 안전성: TypeScript 기반 설계로 안정적인 코드 유지
- 🌐 데이터 계층 분리: API / query / mutation / 캐시 invalidation을 명확히 분리
- React 19.2.4
- TypeScript ~6.0.2
- Vite 8.0.4
- Zustand 5.0.12
- @tanstack/react-query 5.99.0
- @mui/material 9.0.1
- @mui/icons-material 9.0.1
- Tailwind CSS 4.2.2
- @emotion/react 11.14.0
- @emotion/styled 11.14.1
- react-hook-form 7.75.0
- axios 1.15.1
- react-router-dom 7.14.2
- qs 6.15.1
- ESLint + Prettier
- Babel
- PostCSS + Tailwind
- @vitejs/plugin-react
src/main.tsx: 앱 시작점, 글로벌 Provider 등록src/App.tsx: 라우터 기반 앱 구조
src/app/providers/QueryClient.ts: React Query 전역 설정src/app/providers/QueryProvider.tsx:QueryClientProvider등록src/app/providers/ThemeProvider.tsx: MUI + 테마 적용src/app/providers/ToastProviders.tsx: 전역 toast 환경 구성src/app/providers/RouterProvider.tsx: 라우터 최상위 Providersrc/app/providers/ErrorBoundaryProvider.tsx: 최상위 렌더링 레벨 에러 처리 Provider
src/app/store/authStore.ts: 인증 토큰, 권한, 로그인 상태src/app/store/tabStore.ts: 탭 목록 및 활성 탭 관리src/app/store/dirtyStore.ts: 폼/데이터 변경 감지src/app/store/keepAliveStore.ts: 컴포넌트 Keep-Alive 캐시src/app/store/uiStore.ts: 글로벌 로딩 상태
src/app/router/KeepAliveCache.ts: 탭별 컴포넌트 캐싱src/app/router/TabRouterSync.ts: URL-탭 상태 동기화src/app/router/ProtectedRoute.tsx: 인증/권한 보호src/app/router/TabErrorBoundary.tsx: 탭 단위 에러 격리src/app/hooks/useBeforeUnload.ts: 페이지 나가기 차단src/app/hooks/useRouterBlocker.ts: 라우터 전환 차단
src/shared/api/client/axiosClient.ts: 공통 API 인스턴스src/shared/api/client/authClient.ts: 로그인/refresh 전용 인스턴스src/shared/api/utils/fetcher.ts: API 응답 표준화src/shared/api/error/ApiError.ts: 공통 API 예외src/shared/api/error/errorCode.ts: 에러 코드 정의src/shared/api/error/errorHandler.ts: 인증/세션 검사 유틸src/shared/api/interceptor/requestInterceptor.ts: 요청 전처리src/shared/api/interceptor/responseInterceptor.ts: 응답 후처리src/shared/api/auth/refreshTokenService.ts: refresh token 로직src/shared/api/auth/sessionService.ts: 세션 만료 처리
src/shared/lib/query/createQueryKeys.ts: query key factorysrc/shared/lib/query/createListQueryOptions.ts: 리스트 query 옵션src/shared/lib/query/createMutationOptions.ts: mutation 옵션 공통화src/shared/lib/query/invalidation.ts: query invalidation helpersrc/shared/lib/query/normalizeParams.ts: 파라미터 정규화src/shared/lib/toast/toastHandler.ts: API 오류 toast 연결
src/
├── App.tsx
├── index.css
├── main.tsx
├── app/
│ ├── hooks/
│ │ ├── useBeforeUnload.ts
│ │ └── useRouterBlocker.ts
│ ├── providers/
│ │ ├── ErrorBoundaryProvider.tsx
│ │ ├── QueryClient.ts
│ │ ├── QueryProvider.tsx
│ │ ├── RouterProvider.tsx
│ │ ├── ThemeProvider.tsx
│ │ └── ToastProviders.tsx
│ ├── router/
│ │ ├── index.tsx
│ │ ├── KeepAliveCache.ts
│ │ ├── ProtectedRoute.tsx
│ │ ├── routerConfig.ts
│ │ ├── routes.tsx
│ │ ├── TabErrorBoundary.tsx
│ │ └── TabRouterSync.tsx
│ ├── store/
│ │ ├── authStore.ts
│ │ ├── dirtyStore.ts
│ │ ├── index.ts
│ │ ├── keepAliveStore.ts
│ │ ├── tabStore.ts
│ │ └── uiStore.ts
│ └── theme/
│ ├── components.ts
│ ├── index.ts
│ ├── palette.ts
│ └── typography.ts
├── assets/
│ └── bg-login.png
├── components/
│ ├── common/
│ │ ├── button/
│ │ │ ├── BaseButton.tsx
│ │ │ ├── HeaderUserMenu.tsx
│ │ │ └── index.ts
│ │ ├── checkbox/
│ │ │ ├── BaseCheckbox.tsx
│ │ │ └── index.ts
│ │ ├── input/
│ │ │ ├── BaseInput.tsx
│ │ │ └── index.ts
│ │ └── modal/
│ │ ├── AlertModal.tsx
│ │ └── index.ts
│ ├── grid/
│ │ ├── BaseGrid.tsx
│ │ └── DataInfoGrid.tsx
│ ├── layout/
│ │ ├── DashboardLayout.tsx
│ │ ├── Header.tsx
│ │ ├── Layout.tsx
│ │ ├── Sidebar.tsx
│ │ ├── TabLayout.tsx
│ │ └── Tabs.tsx
│ └── pagination/
│ └── Basepagination.tsx
├── pages/
│ ├── Dashboard/
│ │ └── ui/
│ │ └── DashboardPage.tsx
│ ├── Inbound/
│ │ └── ui/
│ │ └── InboundPage.tsx
│ ├── Login/
│ │ ├── api/
│ │ │ └── loginApi.ts
│ │ ├── hooks/
│ │ │ └── useLogin.ts
│ │ ├── model/
│ │ │ ├── loginMutation.ts
│ │ │ ├── loginTypes.ts
│ │ │ ├── mutationKeys.ts
│ │ │ └── useLoginMutation.ts
│ │ └── ui/
│ │ └── LoginPage.tsx
│ ├── Outbound/
│ │ └── ui/
│ │ └── OutboundPage.tsx
│ └── Test/
│ ├── api/
│ │ └── testApi.tsx
│ ├── hooks/
│ │ └── useTest.ts
│ ├── model/
│ │ ├── mutationKeys.ts
│ │ ├── queryKeys.ts
│ │ ├── testMutations.ts
│ │ ├── testQueries.ts
│ │ ├── testTypes.ts
│ │ ├── useTestQuery.ts
│ │ └── useUpdateTestMutation.ts
│ └── ui/
│ └── TestPage.tsx
└── shared/
├── api/
│ ├── auth/
│ │ ├── refreshTokenService.ts
│ │ ├── sessionService.ts
│ │ └── sessionTimer.ts
│ ├── client/
│ │ ├── authClient.ts
│ │ └── axiosClient.ts
│ ├── error/
│ │ ├── ApiError.ts
│ │ ├── errorCode.ts
│ │ └── errorHandler.ts
│ ├── interceptor/
│ │ ├── refreshQueue.ts
│ │ ├── requestInterceptor.ts
│ │ └── responseInterceptor.ts
│ ├── types/
│ │ └── responseTypes.ts
│ └── utils/
│ └── fetcher.ts
├── config/
│ └── tab.ts
├── constants/
│ ├── message.ts
│ ├── roles.ts
│ ├── routes.ts
│ └── stogare.ts
├── hooks/
│ ├── useActiveTab.ts
│ └── useFormDirty.ts
├── lib/
│ ├── query/
│ │ ├── createListQueryOptions.ts
│ │ ├── createMutationOptions.ts
│ │ ├── createQueryKeys.ts
│ │ ├── invalidation.ts
│ │ ├── normalizeParams.ts
│ │ └── paginationTypes.ts
│ ├── toast/
│ │ ├── toast.ts
│ │ ├── toastHandler.ts
│ │ └── toastMessage.ts
│ └── ui/
│ └── ErrorFallback.tsx
├── styles/
│ └── globals.css
└── utils/
└── sha512.ts
wms_web의 데이터 조회 계층은 @tanstack/react-query를 중심으로 설계되어 있습니다.
- 도메인별 query key factory로 일관된 쿼리 키 생성
- 탭 ID를 포함하여 탭별 캐시 분리
- 활성 탭 전용 요청, 비활성 탭에서는 쿼리 취소
QueryClient기본 설정으로 캐시/재요청 정책 통제
QueryClient.tsstaleTime: 1분gcTime: 5분refetchOnWindowFocus: falserefetchOnReconnect: trueretry: 1throwOnError: false
createQueryKeys.ts- 도메인별
list,listBy,detail,search등의 key factory
- 도메인별
createListQueryOptions.ts- 파라미터 정규화
queryKey에tabId병합- 리스트 조회 옵션 일관화
useTestQuery.tsenabled제어로 활성 탭에서만 fetch- 비활성 탭에서는
cancelQueries()호출 - 다시 활성화 시
query.isStale기반refetch()
- 화면 훅에서
queryOptions생성 useQuery()호출 및 캐시 확인- 필요한 경우 API 호출 수행
- 결과는 react-query 캐시에 저장되어 재사용
Mutation 계층은 API 변경 작업을 표준화하고 후처리를 공통화합니다.
- mutation은
model레이어에서 정의하고, hook에서useMutation으로 감싼다 - 성공 시 관련 쿼리를 invalidation하여 최신 데이터 유지
- 실패 시 공통 toast 에러 처리
- CUD 작업은 재시도를 최소화
mutationKeys.ts: 도메인별 mutation key 생성testMutations.ts,loginMutation.ts: mutation 정의useUpdateTestMutation.ts,useLoginMutation.ts: mutation hookcreateMutationOptions.tsqueryClient와invalidateKeys연결successMessage자동 toastshowApiError()공통 에러 처리
useUpdateTestMutation은testKeys.list()를 invalidation하여 테스트 리스트를 갱신useLoginMutation은 로그인 성공 시authStore에 토큰/role 저장
- 공통 API:
axiosClient.ts - 인증 전용 API:
authClient.ts - fetcher:
fetcher.ts - 에러/로그인 관리:
errorHandler.ts,ApiError.ts,errorCode.ts - 토큰 갱신:
refreshTokenService.ts - 세션 만료:
sessionService.ts
- 기본 URL:
VITE_API_BASE_URL - timeout 360초
Content-Type: application/jsonqs기반 params serializer- request/response interceptor 등록
- request interceptor
Authorization헤더 자동 주입- 로딩 상태 증가/감소
multipart/form-data제외 body wrappingPOST/PUT/PATCH요청 body를{ data: ... }형태로 래핑
- response interceptor
- 세션 만료 시 로그아웃 및 로그인 화면 이동
- 액세스 토큰 만료 시 refresh token 흐름 실행
- 동시 요청 시 refresh token 큐 처리
fetcher.ts는 API responsecode가1000일 때만 데이터를 반환- 비즈니스 에러 시
ApiErrorthrow showApiError()는ApiError코드에 따라 toast 메시지 매핑
KeepAliveCache로 탭 간 컴포넌트 상태 유지TabRouterSync로 URL과 탭 상태 동기화TabErrorBoundary로 탭 단위 에러 격리
useBeforeUnload와useRouterBlocker로 미저장 변경 보호dirtyStore로 변경 상태 중앙 관리uiStore로 글로벌 로딩/로딩 카운트 관리authStore로 인증 상태 동기화
- MUI + Tailwind 기반 UI
ThemeProvider로 디자인 토큰 적용ToastProviders로 전역 알림 구성Header,Sidebar,TabLayout등의 공통 레이아웃 컴포넌트
npm install
npm run dev브라우저에서 http://localhost:5173로 접근하세요.
npm run build
npm run previewVITE_API_BASE_URL: 백엔드 API 기본 URL
- 인증 요청과 refresh 요청을 분리한
authClient로 인터셉터 순환 문제 방지 react-queryquery key는 탭 ID를 포함해 탭별 캐시 분리createMutationOptions로 success toast, invalidation, 공통 오류 처리를 일관되게 적용