동국대학교 축제 주점 운영을 위한 관리자용 대시보드입니다! 🍺
디오더는 동국대 축제 주점에서 사용하는 실시간 주문 관리 서비스입니다.
https://taejun0.github.io/2025-d-order-fe-admin-v2/
| 분류 | 기술 |
|---|---|
| 프레임워크 | React 19 + TypeScript |
| 빌드 도구 | Vite 6 |
| 스타일링 | Styled Components |
| 상태 관리 | Zustand -> API |
| 라우팅 | React Router DOM 7 |
| HTTP 클라이언트 | Axios |
npm install
npm run devhttp://localhost:5173에서 바로 확인할 수 있습니다.
문제점:
- 기존에는
Zustand를 사용한 클라이언트 전역 상태 관리로 장바구니를 관리 - 페이지 새로고침, 여러 탭 사용, 로그인/로그아웃 시 장바구니 데이터 손실 발생
해결 방안:
- 서버 기반 아키텍처로 전환: 서버를 "Source of Truth"로 설정하고, 클라이언트는 캐싱과 표시만 담당
cart_id관리:localStorage에cart_id를 저장하고 이후 요청에 함께 전송하여 세션/탭 간 동일한 장바구니 유지- React Query 도입: API 데이터 캐싱 및 무효화 전략 구현
- 장바구니 수정 시 자동 캐시 무효화
- 메뉴, 장바구니, 주문 확인 페이지 간 서버-클라이언트 데이터 동기화
구현 위치:
src/services/- API 서비스 레이어- React Query를 통한 데이터 페칭 및 캐싱
목적: 운영진이 실시간 매출 현황을 모니터링하고 누적된 주문 데이터를 기반으로 데이터 기반 의사결정을 할 수 있도록 Dashboard 설계 및 구현
주요 구현 내용:
- 서버로부터 실시간 통계 변경사항을 WebSocket으로 수신
- 메시지 타입별 처리 로직 구현:
INIT_STATISTICS: 초기 통계 데이터 전체 로드STATISTICS_UPDATED: 부분 업데이트 데이터 병합ERROR: 에러 처리
구현 위치:
src/pages/dashboard/_hooks/useStatisticsWS.ts
// WebSocket 메시지 타입별 처리
if (msg?.type === TYPE_INIT) {
onInit(msg.data); // 전체 데이터 교체
} else if (msg?.type === TYPE_PATCH) {
onPatch(msg.data); // 부분 업데이트 병합
}- 서버 응답의
snake_case를 프론트엔드camelCase로 변환 mapDashboardResponse: 초기 로드 시 전체 데이터 변환mapDashboardPatch: 부분 업데이트 데이터 변환
구현 위치:
src/pages/dashboard/_services/dashboard.mapper.ts
// 서버 응답 변환 예시
export function mapDashboardResponse(res: DashboardResponse): DashboardData {
return {
kpi: {
totalOrders: d.total_orders ?? 0,
recentOrders: d.recent_orders ?? 0,
// ...
},
top3: (d.top3_menus ?? []).map((m: any) => ({
name: m?.menu__menu_name ?? '',
imageUrl: withImageBase(m.menu__menu_image) || '/images/Pizza.png',
// ...
})),
};
}- 초기 로드 시 전체 데이터 교체
- 이후 업데이트는
mergeDashboard함수로 부분 병합 - 변경되지 않은 필드의 불필요한 리렌더링 방지
구현 위치:
src/pages/dashboard/_services/dashboard.mapper.ts
export function mergeDashboard(
prev: DashboardData | undefined,
patch: Partial<DashboardData>
): DashboardData {
return {
...prev,
kpi: { ...prev.kpi, ...patch.kpi },
top3: 'top3' in patch ? patch.top3 ?? [] : prev.top3,
// 변경된 필드만 업데이트
};
}- 일일 매출 현황
- 인기 메뉴 TOP 3
- 재고 부족 메뉴
- 평균 대기 시간
- 기타 축제 운영에 중요한 실시간 지표
구현 위치:
src/pages/dashboard/DashboardPage.tsxsrc/pages/dashboard/_components/
목적: 축제 현장에서 장시간 근무하는 운영진을 위한 원활한 인증 경험 제공
주요 구현 내용:
- 모든 API 요청에
localStorage의accessToken을 자동으로Authorization헤더에 추가 - 로그인 없이도 자동 인증 가능
구현 위치:
src/services/instance.ts
instance.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken');
if (token && !config.url?.includes('/api/v2/manager/auth/')) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
});- 401 에러 발생 시 쿠키의
Refresh Token을 사용하여 자동으로 새로운Access Token발급 및 저장 - 인증 세션 유지
구현 위치:
src/services/instance.ts
instance.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401 && !originalRequest._retry) {
// Refresh Token으로 새 Access Token 발급
const res = await instance.get('/api/v2/manager/auth/');
const newAccessToken = res.data?.data?.access;
setAccessToken(newAccessToken);
// 원본 요청 재시도
return instance(originalRequest);
}
}
);- 동시에 여러 요청이 401 에러를 받을 때, 첫 번째 요청만 토큰 갱신을 수행
- 나머지 요청은 대기 후 새 토큰으로 재시도
- 중복 토큰 갱신 요청 방지로 서버 부하 감소
구현 위치:
src/services/instance.ts
let isRefreshing = false;
let failedQueue: Array<{
resolve: (token: string) => void;
reject: (err: any) => void;
}> = [];
if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// 이미 갱신 중이면 큐에 추가
return new Promise((resolve, reject) => {
failedQueue.push({
resolve: (token: string) => {
originalRequest.headers['Authorization'] = `Bearer ${token}`;
resolve(instance(originalRequest));
},
reject,
});
});
} else {
// 첫 번째 요청만 토큰 갱신 수행
isRefreshing = true;
// ... 토큰 갱신 로직
processQueue(newAccessToken, null); // 대기 중인 요청들 처리
}
}localStorage에accessToken이 없으면 자동으로 초기화 페이지로 리다이렉트- 보안 강화
구현 위치:
src/hooks/useAuthRedirect.ts
const useAuthRedirect = () => {
const navigate = useNavigate();
useEffect(() => {
const token = localStorage.getItem('accessToken');
if (!token) {
navigate(ROUTE_PATHS.INIT);
}
}, [navigate]);
};src/
├── pages/
│ ├── dashboard/ # WebSocket 기반 실시간 대시보드
│ │ ├── _hooks/
│ │ │ └── useStatisticsWS.ts # WebSocket 통계 수신 훅
│ │ ├── _services/
│ │ │ ├── dashboard.mapper.ts # 타입 변환 및 병합 로직
│ │ │ └── dashboard.types.ts
│ │ └── _components/ # 대시보드 컴포넌트들
│ ├── liveorder_v2/ # 실시간 주문 관리
│ ├── tableView/ # 테이블 관리
│ ├── menu/ # 메뉴 관리
│ ├── coupon/ # 쿠폰 관리
│ └── mypage/ # 마이페이지
├── services/
│ └── instance.ts # Axios 인스턴스 및 JWT 자동 인증
├── hooks/ # 커스텀 훅
│ └── useAuthRedirect.ts # 인증 리다이렉트 훅
├── components/ # 공통 컴포넌트
└── constants/ # 상수 정의
WebSocket 기반 실시간 통계 대시보드
기능:
- 📊 총 주문 수, 방문자 수 실시간 확인
- 🏆 인기 메뉴 TOP 3
⚠️ 재고 부족 메뉴 알림- 💰 실시간 매출 통계
- ⏱️ 평균 대기 시간 및 테이블 사용률
기술적 특징:
- WebSocket으로 실시간 업데이트
- 초기 로드 시 전체 데이터, 이후 부분 업데이트만 병합
- TypeScript 타입 안정성 보장
인증:
useAuthRedirect훅으로 인증되지 않은 사용자 자동 리다이렉트UserLayout컴포넌트에서 인증 체크
사용 커스텀 훅:
useDashboard: 대시보드 데이터 로딩 및 상태 관리useStatisticsWS: WebSocket 통계 수신 및 메시지 처리
기능:
- 📥 주문이 들어오면 실시간으로 화면에 표시
- 👨🍳 주방 모드 / 서빙 모드 전환 가능
- 🔄 주문 상태 변경: 대기 → 조리 중 → 서빙 완료
- 📋 메뉴별 필터링으로 주문 확인
- 🔔 WebSocket으로 실시간 업데이트
인증:
useAuthRedirect훅으로 인증 체크UserLayout에서 인증되지 않은 사용자 차단
사용 커스텀 훅:
useLiveOrdersWebSocket: WebSocket 연결 및 주문 데이터 수신useMenuData: 메뉴 목록 데이터 관리useGroupedAndSortedOrders: 주문 그룹화 및 정렬 로직
화면 미리보기:
기능:
- 📋 테이블 목록 조회 (활성/비활성 상태 표시)
- 🔍 테이블별 상세 주문 내역 확인
- 🔄 테이블 리셋 기능
- ✏️ 주문 수량 변경 및 취소
- 💰 테이블별 총 금액 확인
인증:
useAuthRedirect훅으로 인증 체크- 모든 테이블 조작은 인증된 사용자만 가능
화면 미리보기:
기능:
- ➕ 메뉴 추가/수정/삭제
- 🖼️ 메뉴 이미지 업로드
- 📦 재고 수량 관리
- 🏷️ 메뉴 카테고리 설정
- 🍱 세트메뉴 구성 및 관리
⚠️ 품절 처리
인증:
useAuthRedirect훅으로 인증 체크- 메뉴 수정/삭제는 인증된 관리자만 가능
화면 미리보기:
기능:
- ➕ 쿠폰 생성 (할인율/할인금액)
- 🎟️ 쿠폰 코드 발급
- 📊 쿠폰 사용 내역 확인
- 📥 엑셀 다운로드
- 📈 쿠폰별 통계 확인
인증:
useAuthRedirect훅으로 인증 체크- 쿠폰 생성 및 관리는 인증된 사용자만 가능
사용 커스텀 훅:
useCouponList: 쿠폰 목록 조회useCouponDetail: 쿠폰 상세 정보 조회useCreateCoupon: 쿠폰 생성 로직useCouponForm: 쿠폰 폼 상태 관리useCouponCode: 쿠폰 코드 관리
화면 미리보기:
기능:
- 🏪 부스 정보 수정
- 🪑 테이블 수 확인
- 💳 계좌 정보 관리
- 💰 좌석 과금 설정 (인원당/테이블당)
- ⏰ 이용 시간 제한 설정
- 📱 QR 코드 다운로드
- 🚪 로그아웃
인증:
useAuthRedirect훅으로 인증 체크- 본인 정보만 수정 가능
사용 커스텀 훅:
useManagers: 운영자 정보 조회 및 상태 관리useManagerPatch: 운영자 정보 수정 로직
화면 미리보기:
기능:
- 🔑 ID/비밀번호 로그인
- 🎫 로그인 성공 시
accessToken저장 - 🔄 자동 인증 시스템과 연동
인증 플로우:
- 사용자 로그인 정보 입력
UserService.login()호출- 성공 시
accessToken을localStorage에 저장 /home으로 자동 리다이렉트- 이후 모든 API 요청에 자동으로 토큰 포함
화면 미리보기:
useAuthRedirect: 인증되지 않은 사용자를 자동으로 초기화 페이지로 리다이렉트- 위치:
src/hooks/useAuthRedirect.ts - 사용:
UserLayout,DefaultLayout컴포넌트
- 위치:
-
useDashboard: 대시보드 데이터 로딩 및 상태 관리- 위치:
src/pages/dashboard/_hooks/useDashboard.ts - 기능: 초기 데이터 로드, 폴링 옵션, 에러 처리
- 위치:
-
useStatisticsWS: WebSocket 통계 수신 및 메시지 처리- 위치:
src/pages/dashboard/_hooks/useStatisticsWS.ts - 기능: WebSocket 연결, 메시지 타입별 처리 (INIT_STATISTICS, STATISTICS_UPDATED, ERROR)
- 위치:
-
useLiveOrdersWebSocket: WebSocket 연결 및 주문 데이터 수신- 위치:
src/pages/liveorder_v2/hooks/useLiveOrdersWebSocket.ts - 기능: WebSocket 연결 관리, 주문 데이터 실시간 수신
- 위치:
-
useMenuData: 메뉴 목록 데이터 관리- 위치:
src/pages/liveorder_v2/hooks/useMenuData.ts
- 위치:
-
useGroupedAndSortedOrders: 주문 그룹화 및 정렬 로직- 위치:
src/pages/liveorder_v2/hooks/useGroupedAndSortedOrders.ts - 기능: 주문을 메뉴별/상태별로 그룹화 및 정렬
- 위치:
-
useManagers: 운영자 정보 조회 및 상태 관리- 위치:
src/pages/mypage/hooks/useManagers.ts - 기능: 운영자 정보 조회, 수정, 자동 로드 옵션
- 위치:
-
useManagerPatch: 운영자 정보 수정 로직- 위치:
src/pages/mypage/hooks/useManagerPatch.ts - 기능: 부분 업데이트, 좌석 필드 정규화
- 위치:
-
useCouponList: 쿠폰 목록 조회- 위치:
src/pages/coupon/hooks/useCouponList.tsx
- 위치:
-
useCouponDetail: 쿠폰 상세 정보 조회- 위치:
src/pages/coupon/hooks/useCouponDetail.tsx
- 위치:
-
useCreateCoupon: 쿠폰 생성 로직- 위치:
src/pages/coupon/hooks/useCreateCoupon.tsx
- 위치:
-
useCouponForm: 쿠폰 폼 상태 관리- 위치:
src/pages/coupon/hooks/useCouponForm.tsx
- 위치:
-
useCouponCode: 쿠폰 코드 관리- 위치:
src/pages/coupon/hooks/useCouponCode.tsx
- 위치:
-
useOrderData: 주문 데이터 관리- 위치:
src/pages/order/hooks/useOrderData.ts
- 위치:
-
useStaffCall: 스태프 호출 기능- 위치:
src/components/header/hooks/useStaffCall.ts
- 위치:
-
useBoothRevenue: 부스 매출 조회- 위치:
src/components/header/hooks/useBoothRevenue.ts
- 위치:
-
useAnimatedNumber: 숫자 애니메이션- 위치:
src/components/header/hooks/useAnimatedNumber.ts
- 위치:
-
useGoogleAnalytics: 구글 애널리틱스 연동- 위치:
src/hooks/useGoogleAnalytics.ts
- 위치:
-
useCalcVh: 뷰포트 높이 계산- 위치:
src/hooks/useCalcVh.ts
- 위치:
- 로그인:
accessToken과refreshToken발급 - 자동 토큰 추가: 모든 API 요청에
accessToken자동 포함 - 자동 토큰 갱신: 401 에러 시
refreshToken으로 자동 갱신 - 중복 요청 방지:
failedQueue패턴으로 동시 요청 처리
- 토큰 만료 시 자동 로그아웃
useAuthRedirect훅으로 인증되지 않은 사용자 자동 리다이렉트- 쿠키 기반
refreshToken관리
모든 주요 페이지는 UserLayout 또는 DefaultLayout 컴포넌트를 통해 useAuthRedirect 훅을 사용하여 인증을 체크합니다:
/dashboard- 대시보드/home- 실시간 주문/table-view- 테이블 관리/menu- 메뉴 관리/coupon- 쿠폰 관리/mypage- 마이페이지
인증되지 않은 사용자는 자동으로 / (초기화 페이지)로 리다이렉트됩니다.
# 개발 모드
npm run dev
# 프로덕션 빌드
npm run build
# 빌드 미리보기
npm run preview.env 파일 설정:
# 백엔드 API URL
VITE_BASE_URL=https://api.example.com
# WebSocket URL
VITE_WS_URL=wss://api.example.com





