From e2cb482e5c34f091a7aab2876b9aea30b65d8ed4 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 30 Jul 2025 18:11:01 +0900 Subject: [PATCH] =?UTF-8?q?=ED=94=BC=EB=93=9C=EB=B0=B1=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EB=B0=8F=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=97=85.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 60 +- viewer/.env | 10 + viewer/.env.example | 15 +- viewer/package.json | 11 +- viewer/pnpm-lock.yaml | 741 +++++++++++++++++++----- viewer/src/App.tsx | 62 +- viewer/src/components/DynamicForm.tsx | 116 ++++ viewer/src/components/DynamicTable.tsx | 123 ++++ viewer/src/components/ErrorDisplay.tsx | 97 ++++ viewer/src/components/MainLayout.tsx | 18 + viewer/src/components/ui/button.tsx | 2 +- viewer/src/components/ui/card.tsx | 2 +- viewer/src/components/ui/input.tsx | 2 +- viewer/src/components/ui/label.tsx | 24 + viewer/src/components/ui/select.tsx | 156 +++++ viewer/src/components/ui/separator.tsx | 2 +- viewer/src/components/ui/table.tsx | 120 ++++ viewer/src/components/ui/textarea.tsx | 2 +- viewer/src/main.tsx | 9 +- viewer/src/pages/FeedbackCreatePage.tsx | 106 ++++ viewer/src/pages/FeedbackDetailPage.tsx | 148 +++++ viewer/src/pages/FeedbackListPage.tsx | 68 +++ viewer/src/pages/FeedbackPage.tsx | 230 -------- viewer/src/pages/IssueViewerPage.tsx | 100 ++++ viewer/src/services/error.ts | 18 + viewer/src/services/feedback.ts | 138 ++++- viewer/src/services/issue.ts | 41 ++ viewer/vite.config.ts | 19 +- 28 files changed, 1942 insertions(+), 498 deletions(-) create mode 100644 viewer/.env create mode 100644 viewer/src/components/DynamicForm.tsx create mode 100644 viewer/src/components/DynamicTable.tsx create mode 100644 viewer/src/components/ErrorDisplay.tsx create mode 100644 viewer/src/components/MainLayout.tsx create mode 100644 viewer/src/components/ui/label.tsx create mode 100644 viewer/src/components/ui/select.tsx create mode 100644 viewer/src/components/ui/table.tsx create mode 100644 viewer/src/pages/FeedbackCreatePage.tsx create mode 100644 viewer/src/pages/FeedbackDetailPage.tsx create mode 100644 viewer/src/pages/FeedbackListPage.tsx delete mode 100644 viewer/src/pages/FeedbackPage.tsx create mode 100644 viewer/src/pages/IssueViewerPage.tsx create mode 100644 viewer/src/services/error.ts create mode 100644 viewer/src/services/issue.ts diff --git a/TODO.md b/TODO.md index 984b794..a9c40fb 100644 --- a/TODO.md +++ b/TODO.md @@ -1,45 +1,27 @@ -# Q&A 뷰어 프로젝트: 할 일 목록 +# Q&A 뷰어 프로젝트 TODO 리스트 -## 🚀 1단계: 프로젝트 초기 설정 +## Phase 1: 기반 구축 및 핵심 기능 구현 -- [x] Vite + React + TypeScript 프로젝트 생성 (완료 시간: 2025-07-29T12:00:00) -- [x] pnpm 워크스페이스 설정 (완료 시간: 2025-07-29T12:01:00) -- [x] Biome 포맷터/린터 설정 (완료 시간: 2025-07-29T12:02:00) -- [x] Tailwind CSS 설정 (완료 시간: 2025-07-29T12:03:00) -- [x] shadcn/ui 초기화 및 기본 컴포넌트 추가 (완료 시간: 2025-07-29T12:04:00) -- [x] Zustand 상태 관리 라이브러리 설치 (완료 시간: 2025-07-29T12:05:00) -- [x] pnpm 워크스페이스를 단일 프로젝트 구조로 변경 (viewer를 루트로 설정) (완료 시간: 2025-07-29T12:06:00) +- [x] `GEMINI.md`에 프로젝트 개요 및 개발 계획 정리 (완료: 2025-07-30 17:53:48) +- [x] `DynamicTable` 컴포넌트 구현 및 API 연동 (완료: 2025-07-30 17:53:48) +- [x] `DynamicForm` 컴포넌트 구현 및 API 연동 (완료: 2025-07-30 17:53:48) +- [x] 피드백 목록 페이지 (`FeedbackListPage`) 구현 (완료: 2025-07-30 17:53:48) +- [x] 피드백 생성 페이지 (`FeedbackCreatePage`) 구현 (완료: 2025-07-30 17:53:48) +- [x] 피드백 상세/수정 페이지 (`FeedbackDetailPage`) 구현 (완료: 2025-07-30 17:53:48) +- [x] React Router를 이용한 전체 페이지 라우팅 설정 (완료: 2025-07-30 17:53:48) +- [x] 테이블 UI/UX 개선 (행 클릭, 특정 필드 서식 지정 등) (완료: 2025-07-30 17:53:48) +- [x] 폼 UI/UX 개선 (필드 숨김, 읽기 전용 처리 등) (완료: 2025-07-30 17:53:48) -## 🏗️ 2단계: 핵심 기능 개발 +## Phase 2: 기능 고도화 및 안정화 -- [x] Vite 프록시를 이용한 동적 API 서비스 모듈 구현 (완료 시간: 2025-07-29T12:08:00) -- [x] React Router를 이용한 동적 라우팅 설정 (완료 시간: 2025-07-29T12:09:00) -- [x] 피드백 페이지 기능 구현 (완료 시간: 2025-07-30T00:15:00) - - `feedback.ts` 서비���: 피드백 생성/조회, 이슈 검색 API 연동 - - `FeedbackPage.tsx`: 피드백 생성 폼 및 목록 UI 구현 -- [ ] 스키마 기반 동적 테이블 컴포넌트 개발 -- [ ] 스키마 기반 동적 폼 컴포넌트 개발 -- [ ] 데이터 목록 조회 페이지 구현 -- [ ] 데이터 상세 조회/수정 페이지 구현 +- [ ] 동적 테이블에 페이지네이션, 정렬, 필터링 기능 추가 +- [ ] 동적 폼에 데이터 유효성 검사 기능 추가 +- [ ] 전역 상태 관리를 위한 Zustand 도입 검토 +- [ ] Biome을 이용한 코드 포맷팅 및 린트 규칙 적용 및 검사 -## 🔐 3단계: 인증 기능 개발 +## Phase 3: 인증 및 배포 -- [ ] OIDC 클라이언트 라이브러리 설치 및 설정 -- [ ] 로그인/로그아웃 기능 구현 -- [ ] 인증 상태에 따른 라우팅 처리 (Public/Private Routes) - -## ✨ 4단계: 리팩토링 및 환경 개선 - -- [x] 빌드 및 스타일링 환경 안정화 (완료 시간: 2025-07-30T00:10:00) - - Tailwind CSS v4 -> v3 다운그레이드 - - PostCSS 설정을 `vite.config.ts`에 통합하여 빌드 오류 해결 - - `shadcn/ui` 경로 별칭 문제 및 테마 설정 복구 -- [x] 설정 파일 TypeScript 마이그레이션 (완료 시간: 2025-07-30T00:05:00) - - `tailwind.config.ts`, `postcss.config.ts`로 전환 -- [x] 피드백 페이지 UI 개선 (완료 시간: 2025-07-30T00:15:00) - - `Card`, `Separator` 컴포넌트를 활용한 레이아웃 재구성 -- [x] 개발 환경 개선 (완료 시간: 2025-07-30T00:20:00) - - `.env.example` 파일 추가 -- [ ] 전역 레이아웃 및 네비게이션 UI 개선 -- [ ] 예외 처리 및 로딩/에러 상태 UI 구현 -- [ ] 최종 코드 리뷰 및 리팩토링 \ No newline at end of file +- [ ] OIDC 클라이언트 연동 및 인증 로직 구현 +- [ ] 로그인/로그아웃 및 인증 상태 관리 +- [ ] 인증이 필요한 라우트 보호 기능 적용 +- [ ] Docker를 이용한 배포 환경 구축 \ No newline at end of file diff --git a/viewer/.env b/viewer/.env new file mode 100644 index 0000000..877a236 --- /dev/null +++ b/viewer/.env @@ -0,0 +1,10 @@ +VITE_API_PROXY_TARGET=http://172.16.10.175:3030/_back + +# API 요청 시 필요한 인증 키 +VITE_API_KEY=F5FE0363E37C012204F5 + +# 기본으로 사용할 프로젝트 ID +VITE_DEFAULT_PROJECT_ID=1 + +# 기본으로 사용할 채널 ID +VITE_DEFAULT_CHANNEL_ID=4 \ No newline at end of file diff --git a/viewer/.env.example b/viewer/.env.example index df84c8a..240a0bd 100644 --- a/viewer/.env.example +++ b/viewer/.env.example @@ -1,11 +1,8 @@ -# API 서버의 기본 URL -VITE_API_BASE_URL=API_BASE_URL +# Vite 환경 변수 예시 +# 이 파일을 복사하여 .env 파일을 만들고, 실제 환경에 맞는 값으로 수정하여 사용하세요. -# API 요청 시 필요한 인증 키 -VITE_API_KEY= +# API 프록시 서버 주소 +VITE_API_PROXY_TARGET=http://localhost:3030 -# 기본으로 사용할 프로젝트 ID -VITE_DEFAULT_PROJECT_ID=1 - -# 기본으로 사용할 채널 ID -VITE_DEFAULT_CHANNEL_ID=4 +# API 키 +VITE_API_KEY=your_api_key_here \ No newline at end of file diff --git a/viewer/package.json b/viewer/package.json index 44a61dc..b1a7663 100644 --- a/viewer/package.json +++ b/viewer/package.json @@ -7,11 +7,14 @@ "dev": "vite", "build": "tsc -b && vite build", "format": "biome format --write .", - "lint": "biome lint --apply .", + "lint": "biome lint --write .", "preview": "vite preview", "shadcn": "shadcn-ui" }, "dependencies": { + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "class-variance-authority": "^0.7.1", @@ -25,21 +28,15 @@ }, "devDependencies": { "@biomejs/biome": "^2.1.2", - "@eslint/js": "^9.30.1", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", "autoprefixer": "^10.4.21", - "eslint": "^9.30.1", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.20", - "globals": "^16.3.0", "postcss": "^8.5.6", "tailwindcss": "^3.4.17", "tailwindcss-animate": "^1.0.7", "tw-animate-css": "^1.3.6", "typescript": "~5.8.3", - "typescript-eslint": "^8.35.1", "vite": "^7.0.4" } } diff --git a/viewer/pnpm-lock.yaml b/viewer/pnpm-lock.yaml index 06010c8..7491bad 100644 --- a/viewer/pnpm-lock.yaml +++ b/viewer/pnpm-lock.yaml @@ -8,6 +8,15 @@ importers: .: dependencies: + '@radix-ui/react-icons': + specifier: ^1.3.2 + version: 1.3.2(react@19.1.1) + '@radix-ui/react-label': + specifier: ^2.1.7 + version: 2.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-select': + specifier: ^2.2.5 + version: 2.2.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-separator': specifier: ^1.1.7 version: 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -42,9 +51,6 @@ importers: '@biomejs/biome': specifier: ^2.1.2 version: 2.1.2 - '@eslint/js': - specifier: ^9.30.1 - version: 9.32.0 '@types/react': specifier: ^19.1.8 version: 19.1.9 @@ -57,18 +63,6 @@ importers: autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) - eslint: - specifier: ^9.30.1 - version: 9.32.0(jiti@2.5.1) - eslint-plugin-react-hooks: - specifier: ^5.2.0 - version: 5.2.0(eslint@9.32.0(jiti@2.5.1)) - eslint-plugin-react-refresh: - specifier: ^0.4.20 - version: 0.4.20(eslint@9.32.0(jiti@2.5.1)) - globals: - specifier: ^16.3.0 - version: 16.3.0 postcss: specifier: ^8.5.6 version: 8.5.6 @@ -84,9 +78,6 @@ importers: typescript: specifier: ~5.8.3 version: 5.8.3 - typescript-eslint: - specifier: ^8.35.1 - version: 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) vite: specifier: ^7.0.4 version: 7.0.6(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.0) @@ -507,6 +498,21 @@ packages: resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.7.2': + resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==} + + '@floating-ui/dom@1.7.2': + resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==} + + '@floating-ui/react-dom@2.1.4': + resolution: {integrity: sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -560,6 +566,38 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.2': + resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.1.2': resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: @@ -569,6 +607,112 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.10': + resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.2': + resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-icons@1.3.2': + resolution: {integrity: sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==} + peerDependencies: + react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.7': + resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.1.3': resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} peerDependencies: @@ -582,6 +726,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-select@2.2.5': + resolution: {integrity: sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-separator@1.1.7': resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} peerDependencies: @@ -604,6 +761,94 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -840,6 +1085,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + autoprefixer@10.4.21: resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} @@ -944,6 +1193,9 @@ packages: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -1090,6 +1342,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1463,6 +1719,26 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.1: + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react-router-dom@7.7.1: resolution: {integrity: sha512-bavdk2BA5r3MYalGKZ01u8PGuDBloQmzpBZVhDLrOOv1N943Wq6dcM9GhB3x8b7AbqPMEezauv4PeGkAJfy7FQ==} engines: {node: '>=20.0.0'} @@ -1480,6 +1756,16 @@ packages: react-dom: optional: true + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react@19.1.1: resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} engines: {node: '>=0.10.0'} @@ -1616,6 +1902,9 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tw-animate-css@1.3.6: resolution: {integrity: sha512-9dy0R9UsYEGmgf26L8UcHiLmSFTHa9+D7+dAt/G/sF5dCnPePZbfgDYinc7/UzAM7g/baVrmS6m9yEpU46d+LA==} @@ -1644,6 +1933,26 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -1968,11 +2277,6 @@ snapshots: '@esbuild/win32-x64@0.25.8': optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@9.32.0(jiti@2.5.1))': - dependencies: - eslint: 9.32.0(jiti@2.5.1) - eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.7.0(eslint@9.32.0)': dependencies: eslint: 9.32.0 @@ -2017,6 +2321,23 @@ snapshots: '@eslint/core': 0.15.1 levn: 0.4.1 + '@floating-ui/core@1.7.2': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.2': + dependencies: + '@floating-ui/core': 1.7.2 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@floating-ui/dom': 1.7.2 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + '@floating-ui/utils@0.2.10': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -2068,12 +2389,127 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.2': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.9)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: '@types/react': 19.1.9 + '@radix-ui/react-context@1.1.2(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-direction@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-icons@1.3.2(react@19.1.1)': + dependencies: + react: 19.1.1 + + '@radix-ui/react-id@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@floating-ui/react-dom': 2.1.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/rect': 1.1.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) @@ -2083,6 +2519,35 @@ snapshots: '@types/react': 19.1.9 '@types/react-dom': 19.1.7(@types/react@19.1.9) + '@radix-ui/react-select@2.2.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + aria-hidden: 1.2.6 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-remove-scroll: 2.7.1(@types/react@19.1.9)(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -2099,6 +2564,71 @@ snapshots: optionalDependencies: '@types/react': 19.1.9 + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.1.9)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.9 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + '@types/react-dom': 19.1.7(@types/react@19.1.9) + + '@radix-ui/rect@1.1.1': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.46.1': @@ -2194,23 +2724,6 @@ snapshots: dependencies: csstype: 3.1.3 - '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.38.0 - '@typescript-eslint/type-utils': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) - '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.38.0 - eslint: 9.32.0(jiti@2.5.1) - graphemer: 1.4.0 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint@9.32.0)(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -2228,18 +2741,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.38.0 - '@typescript-eslint/types': 8.38.0 - '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.38.0 - debug: 4.4.1 - eslint: 9.32.0(jiti@2.5.1) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.38.0 @@ -2270,18 +2771,6 @@ snapshots: dependencies: typescript: 5.8.3 - '@typescript-eslint/type-utils@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3)': - dependencies: - '@typescript-eslint/types': 8.38.0 - '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) - debug: 4.4.1 - eslint: 9.32.0(jiti@2.5.1) - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/type-utils@8.38.0(eslint@9.32.0)(typescript@5.8.3)': dependencies: '@typescript-eslint/types': 8.38.0 @@ -2312,17 +2801,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3)': - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@2.5.1)) - '@typescript-eslint/scope-manager': 8.38.0 - '@typescript-eslint/types': 8.38.0 - '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) - eslint: 9.32.0(jiti@2.5.1) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/utils@8.38.0(eslint@9.32.0)(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0) @@ -2397,6 +2875,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.25.1 @@ -2493,6 +2975,8 @@ snapshots: detect-libc@2.0.4: optional: true + detect-node-es@1.1.0: {} + didyoumean@1.2.2: {} dlv@1.1.3: {} @@ -2538,18 +3022,10 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.5.1)): - dependencies: - eslint: 9.32.0(jiti@2.5.1) - eslint-plugin-react-hooks@5.2.0(eslint@9.32.0): dependencies: eslint: 9.32.0 - eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.5.1)): - dependencies: - eslint: 9.32.0(jiti@2.5.1) - eslint-plugin-react-refresh@0.4.20(eslint@9.32.0): dependencies: eslint: 9.32.0 @@ -2603,48 +3079,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint@9.32.0(jiti@2.5.1): - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@2.5.1)) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.21.0 - '@eslint/config-helpers': 0.3.0 - '@eslint/core': 0.15.1 - '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.32.0 - '@eslint/plugin-kit': 0.3.4 - '@humanfs/node': 0.16.6 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.1 - escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - optionalDependencies: - jiti: 2.5.1 - transitivePeerDependencies: - - supports-color - espree@10.4.0: dependencies: acorn: 8.15.0 @@ -2719,6 +3153,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-nonce@1.0.1: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -3014,6 +3450,25 @@ snapshots: react-refresh@0.17.0: {} + react-remove-scroll-bar@2.3.8(@types/react@19.1.9)(react@19.1.1): + dependencies: + react: 19.1.1 + react-style-singleton: 2.2.3(@types/react@19.1.9)(react@19.1.1) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.9 + + react-remove-scroll@2.7.1(@types/react@19.1.9)(react@19.1.1): + dependencies: + react: 19.1.1 + react-remove-scroll-bar: 2.3.8(@types/react@19.1.9)(react@19.1.1) + react-style-singleton: 2.2.3(@types/react@19.1.9)(react@19.1.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.1.9)(react@19.1.1) + use-sidecar: 1.1.3(@types/react@19.1.9)(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.9 + react-router-dom@7.7.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: react: 19.1.1 @@ -3028,6 +3483,14 @@ snapshots: optionalDependencies: react-dom: 19.1.1(react@19.1.1) + react-style-singleton@2.2.3(@types/react@19.1.9)(react@19.1.1): + dependencies: + get-nonce: 1.0.1 + react: 19.1.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.9 + react@19.1.1: {} read-cache@1.0.0: @@ -3196,23 +3659,14 @@ snapshots: ts-interface-checker@0.1.13: {} + tslib@2.8.1: {} + tw-animate-css@1.3.6: {} type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) - '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) - '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.8.3) - eslint: 9.32.0(jiti@2.5.1) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - typescript-eslint@8.38.0(eslint@9.32.0)(typescript@5.8.3): dependencies: '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint@9.32.0)(typescript@5.8.3) @@ -3236,6 +3690,21 @@ snapshots: dependencies: punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@19.1.9)(react@19.1.1): + dependencies: + react: 19.1.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.9 + + use-sidecar@1.1.3(@types/react@19.1.9)(react@19.1.1): + dependencies: + detect-node-es: 1.1.0 + react: 19.1.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.9 + util-deprecate@1.0.2: {} vite@7.0.6: diff --git a/viewer/src/App.tsx b/viewer/src/App.tsx index 3754e1a..141ba37 100644 --- a/viewer/src/App.tsx +++ b/viewer/src/App.tsx @@ -1,35 +1,41 @@ // src/App.tsx import { - BrowserRouter, - Routes, - Route, - Navigate, -} from 'react-router-dom'; -import { FeedbackPage } from '@/pages/FeedbackPage'; + Routes, + Route, + Navigate, +} from "react-router-dom"; +import { MainLayout } from "@/components/MainLayout"; +import { FeedbackCreatePage } from "@/pages/FeedbackCreatePage"; +import { FeedbackListPage } from "@/pages/FeedbackListPage"; +import { FeedbackDetailPage } from "@/pages/FeedbackDetailPage"; +import { IssueViewerPage } from "@/pages/IssueViewerPage"; function App() { - const defaultProjectId = import.meta.env.VITE_DEFAULT_PROJECT_ID || 'default'; - const defaultChannelId = import.meta.env.VITE_DEFAULT_CHANNEL_ID || 'general'; + return ( + + {/* 기본 경로 리디렉션 */} + } /> - return ( - - - } - /> - {/* .env.local에 설정된 기본 프로젝트/채널로 리디렉션합니다. */} - - } - /> - - - ); + {/* 피드백 관련 페이지 (메인 레이아웃 사용) */} + } + > + } /> + } /> + } /> + + + {/* 독립적인 이슈 뷰어 페이지 */} + } + /> + + {/* 잘못된 접근을 위한 리디렉션 */} + } /> + + ); } -export default App; \ No newline at end of file +export default App; diff --git a/viewer/src/components/DynamicForm.tsx b/viewer/src/components/DynamicForm.tsx new file mode 100644 index 0000000..bb52097 --- /dev/null +++ b/viewer/src/components/DynamicForm.tsx @@ -0,0 +1,116 @@ + +import { useState, useEffect } from "react"; +import type { FeedbackField } from "@/services/feedback"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; + +// 컴포넌트 외부에 안정적인 참조를 가진 빈 객체 상수 선언 +const EMPTY_INITIAL_DATA = {}; + +interface DynamicFormProps { + fields: FeedbackField[]; + onSubmit: (formData: Record) => Promise; + initialData?: Record; + submitButtonText?: string; +} + +export function DynamicForm({ + fields, + onSubmit, + initialData = EMPTY_INITIAL_DATA, // 기본값으로 상수 사용 + submitButtonText = "제출", +}: DynamicFormProps) { + const [formData, setFormData] = useState>(initialData); + const [isSubmitting, setIsSubmitting] = useState(false); + + useEffect(() => { + // initialData prop이 변경될 때만 폼 데이터를 동기화 + setFormData(initialData); + }, [initialData]); + + const handleFormChange = (fieldId: string, value: any) => { + setFormData((prev) => ({ ...prev, [fieldId]: value })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsSubmitting(true); + try { + await onSubmit(formData); + } catch (error) { + console.error("Form submission error:", error); + } finally { + setIsSubmitting(false); + } + }; + + const renderField = (field: FeedbackField) => { + const commonProps = { + id: field.id, + value: formData[field.id] ?? "", + disabled: field.readOnly, + }; + + switch (field.type) { + case "textarea": + return ( +