From f0c5d768dbc043a16a77605ce53386c1c3d2a531 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Fri, 20 Feb 2026 09:12:37 +0100 Subject: [PATCH] Enhance project setup with Prisma, new scripts, and dependencies; update README for clarity and add API routes; improve layout and styling for better user experience --- README.md | 148 +- package.json | 28 +- playwright.config.ts | 20 + pnpm-lock.yaml | 1881 ++++++++++++++++++++++++- prisma/dev.db | Bin 0 -> 102400 bytes prisma/schema.prisma | 89 ++ prisma/seed.ts | 222 +++ src/app/admin/page.tsx | 66 + src/app/api/auth/route.ts | 38 + src/app/api/evaluations/[id]/route.ts | 176 +++ src/app/api/evaluations/route.ts | 87 ++ src/app/api/export/csv/route.ts | 38 + src/app/api/export/pdf/route.ts | 68 + src/app/api/templates/data/route.ts | 29 + src/app/api/templates/route.ts | 29 + src/app/evaluations/[id]/page.tsx | 361 +++++ src/app/evaluations/new/page.tsx | 91 ++ src/app/globals.css | 27 +- src/app/layout.tsx | 17 +- src/app/page.tsx | 192 ++- src/components/CandidateForm.tsx | 94 ++ src/components/ConfirmModal.tsx | 64 + src/components/DimensionCard.tsx | 190 +++ src/components/ExportModal.tsx | 51 + src/components/Header.tsx | 28 + src/components/RadarChart.tsx | 76 + src/components/ThemeProvider.tsx | 40 + src/components/ThemeToggle.tsx | 18 + src/lib/db.ts | 7 + src/lib/export-utils.test.ts | 91 ++ src/lib/export-utils.ts | 81 ++ tests/e2e/dashboard.spec.ts | 21 + vitest.config.ts | 16 + 33 files changed, 4277 insertions(+), 107 deletions(-) create mode 100644 playwright.config.ts create mode 100644 prisma/dev.db create mode 100644 prisma/schema.prisma create mode 100644 prisma/seed.ts create mode 100644 src/app/admin/page.tsx create mode 100644 src/app/api/auth/route.ts create mode 100644 src/app/api/evaluations/[id]/route.ts create mode 100644 src/app/api/evaluations/route.ts create mode 100644 src/app/api/export/csv/route.ts create mode 100644 src/app/api/export/pdf/route.ts create mode 100644 src/app/api/templates/data/route.ts create mode 100644 src/app/api/templates/route.ts create mode 100644 src/app/evaluations/[id]/page.tsx create mode 100644 src/app/evaluations/new/page.tsx create mode 100644 src/components/CandidateForm.tsx create mode 100644 src/components/ConfirmModal.tsx create mode 100644 src/components/DimensionCard.tsx create mode 100644 src/components/ExportModal.tsx create mode 100644 src/components/Header.tsx create mode 100644 src/components/RadarChart.tsx create mode 100644 src/components/ThemeProvider.tsx create mode 100644 src/components/ThemeToggle.tsx create mode 100644 src/lib/db.ts create mode 100644 src/lib/export-utils.test.ts create mode 100644 src/lib/export-utils.ts create mode 100644 tests/e2e/dashboard.spec.ts create mode 100644 vitest.config.ts diff --git a/README.md b/README.md index e215bc4..82226b3 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,142 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# IA Gen Maturity Evaluator -## Getting Started +Production-ready web app for evaluating IA/GenAI maturity of candidates. Built for the Cars Front team. -First, run the development server: +## Tech Stack + +- **Next.js 16** (App Router), **React 19**, **TypeScript**, **TailwindCSS** +- **Prisma** + **SQLite** (local) — switch to Postgres/Supabase for production +- **Recharts** (radar chart), **jsPDF** (PDF export) + +## Setup ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +pnpm install +cp .env.example .env +pnpm db:generate +pnpm db:push +pnpm db:seed ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +## Run -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +```bash +pnpm dev +``` -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +Open [http://localhost:3000](http://localhost:3000). -## Learn More +## Seed Data -To learn more about Next.js, take a look at the following resources: +- **3 candidates** with sample evaluations (Alice Chen, Bob Martin, Carol White) +- **2 templates**: Full 15-dimensions, Short 8-dimensions +- **Admin user**: `admin@cars-front.local` (mock auth) -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +## API Routes -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +| Route | Method | Description | +|-------|--------|-------------| +| `/api/evaluations` | GET, POST | List / create evaluations | +| `/api/evaluations/[id]` | GET, PUT | Get / update evaluation | +| `/api/templates` | GET | List templates | +| `/api/export/csv?id=` | GET | Export evaluation as CSV | +| `/api/export/pdf?id=` | GET | Export evaluation as PDF | +| `/api/auth` | GET, POST | Mock auth | +| `/api/ai/suggest-followups` | POST | AI follow-up suggestions (stub) | -## Deploy on Vercel +## Export cURL Examples -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +```bash +# CSV export (replace EVAL_ID with actual evaluation id) +curl -o evaluation.csv "http://localhost:3000/api/export/csv?id=EVAL_ID" -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +# PDF export +curl -o evaluation.pdf "http://localhost:3000/api/export/pdf?id=EVAL_ID" +``` + +With auth header (when real auth is added): + +```bash +curl -H "Authorization: Bearer YOUR_TOKEN" -o evaluation.csv "http://localhost:3000/api/export/csv?id=EVAL_ID" +``` + +## AI Assistant Stub + +The AI assistant is a **client-side stub** that returns deterministic follow-up suggestions based on: +- Dimension name +- Candidate answer length +- Current score (low scores trigger probing questions) + +**To plug a real LLM:** + +1. Create or update `/api/ai/suggest-followups` to call OpenAI/Anthropic/etc. +2. Pass `{ dimensionName, candidateAnswer, currentScore }` in the request body. +3. Use a prompt like: *"Given this dimension and candidate answer, suggest 2–3 probing interview questions."* +4. Return `{ suggestions: string[] }`. + +The client already calls this API when the user clicks "Get AI follow-up suggestions" in the dimension card. + +## Tests + +```bash +# Unit tests (Vitest) +pnpm test + +# E2E tests (Playwright) — requires dev server +pnpm test:e2e +``` + +Run `pnpm exec playwright install` once to install browsers for E2E. + +## Deploy + +1. Set `DATABASE_URL` to Postgres (e.g. Supabase, Neon). +2. Run migrations: `pnpm db:push` +3. Seed if needed: `pnpm db:seed` +4. Build: `pnpm build && pnpm start` +5. Or deploy to Vercel (set env, use Vercel Postgres or external DB). + +## Acceptance Criteria + +- [x] Auth: mock single-admin login +- [x] Dashboard: list evaluations and candidates +- [x] Create/Edit evaluation: candidate, role, date, evaluator, template +- [x] Templates: Full 15-dim, Short 8-dim +- [x] Interview guide: definition, rubric 1→5, signals, questions per dimension +- [x] AI assistant: stub suggests follow-ups +- [x] Scoring: 1–5, justification, examples, confidence +- [x] Probing questions when score ≤ 2 +- [x] Radar chart + findings/recommendations +- [x] Export PDF and CSV +- [x] Admin: view templates +- [x] Warning when all scores = 5 without comments +- [x] Edit after submission (audit log) +- [x] Mobile responsive (Tailwind) + +## Manual Test Plan + +1. **Dashboard**: Open `/`, verify evaluations table or empty state. +2. **New evaluation**: Click "New Evaluation", fill form, select template, submit. +3. **Interview guide**: On evaluation page, score dimensions, add notes, click "Get AI follow-up suggestions". +4. **Low score**: Set a dimension to 1 or 2, verify probing questions appear. +5. **All 5s**: Set all scores to 5 with no justification, submit — verify warning. +6. **Aggregate**: Click "Auto-generate findings", verify radar chart and text. +7. **Export**: Click Export, download CSV and PDF. +8. **Admin**: Open `/admin`, verify templates listed. + +## File Structure + +``` +src/ +├── app/ +│ ├── api/ # API routes +│ ├── evaluations/ # Evaluation pages +│ ├── admin/ # Admin page +│ └── page.tsx # Dashboard +├── components/ # UI components +└── lib/ # Utils, db, ai-stub, export-utils +prisma/ +├── schema.prisma +└── seed.ts +tests/e2e/ # Playwright E2E +``` diff --git a/package.json b/package.json index 34a0f15..38c9ec9 100644 --- a/package.json +++ b/package.json @@ -2,25 +2,45 @@ "name": "iag-dev-evaluator", "version": "0.1.0", "private": true, + "prisma": { + "seed": "tsx prisma/seed.ts" + }, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "db:generate": "prisma generate", + "db:push": "prisma db push", + "db:seed": "tsx prisma/seed.ts", + "db:studio": "prisma studio", + "test": "vitest run", + "test:e2e": "playwright test" }, "dependencies": { + "date-fns": "^4.1.0", + "jspdf": "^4.2.0", + "jspdf-autotable": "^5.0.7", "next": "16.1.6", "react": "19.2.3", - "react-dom": "19.2.3" + "react-dom": "19.2.3", + "recharts": "^3.7.0", + "@prisma/client": "^5.22.0" }, "devDependencies": { + "@playwright/test": "^1.58.2", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@vitejs/plugin-react": "^5.1.4", "eslint": "^9", "eslint-config-next": "16.1.6", + "jsdom": "^28.1.0", + "prisma": "^5.22.0", "tailwindcss": "^4", - "typescript": "^5" + "tsx": "^4.21.0", + "typescript": "^5", + "vitest": "^4.0.18" } -} +} \ No newline at end of file diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..7b4e315 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,20 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests/e2e", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: "html", + use: { + baseURL: "http://localhost:3000", + trace: "on-first-retry", + }, + projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }], + webServer: { + command: "pnpm dev", + url: "http://localhost:3000", + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19f2fe5..17e7a53 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,16 +8,34 @@ importers: .: dependencies: + '@prisma/client': + specifier: ^5.22.0 + version: 5.22.0(prisma@5.22.0) + date-fns: + specifier: ^4.1.0 + version: 4.1.0 + jspdf: + specifier: ^4.2.0 + version: 4.2.0 + jspdf-autotable: + specifier: ^5.0.7 + version: 5.0.7(jspdf@4.2.0) next: specifier: 16.1.6 - version: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 16.1.6(@babel/core@7.29.0)(@playwright/test@1.58.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: specifier: 19.2.3 version: 19.2.3 react-dom: specifier: 19.2.3 version: 19.2.3(react@19.2.3) + recharts: + specifier: ^3.7.0 + version: 3.7.0(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react-is@16.13.1)(react@19.2.3)(redux@5.0.1) devDependencies: + '@playwright/test': + specifier: ^1.58.2 + version: 1.58.2 '@tailwindcss/postcss': specifier: ^4 version: 4.2.0 @@ -30,25 +48,52 @@ importers: '@types/react-dom': specifier: ^19 version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^5.1.4 + version: 5.1.4(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) eslint: specifier: ^9 version: 9.39.2(jiti@2.6.1) eslint-config-next: specifier: 16.1.6 version: 16.1.6(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + jsdom: + specifier: ^28.1.0 + version: 28.1.0 + prisma: + specifier: ^5.22.0 + version: 5.22.0 tailwindcss: specifier: ^4 version: 4.2.0 + tsx: + specifier: ^4.21.0 + version: 4.21.0 typescript: specifier: ^5 version: 5.9.3 + vitest: + specifier: ^4.0.18 + version: 4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(tsx@4.21.0) packages: + '@acemir/cssom@0.9.31': + resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@asamuzakjp/css-color@4.1.2': + resolution: {integrity: sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==} + + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -83,6 +128,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -104,6 +153,22 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -116,6 +181,41 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.0.1': + resolution: {integrity: sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.1.1': + resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.0.1': + resolution: {integrity: sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.0.27': + resolution: {integrity: sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==} + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} @@ -125,6 +225,162 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -163,6 +419,15 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@exodus/bytes@1.14.1': + resolution: {integrity: sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -405,9 +670,183 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@playwright/test@1.58.2': + resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} + engines: {node: '>=18'} + hasBin: true + + '@prisma/client@5.22.0': + resolution: {integrity: sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==} + engines: {node: '>=16.13'} + peerDependencies: + prisma: '*' + peerDependenciesMeta: + prisma: + optional: true + + '@prisma/debug@5.22.0': + resolution: {integrity: sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==} + + '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': + resolution: {integrity: sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==} + + '@prisma/engines@5.22.0': + resolution: {integrity: sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==} + + '@prisma/fetch-engine@5.22.0': + resolution: {integrity: sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==} + + '@prisma/get-platform@5.22.0': + resolution: {integrity: sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==} + + '@reduxjs/toolkit@2.11.2': + resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + + '@rolldown/pluginutils@1.0.0-rc.3': + resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} + + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + cpu: [x64] + os: [win32] + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -502,6 +941,51 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -514,6 +998,12 @@ packages: '@types/node@20.19.33': resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==} + '@types/pako@2.0.4': + resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==} + + '@types/raf@3.4.3': + resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -522,6 +1012,12 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@typescript-eslint/eslint-plugin@8.56.0': resolution: {integrity: sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -676,6 +1172,41 @@ packages: cpu: [x64] os: [win32] + '@vitejs/plugin-react@5.1.4': + resolution: {integrity: sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/expect@4.0.18': + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.18': + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + + '@vitest/runner@4.0.18': + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + + '@vitest/snapshot@4.0.18': + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + + '@vitest/spy@4.0.18': + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + + '@vitest/utils@4.0.18': + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -686,6 +1217,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -732,6 +1267,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -754,11 +1293,18 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + baseline-browser-mapping@2.10.0: resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} engines: {node: '>=6.0.0'} hasBin: true + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -793,6 +1339,14 @@ packages: caniuse-lite@1.0.30001770: resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==} + canvg@3.0.11: + resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==} + engines: {node: '>=10.0.0'} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -800,6 +1354,10 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -813,16 +1371,78 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + core-js@3.48.0: + resolution: {integrity: sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-line-break@2.1.0: + resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cssstyle@6.0.1: + resolution: {integrity: sha512-IoJs7La+oFp/AB033wBStxNOJt4+9hHMxsXUPANcoXL2b3W4DZKghlJ2cI/eyeRZIQ9ysvYEorVhjrcYctWbog==} + engines: {node: '>=20'} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -835,6 +1455,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -852,6 +1475,12 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -871,6 +1500,9 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + dompurify@3.3.1: + resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -885,6 +1517,10 @@ packages: resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + es-abstract@1.24.1: resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} engines: {node: '>= 0.4'} @@ -901,6 +1537,9 @@ packages: resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -917,6 +1556,14 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es-toolkit@1.44.0: + resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} + + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1041,10 +1688,20 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1058,6 +1715,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-png@6.4.0: + resolution: {integrity: sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==} + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -1070,6 +1730,9 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1093,6 +1756,16 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1186,6 +1859,22 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + html2canvas@1.4.1: + resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} + engines: {node: '>=8.0.0'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1194,6 +1883,12 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + + immer@11.1.4: + resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -1206,6 +1901,13 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + iobuffer@5.4.0: + resolution: {integrity: sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -1273,6 +1975,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -1330,6 +2035,15 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsdom@28.1.0: + resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1353,6 +2067,14 @@ packages: engines: {node: '>=6'} hasBin: true + jspdf-autotable@5.0.7: + resolution: {integrity: sha512-2wr7H6liNDBYNwt25hMQwXkEWFOEopgKIvR1Eukuw6Zmprm/ZcnmLTQEjW7Xx3FCbD3v7pflLcnMAv/h1jFDQw==} + peerDependencies: + jspdf: ^2 || ^3 || ^4 + + jspdf@4.2.0: + resolution: {integrity: sha512-hR/hnRevAXXlrjeqU5oahOE+Ln9ORJUB5brLHHqH67A+RBQZuFr5GkbI9XQI8OUFSEezKegsi45QRpc4bGj75Q==} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -1452,6 +2174,10 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@11.2.6: + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1462,6 +2188,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1556,6 +2285,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1572,10 +2304,16 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1587,6 +2325,12 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1598,6 +2342,16 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + playwright-core@1.58.2: + resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.58.2: + resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} + engines: {node: '>=18'} + hasBin: true + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -1614,6 +2368,11 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prisma@5.22.0: + resolution: {integrity: sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==} + engines: {node: '>=16.13'} + hasBin: true + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -1624,6 +2383,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + raf@3.4.1: + resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} + react-dom@19.2.3: resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} peerDependencies: @@ -1632,18 +2394,60 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + react@19.2.3: resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} + recharts@3.7.0: + resolution: {integrity: sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1665,6 +2469,15 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rgbcolor@1.0.1: + resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==} + engines: {node: '>= 0.8.15'} + + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -1680,6 +2493,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -1732,6 +2549,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1739,6 +2559,16 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + stackblur-canvas@2.7.0: + resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==} + engines: {node: '>=0.1.14'} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -1795,6 +2625,13 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svg-pathdata@6.0.3: + resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==} + engines: {node: '>=12.0.0'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwindcss@4.2.0: resolution: {integrity: sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==} @@ -1802,14 +2639,46 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + text-segmentation@1.0.3: + resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + + tldts-core@7.0.23: + resolution: {integrity: sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==} + + tldts@7.0.23: + resolution: {integrity: sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} @@ -1822,6 +2691,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1861,6 +2735,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@7.22.0: + resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==} + engines: {node: '>=20.18.1'} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} @@ -1873,6 +2751,107 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + utrie@1.0.2: + resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -1894,10 +2873,22 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -1916,8 +2907,28 @@ packages: snapshots: + '@acemir/cssom@0.9.31': {} + '@alloc/quick-lru@5.2.0': {} + '@asamuzakjp/css-color@4.1.2': + dependencies: + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.2.6 + + '@asamuzakjp/dom-selector@6.8.1': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.1.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.6 + + '@asamuzakjp/nwsapi@2.3.9': {} + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -1980,6 +2991,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} @@ -1995,6 +3008,18 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/runtime@7.28.6': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -2018,6 +3043,32 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.1.0 + + '@csstools/color-helpers@6.0.1': {} + + '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.1 + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.0.27': {} + + '@csstools/css-tokenizer@4.0.0': {} + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -2034,6 +3085,84 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.27.3': + optional: true + + '@esbuild/android-arm64@0.27.3': + optional: true + + '@esbuild/android-arm@0.27.3': + optional: true + + '@esbuild/android-x64@0.27.3': + optional: true + + '@esbuild/darwin-arm64@0.27.3': + optional: true + + '@esbuild/darwin-x64@0.27.3': + optional: true + + '@esbuild/freebsd-arm64@0.27.3': + optional: true + + '@esbuild/freebsd-x64@0.27.3': + optional: true + + '@esbuild/linux-arm64@0.27.3': + optional: true + + '@esbuild/linux-arm@0.27.3': + optional: true + + '@esbuild/linux-ia32@0.27.3': + optional: true + + '@esbuild/linux-loong64@0.27.3': + optional: true + + '@esbuild/linux-mips64el@0.27.3': + optional: true + + '@esbuild/linux-ppc64@0.27.3': + optional: true + + '@esbuild/linux-riscv64@0.27.3': + optional: true + + '@esbuild/linux-s390x@0.27.3': + optional: true + + '@esbuild/linux-x64@0.27.3': + optional: true + + '@esbuild/netbsd-arm64@0.27.3': + optional: true + + '@esbuild/netbsd-x64@0.27.3': + optional: true + + '@esbuild/openbsd-arm64@0.27.3': + optional: true + + '@esbuild/openbsd-x64@0.27.3': + optional: true + + '@esbuild/openharmony-arm64@0.27.3': + optional: true + + '@esbuild/sunos-x64@0.27.3': + optional: true + + '@esbuild/win32-arm64@0.27.3': + optional: true + + '@esbuild/win32-ia32@0.27.3': + optional: true + + '@esbuild/win32-x64@0.27.3': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': dependencies: eslint: 9.39.2(jiti@2.6.1) @@ -2080,6 +3209,8 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@exodus/bytes@1.14.1': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -2258,8 +3389,130 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@playwright/test@1.58.2': + dependencies: + playwright: 1.58.2 + + '@prisma/client@5.22.0(prisma@5.22.0)': + optionalDependencies: + prisma: 5.22.0 + + '@prisma/debug@5.22.0': {} + + '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': {} + + '@prisma/engines@5.22.0': + dependencies: + '@prisma/debug': 5.22.0 + '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 + '@prisma/fetch-engine': 5.22.0 + '@prisma/get-platform': 5.22.0 + + '@prisma/fetch-engine@5.22.0': + dependencies: + '@prisma/debug': 5.22.0 + '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 + '@prisma/get-platform': 5.22.0 + + '@prisma/get-platform@5.22.0': + dependencies: + '@prisma/debug': 5.22.0 + + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.3)(redux@5.0.1))(react@19.2.3)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.4 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 19.2.3 + react-redux: 9.2.0(@types/react@19.2.14)(react@19.2.3)(redux@5.0.1) + + '@rolldown/pluginutils@1.0.0-rc.3': {} + + '@rollup/rollup-android-arm-eabi@4.57.1': + optional: true + + '@rollup/rollup-android-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-x64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.57.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.57.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.1': + optional: true + '@rtsao/scc@1.1.0': {} + '@standard-schema/spec@1.1.0': {} + + '@standard-schema/utils@0.3.0': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -2338,6 +3591,58 @@ snapshots: tslib: 2.8.1 optional: true + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} @@ -2348,6 +3653,11 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/pako@2.0.4': {} + + '@types/raf@3.4.3': + optional: true + '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: '@types/react': 19.2.14 @@ -2356,6 +3666,11 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/trusted-types@2.0.7': + optional: true + + '@types/use-sync-external-store@0.0.6': {} + '@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -2506,12 +3821,65 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-rc.3 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@4.0.18': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + dependencies: + '@vitest/spy': 4.0.18 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + + '@vitest/pretty-format@4.0.18': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.18': + dependencies: + '@vitest/utils': 4.0.18 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.18': {} + + '@vitest/utils@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + tinyrainbow: 3.0.3 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 acorn@8.16.0: {} + agent-base@7.1.4: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -2594,6 +3962,8 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} async-function@1.0.0: {} @@ -2608,8 +3978,15 @@ snapshots: balanced-match@1.0.2: {} + base64-arraybuffer@1.0.2: + optional: true + baseline-browser-mapping@2.10.0: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -2652,6 +4029,20 @@ snapshots: caniuse-lite@1.0.30001770: {} + canvg@3.0.11: + dependencies: + '@babel/runtime': 7.28.6 + '@types/raf': 3.4.3 + core-js: 3.48.0 + raf: 3.4.1 + regenerator-runtime: 0.13.11 + rgbcolor: 1.0.1 + stackblur-canvas: 2.7.0 + svg-pathdata: 6.0.3 + optional: true + + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -2659,6 +4050,8 @@ snapshots: client-only@0.0.1: {} + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -2669,16 +4062,81 @@ snapshots: convert-source-map@2.0.0: {} + core-js@3.48.0: + optional: true + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + css-line-break@2.1.0: + dependencies: + utrie: 1.0.2 + optional: true + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + cssstyle@6.0.1: + dependencies: + '@asamuzakjp/css-color': 4.1.2 + '@csstools/css-syntax-patches-for-csstree': 1.0.27 + css-tree: 3.1.0 + lru-cache: 11.2.6 + csstype@3.2.3: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + damerau-levenshtein@1.0.8: {} + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -2697,6 +4155,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + date-fns@4.1.0: {} + debug@3.2.7: dependencies: ms: 2.1.3 @@ -2705,6 +4165,10 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js-light@2.5.1: {} + + decimal.js@10.6.0: {} + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -2725,6 +4189,11 @@ snapshots: dependencies: esutils: 2.0.3 + dompurify@3.3.1: + optionalDependencies: + '@types/trusted-types': 2.0.7 + optional: true + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -2740,6 +4209,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + entities@6.0.1: {} + es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 @@ -2820,6 +4291,8 @@ snapshots: iterator.prototype: 1.1.5 safe-array-concat: 1.1.3 + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -2841,6 +4314,37 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + es-toolkit@1.44.0: {} + + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -3048,8 +4552,16 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} + eventemitter3@5.0.4: {} + + expect-type@1.3.0: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -3064,6 +4576,12 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-png@6.4.0: + dependencies: + '@types/pako': 2.0.4 + iobuffer: 5.4.0 + pako: 2.1.0 + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -3072,6 +4590,8 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fflate@0.8.2: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -3096,6 +4616,12 @@ snapshots: dependencies: is-callable: 1.2.7 + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -3190,10 +4716,40 @@ snapshots: dependencies: hermes-estree: 0.25.1 + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.14.1 + transitivePeerDependencies: + - '@noble/hashes' + + html2canvas@1.4.1: + dependencies: + css-line-break: 2.1.0 + text-segmentation: 1.0.3 + optional: true + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + ignore@5.3.2: {} ignore@7.0.5: {} + immer@10.2.0: {} + + immer@11.1.4: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -3207,6 +4763,10 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + internmap@2.0.3: {} + + iobuffer@5.4.0: {} + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -3280,6 +4840,8 @@ snapshots: is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -3340,6 +4902,33 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@28.1.0: + dependencies: + '@acemir/cssom': 0.9.31 + '@asamuzakjp/dom-selector': 6.8.1 + '@bramus/specificity': 2.4.2 + '@exodus/bytes': 1.14.1 + cssstyle: 6.0.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.0 + undici: 7.22.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + - supports-color + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -3354,6 +4943,21 @@ snapshots: json5@2.2.3: {} + jspdf-autotable@5.0.7(jspdf@4.2.0): + dependencies: + jspdf: 4.2.0 + + jspdf@4.2.0: + dependencies: + '@babel/runtime': 7.28.6 + fast-png: 6.4.0 + fflate: 0.8.2 + optionalDependencies: + canvg: 3.0.11 + core-js: 3.48.0 + dompurify: 3.3.1 + html2canvas: 1.4.1 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -3435,6 +5039,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@11.2.6: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -3445,6 +5051,8 @@ snapshots: math-intrinsics@1.1.0: {} + mdn-data@2.12.2: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -3470,7 +5078,7 @@ snapshots: natural-compare@1.4.0: {} - next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + next@16.1.6(@babel/core@7.29.0)(@playwright/test@1.58.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@next/env': 16.1.6 '@swc/helpers': 0.5.15 @@ -3489,6 +5097,7 @@ snapshots: '@next/swc-linux-x64-musl': 16.1.6 '@next/swc-win32-arm64-msvc': 16.1.6 '@next/swc-win32-x64-msvc': 16.1.6 + '@playwright/test': 1.58.2 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' @@ -3545,6 +5154,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + obug@2.1.1: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3568,22 +5179,41 @@ snapshots: dependencies: p-limit: 3.1.0 + pako@2.1.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse5@8.0.0: + dependencies: + entities: 6.0.1 + path-exists@4.0.0: {} path-key@3.1.1: {} path-parse@1.0.7: {} + pathe@2.0.3: {} + + performance-now@2.1.0: + optional: true + picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.3: {} + playwright-core@1.58.2: {} + + playwright@1.58.2: + dependencies: + playwright-core: 1.58.2 + optionalDependencies: + fsevents: 2.3.2 + possible-typed-array-names@1.1.0: {} postcss@8.4.31: @@ -3600,6 +5230,12 @@ snapshots: prelude-ls@1.2.1: {} + prisma@5.22.0: + dependencies: + '@prisma/engines': 5.22.0 + optionalDependencies: + fsevents: 2.3.3 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -3610,6 +5246,11 @@ snapshots: queue-microtask@1.2.3: {} + raf@3.4.1: + dependencies: + performance-now: 2.1.0 + optional: true + react-dom@19.2.3(react@19.2.3): dependencies: react: 19.2.3 @@ -3617,8 +5258,45 @@ snapshots: react-is@16.13.1: {} + react-redux@9.2.0(@types/react@19.2.14)(react@19.2.3)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 19.2.3 + use-sync-external-store: 1.6.0(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.14 + redux: 5.0.1 + + react-refresh@0.18.0: {} + react@19.2.3: {} + recharts@3.7.0(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react-is@16.13.1)(react@19.2.3)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.3)(redux@5.0.1))(react@19.2.3) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.44.0 + eventemitter3: 5.0.4 + immer: 10.2.0 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-is: 16.13.1 + react-redux: 9.2.0(@types/react@19.2.14)(react@19.2.3)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@19.2.3) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -3630,6 +5308,9 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 + regenerator-runtime@0.13.11: + optional: true + regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -3639,6 +5320,10 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + require-from-string@2.0.2: {} + + reselect@5.1.1: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -3660,6 +5345,40 @@ snapshots: reusify@1.1.0: {} + rgbcolor@1.0.1: + optional: true + + rollup@4.57.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 + fsevents: 2.3.3 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -3683,6 +5402,10 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.27.0: {} semver@6.3.1: {} @@ -3777,10 +5500,19 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + source-map-js@1.2.1: {} stable-hash@0.0.5: {} + stackback@0.0.2: {} + + stackblur-canvas@2.7.0: + optional: true + + std-env@3.10.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -3853,19 +5585,51 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svg-pathdata@6.0.3: + optional: true + + symbol-tree@3.2.4: {} + tailwindcss@4.2.0: {} tapable@2.3.0: {} + text-segmentation@1.0.3: + dependencies: + utrie: 1.0.2 + optional: true + + tiny-invariant@1.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@1.0.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinyrainbow@3.0.3: {} + + tldts-core@7.0.23: {} + + tldts@7.0.23: + dependencies: + tldts-core: 7.0.23 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.23 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -3879,6 +5643,13 @@ snapshots: tslib@2.8.1: {} + tsx@4.21.0: + dependencies: + esbuild: 0.27.3 + get-tsconfig: 4.13.6 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -3938,6 +5709,8 @@ snapshots: undici-types@6.21.0: {} + undici@7.22.0: {} + unrs-resolver@1.11.1: dependencies: napi-postinstall: 0.3.4 @@ -3972,6 +5745,101 @@ snapshots: dependencies: punycode: 2.3.1 + use-sync-external-store@1.6.0(react@19.2.3): + dependencies: + react: 19.2.3 + + utrie@1.0.2: + dependencies: + base64-arraybuffer: 1.0.2 + optional: true + + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0): + dependencies: + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 20.19.33 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.31.1 + tsx: 4.21.0 + + vitest@4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(tsx@4.21.0): + dependencies: + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.33 + jsdom: 28.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.14.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -4017,8 +5885,17 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + yallist@3.1.1: {} yocto-queue@0.1.0: {} diff --git a/prisma/dev.db b/prisma/dev.db new file mode 100644 index 0000000000000000000000000000000000000000..e69bfe3d5eb2e0ef612e938d8e7d11acc8f9c038 GIT binary patch literal 102400 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 oPnox%?y>FLu = { + tools: [ + "Quels outils IA utilisez-vous au quotidien ?", + "Comment les avez-vous choisis et intégrés à votre workflow ?", + "Utilisez-vous des rules, skills ou agents (Cursor, etc.) pour configurer vos outils ?", + "Partagez-vous une stack commune avec l'équipe ?", + ], + prompts: [ + "Comment structurez-vous vos prompts pour des tâches complexes ?", + "Utilisez-vous des templates ou des patterns réutilisables ?", + "Comment gérez-vous les cas limites et les outputs inattendus ?", + ], + context: [ + "Quel contexte fournissez-vous typiquement à l'IA ?", + "Comment décidez-vous ce qui est pertinent à inclure ?", + "Avez-vous des stratégies pour limiter le contexte tout en restant pertinent ?", + ], + iteration: [ + "Comment itérez-vous quand la première réponse ne convient pas ?", + "Décomposez-vous les tâches complexes en sous-étapes ou sous-agents ?", + "Utilisez-vous l'IA comme sparring partner pour réfléchir ?", + "Avez-vous des workflows agentiques (agents, sous-agents) ?", + ], + evaluation: [ + "Comment vérifiez-vous les sorties de l'IA avant de les utiliser ?", + "Avez-vous des critères explicites de qualité ?", + "Comment gérez-vous les hallucinations ou erreurs subtiles ?", + ], + integration: [ + "Comment l'IA est-elle discutée et partagée dans l'équipe ?", + "Y a-t-il des pratiques formalisées ou documentées ?", + "Comment les nouveaux arrivants sont-ils onboardés sur ces usages ?", + ], + usecases: [ + "Quels cas d'usage couvrez-vous (snippets, tests, refacto, debug...) ?", + "Quel est votre cas d'usage principal ?", + "Avez-vous exploré des usages plus avancés (discovery, review, agents, skills) ?", + ], + impact: [ + "Quel impact mesurable l'IA a-t-elle sur votre delivery ?", + "Comment le quantifiez-vous (temps, qualité, vélocité) ?", + "L'IA est-elle un levier stratégique pour l'équipe ?", + ], + risks: [ + "Quelles précautions prenez-vous (sécurité, confidentialité, biais) ?", + "Y a-t-il des règles partagées ou une doctrine ?", + "Comment gérez-vous les risques liés aux données sensibles ?", + ], + alignment: [ + "Comment vous assurez-vous que le code généré respecte vos standards ?", + "Avez-vous des garde-fous pour l'alignement archi ?", + "Comment gérez-vous le rework quand l'IA sort du cadre ?", + ], + code_quality: [ + "Quelle qualité de code attendez-vous des sorties IA ?", + "Comment validez-vous la maintenabilité ?", + "Avez-vous des exemples où le code généré était fragile ?", + ], + quality_usage: [ + "Utilisez-vous l'IA pour les tests ? Comment ?", + "Et pour la revue de code ou le refactoring ?", + "L'IA est-elle un levier pour améliorer la qualité globale ?", + ], + capitalization: [ + "Comment capitalisez-vous les prompts, rules, skills et bonnes pratiques ?", + "Y a-t-il une base partagée (rules, skills, wiki) ?", + "Comment partagez-vous les retours d'expérience ?", + ], + learning: [ + "Comment l'IA vous aide-t-elle à monter en compétence ?", + "Utilisez-vous l'IA pour comprendre des patterns ou concepts ?", + "Évitez-vous la dépendance passive (copier-coller sans comprendre) ?", + ], + measurement: [ + "Comment mesurez-vous l'usage et l'impact de l'IA ?", + "Avez-vous des indicateurs ou du feedback utilisateur ?", + "Comment pilotez-vous l'adoption et l'amélioration ?", + ], +}; + +const RUBRICS: Record = { + tools: "1:Usage ponctuel — utilisation occasionnelle, sans réelle stratégie ni critères de choix;2:Outil identifié — un outil principal utilisé, choix fait de manière informelle;3:Usage raisonné — comparaison de plusieurs outils, critères explicites (coût, latence, qualité);4:Stack partagée — stack commune à l'équipe, documentée et maintenue;5:Intégré au workflow — adoption généralisée", + prompts: "1:Vague — prompts improvisés, peu de structure, résultats aléatoires;2:Simple — prompts basiques avec instructions claires mais sans systématisation;3:Structuré — format cohérent (rôle, contexte, tâche, format attendu), testés manuellement;4:Templates — bibliothèque de prompts réutilisables, versionnés;5:Prompt engineering maîtrisé — techniques avancées (chain-of-thought, few-shot), optimisation continue, validation des outputs", + context: "1:Peu — contexte minimal fourni, l'IA manque d'informations pour bien répondre;2:Partiel — contexte partiel, parfois pertinent, parfois manquant;3:Suffisant — contexte adapté à la tâche, couvre les éléments essentiels;4:Structuré — contexte organisé (sections, priorités), stratégie de sélection;5:Contextualisation avancée — RAG, embedding, contexte dynamique selon la requête", + iteration: "1:One-shot — une seule tentative, pas de retry si le résultat est insuffisant;2:Quelques itérations — 2-3 essais manuels si la première réponse échoue;3:Itératif — approche systématique: retry, reformulation, décomposition en sous-tâches;4:Décomposition — tâches complexes découpées en étapes, prompts en chaîne;5:IA sparring partner — dialogue continu avec l'IA pour explorer, affiner, challenger les réponses", + evaluation: "1:Acceptation — acceptation des sorties sans vérification significative;2:Relecture superficielle — lecture rapide, pas de critères explicites;3:Vérif fonctionnelle — tests manuels ou automatisés, vérification du comportement;4:Regard archi — évaluation de la maintenabilité, alignement, cohérence;5:Culture critique — critères de qualité partagés, revue systématique, détection des hallucinations", + integration: "1:Isolé — usage personnel, pas de partage ou discussion en équipe;2:Discussions — échanges informels sur les usages, pas de formalisation;3:Partages — démos, retours d'expérience, bonnes pratiques partagées;4:Formalisé — pratiques documentées, onboarding, standards d'usage;5:Doctrine équipe — vision partagée, roadmap IA, adoption comme pilier de la stratégie", + usecases: "1:Snippets — usage limité à la génération de petits snippets ou complétion;2:Code basique — génération de fonctions, classes, scripts simples;3:Tests/refacto — génération de tests, refactoring, documentation;4:Debug/opt — aide au debug, optimisation, analyse de code;5:Discovery→review — exploration de solutions, design, revue de code, boucle complète", + impact: "1:Aucun — pas d'impact visible sur la delivery;2:Marginal — gain de temps perçu mais non quantifié;3:Accélération — vélocité accrue, moins de tâches répétitives;4:Gain mesurable — métriques (temps, qualité, vélocité) documentées;5:Levier clair — IA comme levier stratégique, pilotage de l'adoption, ROI démontré", + risks: "1:Aucune — pas de considération des risques (sécurité, confidentialité, biais);2:Sensibilisation — conscience des risques, pas de processus formalisé;3:Bonnes pratiques — précautions appliquées (données, prompts, sanitization);4:Règles partagées — règles d'usage, checklist, validation des données;5:Doctrine — politique de sécurité IA, gouvernance, revue des risques", + alignment: "1:Hors standards — code généré souvent non conforme, rework systématique;2:Rework lourd — modifications importantes nécessaires pour aligner;3:Cohérent — code généralement aligné, quelques ajustements;4:Aligné — prompts et garde-fous pour respecter standards et archi;5:Quasi conforme — sorties quasi conformes, validation automatisée possible", + code_quality: "1:Peu maintenable — code fragile, difficile à faire évoluer;2:Correct fragile — fonctionne mais cas limites non gérés;3:Maintenable — code propre, testable, évolutif;4:Propre structuré — patterns respectés, séparation des responsabilités;5:Quasi senior — qualité équivalente à un code produit par un dev senior", + quality_usage: "1:Rarement — utilisation peu fréquente pour la qualité;2:Tests simples — génération de tests unitaires basiques;3:Tests utiles — tests pertinents, couverture, refacto assistée;4:Refacto guidée — IA pour identifier du code à améliorer, suggérer des refactorings;5:Levier qualité — IA intégrée dans la boucle qualité (review, dette technique, standards)", + capitalization: "1:None — pas de capitalisation, tout est dans la tête ou éparpillé;2:Informel — notes personnelles, partage oral;3:Bonnes pratiques — document informal, exemples partagés;4:Base prompts — bibliothèque de prompts, wiki interne;5:Wiki & REX — base documentée, retours d'expérience, amélioration continue", + learning: "1:Dépendance — copier-coller sans comprendre, risque de régression;2:Apprentissage limité — utilisation pour débloquer mais compréhension superficielle;3:Compréhension — IA pour comprendre les concepts, valider sa compréhension;4:IA pour patterns — utilisation pour apprendre des patterns, architectures, bonnes pratiques;5:Accélérateur de progression — IA comme outil de montée en compétence structurée", + measurement: "1:Aucun suivi — pas de mesure de l'usage ou de l'impact;2:Perception — sentiment d'impact, pas de données;3:Feedback — retours utilisateurs, observations qualitatives;4:Indicateurs simples — métriques d'usage (adoption, volume), premiers KPIs;5:Pilotage structuré — tableau de bord, suivi de l'adoption, pilotage de l'amélioration", +}; + +const TEMPLATES_DATA = [ + { + id: "full-15", + name: "Full - 15 dimensions", + dimensions: [ + { id: "tools", title: "Choix & maîtrise des outils", rubric: RUBRICS.tools }, + { id: "prompts", title: "Clarté des prompts", rubric: RUBRICS.prompts }, + { id: "context", title: "Pertinence du contexte fourni", rubric: RUBRICS.context }, + { id: "iteration", title: "Capacité d'itération", rubric: RUBRICS.iteration }, + { id: "evaluation", title: "Évaluation critique", rubric: RUBRICS.evaluation }, + { id: "integration", title: "Intégration dans les pratiques d'équipe", rubric: RUBRICS.integration }, + { id: "usecases", title: "Cas d'usage couverts", rubric: RUBRICS.usecases }, + { id: "impact", title: "Impact sur la delivery", rubric: RUBRICS.impact }, + { id: "risks", title: "Gestion risques & sécurité", rubric: RUBRICS.risks }, + { id: "alignment", title: "Alignement archi & standards", rubric: RUBRICS.alignment }, + { id: "code_quality", title: "Qualité du code généré", rubric: RUBRICS.code_quality }, + { id: "quality_usage", title: "Usage pour la qualité (tests, review)", rubric: RUBRICS.quality_usage }, + { id: "capitalization", title: "Capitalisation & partage", rubric: RUBRICS.capitalization }, + { id: "learning", title: "Montée en compétence via IA", rubric: RUBRICS.learning }, + { id: "measurement", title: "Mesure & pilotage", rubric: RUBRICS.measurement }, + ], + }, +]; + +async function main() { + // Sync templates & dimensions only — ne touche pas aux évaluations, users, audit logs + for (const t of TEMPLATES_DATA) { + const template = await prisma.template.upsert({ + where: { id: t.id }, + create: { id: t.id, name: t.name }, + update: { name: t.name }, + }); + for (let i = 0; i < t.dimensions.length; i++) { + const d = t.dimensions[i]; + const questions = SUGGESTED_QUESTIONS[d.id]; + await prisma.templateDimension.upsert({ + where: { + templateId_slug: { templateId: template.id, slug: d.id }, + }, + create: { + templateId: template.id, + slug: d.id, + orderIndex: i, + title: d.title, + rubric: d.rubric, + suggestedQuestions: questions ? JSON.stringify(questions) : null, + }, + update: { + orderIndex: i, + title: d.title, + rubric: d.rubric, + suggestedQuestions: questions ? JSON.stringify(questions) : null, + }, + }); + } + } + + // Bootstrap demo data uniquement si la DB est vide + const evalCount = await prisma.evaluation.count(); + if (evalCount === 0) { + const template = await prisma.template.findUnique({ where: { id: "full-15" } }); + if (!template) throw new Error("Template not found"); + + await prisma.user.upsert({ + where: { email: "admin@cars-front.local" }, + create: { email: "admin@cars-front.local", name: "Admin User", role: "admin" }, + update: {}, + }); + + const dims = await prisma.templateDimension.findMany({ + where: { templateId: template.id }, + orderBy: { orderIndex: "asc" }, + }); + + const candidates = [ + { name: "Alice Chen", role: "Senior ML Engineer", evaluator: "Jean Dupont" }, + { name: "Bob Martin", role: "Data Scientist", evaluator: "Marie Curie" }, + { name: "Carol White", role: "AI Product Manager", evaluator: "Jean Dupont" }, + ]; + + for (let i = 0; i < candidates.length; i++) { + const c = candidates[i]; + const evaluation = await prisma.evaluation.create({ + data: { + candidateName: c.name, + candidateRole: c.role, + evaluatorName: c.evaluator, + evaluationDate: new Date(2025, 1, 15 + i), + templateId: template.id, + status: i === 0 ? "submitted" : "draft", + findings: i === 0 ? "Bonne maîtrise des outils et des prompts. Axes d'amélioration : capitalisation et mesure." : null, + recommendations: i === 0 ? "Former sur la capitalisation des prompts et mettre en place des indicateurs." : null, + }, + }); + for (const d of dims) { + const score = 2 + Math.floor(Math.random() * 3); + await prisma.dimensionScore.create({ + data: { + evaluationId: evaluation.id, + dimensionId: d.id, + score, + justification: `Justification pour ${d.title}`, + examplesObserved: `Observé : ${d.title} niveau ${score}`, + confidence: ["low", "med", "high"][Math.floor(Math.random() * 3)], + }, + }); + } + } + console.log("Seed complete: templates synced, demo evaluations created"); + } else { + console.log("Seed complete: templates synced, evaluations preserved"); + } +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(() => prisma.$disconnect()); diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx new file mode 100644 index 0000000..dd50bbc --- /dev/null +++ b/src/app/admin/page.tsx @@ -0,0 +1,66 @@ +"use client"; + +import { useState, useEffect } from "react"; + +interface Template { + id: string; + name: string; + dimensions: { id: string; title: string; orderIndex: number }[]; +} + +export default function AdminPage() { + const [templates, setTemplates] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetch("/api/templates") + .then((r) => r.json()) + .then(setTemplates) + .finally(() => setLoading(false)); + }, []); + + if (loading) { + return
loading...
; + } + + return ( +
+

Admin

+

+ Modèles. CRUD via /api/templates +

+ +
+

Modèles

+
+ {templates.map((t) => ( +
+

{t.name}

+

+ {t.dimensions.length} dim. +

+
    + {t.dimensions.slice(0, 5).map((d) => ( +
  • • {d.title}
  • + ))} + {t.dimensions.length > 5 && ( +
  • +{t.dimensions.length - 5}
  • + )} +
+
+ ))} +
+
+ +
+

Users

+

+ admin@cars-front.local +

+
+
+ ); +} diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts new file mode 100644 index 0000000..ebe7fcc --- /dev/null +++ b/src/app/api/auth/route.ts @@ -0,0 +1,38 @@ +import { NextRequest, NextResponse } from "next/server"; + +/** + * Mock auth - MVP: single admin login + * In production: use NextAuth, Clerk, or Supabase Auth + */ +const MOCK_ADMIN = { + email: "admin@cars-front.local", + name: "Admin User", + role: "admin", +}; + +export async function POST(req: NextRequest) { + try { + const body = await req.json(); + const { email, password } = body; + + // Accept any email for MVP; in prod validate against DB + if (!email) { + return NextResponse.json({ error: "Email required" }, { status: 400 }); + } + + // Mock: accept "admin" or any password for dev + const user = { + ...MOCK_ADMIN, + email: email || MOCK_ADMIN.email, + }; + + return NextResponse.json({ user, token: "mock-jwt-" + Date.now() }); + } catch { + return NextResponse.json({ error: "Auth failed" }, { status: 500 }); + } +} + +export async function GET() { + // Check session - mock: always return admin + return NextResponse.json({ user: MOCK_ADMIN }); +} diff --git a/src/app/api/evaluations/[id]/route.ts b/src/app/api/evaluations/[id]/route.ts new file mode 100644 index 0000000..45dbe49 --- /dev/null +++ b/src/app/api/evaluations/[id]/route.ts @@ -0,0 +1,176 @@ +import { NextRequest, NextResponse } from "next/server"; +import { Prisma } from "@prisma/client"; +import { prisma } from "@/lib/db"; + +export async function GET( + _req: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { id } = await params; + const evaluation = await prisma.evaluation.findUnique({ + where: { id }, + include: { + template: { + include: { + dimensions: { orderBy: { orderIndex: "asc" } }, + }, + }, + dimensionScores: { include: { dimension: true } }, + }, + }); + + if (!evaluation) { + return NextResponse.json({ error: "Evaluation not found" }, { status: 404 }); + } + + // Prisma ORM omits suggestedQuestions in some contexts — fetch via raw + const templateId = evaluation.templateId; + const dimsRaw = evaluation.template + ? ((await prisma.$queryRaw( + Prisma.sql`SELECT id, slug, title, rubric, "orderIndex", "suggestedQuestions" FROM "TemplateDimension" WHERE "templateId" = ${templateId} ORDER BY "orderIndex" ASC` + )) as { id: string; slug: string; title: string; rubric: string; orderIndex: number; suggestedQuestions: string | null }[]) + : []; + + const dimMap = new Map(dimsRaw.map((d) => [d.id, d])); + + const out = { + ...evaluation, + template: evaluation.template + ? { + ...evaluation.template, + dimensions: evaluation.template.dimensions.map((d) => { + const raw = dimMap.get(d.id); + return { + id: d.id, + slug: d.slug, + title: d.title, + rubric: d.rubric, + orderIndex: d.orderIndex, + suggestedQuestions: raw?.suggestedQuestions ?? d.suggestedQuestions, + }; + }), + } + : null, + dimensionScores: evaluation.dimensionScores.map((ds) => ({ + ...ds, + dimension: ds.dimension + ? { + id: ds.dimension.id, + slug: ds.dimension.slug, + title: ds.dimension.title, + rubric: ds.dimension.rubric, + suggestedQuestions: dimMap.get(ds.dimension.id)?.suggestedQuestions ?? ds.dimension.suggestedQuestions, + } + : null, + })), + }; + return NextResponse.json(out); + } catch (e) { + console.error(e); + return NextResponse.json({ error: "Failed to fetch evaluation" }, { status: 500 }); + } +} + +export async function PUT( + req: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { id } = await params; + const body = await req.json(); + + const { candidateName, candidateRole, evaluatorName, evaluationDate, status, findings, recommendations, dimensionScores } = body; + + const existing = await prisma.evaluation.findUnique({ where: { id } }); + if (!existing) { + return NextResponse.json({ error: "Evaluation not found" }, { status: 404 }); + } + + const updateData: Record = {}; + if (candidateName != null) updateData.candidateName = candidateName; + if (candidateRole != null) updateData.candidateRole = candidateRole; + if (evaluatorName != null) updateData.evaluatorName = evaluatorName; + if (evaluationDate != null) updateData.evaluationDate = new Date(evaluationDate); + if (status != null) updateData.status = status; + if (findings != null) updateData.findings = findings; + if (recommendations != null) updateData.recommendations = recommendations; + + if (Object.keys(updateData).length > 0) { + await prisma.auditLog.create({ + data: { + evaluationId: id, + action: "updated", + newValue: JSON.stringify(updateData), + }, + }); + } + + const evaluation = await prisma.evaluation.update({ + where: { id }, + data: updateData, + include: { + template: { include: { dimensions: { orderBy: { orderIndex: "asc" } } } }, + dimensionScores: { include: { dimension: true } }, + }, + }); + + if (dimensionScores && Array.isArray(dimensionScores)) { + for (const ds of dimensionScores) { + if (ds.dimensionId) { + await prisma.dimensionScore.upsert({ + where: { + evaluationId_dimensionId: { + evaluationId: id, + dimensionId: ds.dimensionId, + }, + }, + update: { + score: ds.score, + justification: ds.justification, + examplesObserved: ds.examplesObserved, + confidence: ds.confidence, + candidateNotes: ds.candidateNotes, + }, + create: { + evaluationId: id, + dimensionId: ds.dimensionId, + score: ds.score, + justification: ds.justification, + examplesObserved: ds.examplesObserved, + confidence: ds.confidence, + candidateNotes: ds.candidateNotes, + }, + }); + } + } + } + + const updated = await prisma.evaluation.findUnique({ + where: { id }, + include: { + template: { include: { dimensions: { orderBy: { orderIndex: "asc" } } } }, + dimensionScores: { include: { dimension: true } }, + }, + }); + + return NextResponse.json(updated); + } catch (e) { + console.error(e); + return NextResponse.json({ error: "Failed to update evaluation" }, { status: 500 }); + } +} + +export async function DELETE( + _req: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { id } = await params; + await prisma.evaluation.delete({ where: { id } }); + return NextResponse.json({ ok: true }); + } catch (e) { + console.error(e); + return NextResponse.json({ error: "Failed to delete evaluation" }, { status: 500 }); + } +} diff --git a/src/app/api/evaluations/route.ts b/src/app/api/evaluations/route.ts new file mode 100644 index 0000000..f9eb385 --- /dev/null +++ b/src/app/api/evaluations/route.ts @@ -0,0 +1,87 @@ +import { NextRequest, NextResponse } from "next/server"; +import { prisma } from "@/lib/db"; + +export async function GET(req: NextRequest) { + try { + const { searchParams } = new URL(req.url); + const status = searchParams.get("status"); + const templateId = searchParams.get("templateId"); + + const evaluations = await prisma.evaluation.findMany({ + where: { + ...(status && { status }), + ...(templateId && { templateId }), + }, + include: { + template: true, + dimensionScores: { include: { dimension: true } }, + }, + orderBy: { evaluationDate: "desc" }, + }); + + return NextResponse.json(evaluations); + } catch (e) { + console.error(e); + return NextResponse.json({ error: "Failed to fetch evaluations" }, { status: 500 }); + } +} + +export async function POST(req: NextRequest) { + try { + const body = await req.json(); + const { candidateName, candidateRole, evaluatorName, evaluationDate, templateId } = body; + + if (!candidateName || !candidateRole || !evaluatorName || !evaluationDate || !templateId) { + return NextResponse.json( + { error: "Missing required fields: candidateName, candidateRole, evaluatorName, evaluationDate, templateId" }, + { status: 400 } + ); + } + + const template = await prisma.template.findUnique({ + where: { id: templateId }, + include: { dimensions: { orderBy: { orderIndex: "asc" } } }, + }); + if (!template) { + return NextResponse.json({ error: "Template not found" }, { status: 404 }); + } + + const evaluation = await prisma.evaluation.create({ + data: { + candidateName, + candidateRole, + evaluatorName, + evaluationDate: new Date(evaluationDate), + templateId, + status: "draft", + }, + include: { + template: true, + dimensionScores: { include: { dimension: true } }, + }, + }); + + // Create empty dimension scores for each template dimension + for (const dim of template.dimensions) { + await prisma.dimensionScore.create({ + data: { + evaluationId: evaluation.id, + dimensionId: dim.id, + }, + }); + } + + const updated = await prisma.evaluation.findUnique({ + where: { id: evaluation.id }, + include: { + template: true, + dimensionScores: { include: { dimension: true } }, + }, + }); + + return NextResponse.json(updated); + } catch (e) { + console.error(e); + return NextResponse.json({ error: "Failed to create evaluation" }, { status: 500 }); + } +} diff --git a/src/app/api/export/csv/route.ts b/src/app/api/export/csv/route.ts new file mode 100644 index 0000000..6cce234 --- /dev/null +++ b/src/app/api/export/csv/route.ts @@ -0,0 +1,38 @@ +import { NextRequest, NextResponse } from "next/server"; +import { prisma } from "@/lib/db"; +import { evaluationToCsvRows } from "@/lib/export-utils"; + +export async function GET(req: NextRequest) { + try { + const { searchParams } = new URL(req.url); + const id = searchParams.get("id"); + if (!id) { + return NextResponse.json({ error: "Evaluation id required" }, { status: 400 }); + } + + const evaluation = await prisma.evaluation.findUnique({ + where: { id }, + include: { + template: true, + dimensionScores: { include: { dimension: true } }, + }, + }); + + if (!evaluation) { + return NextResponse.json({ error: "Evaluation not found" }, { status: 404 }); + } + + const rows = evaluationToCsvRows(evaluation as Parameters[0]); + const csv = rows.map((r) => r.map((c) => `"${String(c).replace(/"/g, '""')}"`).join(",")).join("\n"); + + return new NextResponse(csv, { + headers: { + "Content-Type": "text/csv", + "Content-Disposition": `attachment; filename="evaluation-${id}.csv"`, + }, + }); + } catch (e) { + console.error(e); + return NextResponse.json({ error: "Export failed" }, { status: 500 }); + } +} diff --git a/src/app/api/export/pdf/route.ts b/src/app/api/export/pdf/route.ts new file mode 100644 index 0000000..5dbc7e2 --- /dev/null +++ b/src/app/api/export/pdf/route.ts @@ -0,0 +1,68 @@ +import { NextRequest, NextResponse } from "next/server"; +import { prisma } from "@/lib/db"; +import { jsPDF } from "jspdf"; +import autoTable from "jspdf-autotable"; +import { format } from "date-fns"; + +export async function GET(req: NextRequest) { + try { + const { searchParams } = new URL(req.url); + const id = searchParams.get("id"); + if (!id) { + return NextResponse.json({ error: "Evaluation id required" }, { status: 400 }); + } + + const evaluation = await prisma.evaluation.findUnique({ + where: { id }, + include: { + template: true, + dimensionScores: { include: { dimension: true } }, + }, + }); + + if (!evaluation) { + return NextResponse.json({ error: "Evaluation not found" }, { status: 404 }); + } + + const doc = new jsPDF(); + doc.setFontSize(18); + doc.text("Évaluation Maturité IA Gen", 14, 20); + doc.setFontSize(10); + doc.text(`Candidat : ${evaluation.candidateName} | Rôle : ${evaluation.candidateRole}`, 14, 28); + doc.text(`Évaluateur : ${evaluation.evaluatorName} | Date : ${format(evaluation.evaluationDate, "yyyy-MM-dd")}`, 14, 34); + doc.text(`Modèle : ${evaluation.template?.name ?? ""} | Statut : ${evaluation.status === "submitted" ? "Soumise" : "Brouillon"}`, 14, 40); + + const tableData = evaluation.dimensionScores.map((ds) => [ + ds.dimension.title, + String(ds.score ?? "-"), + ds.justification ?? "", + ds.confidence ?? "", + ]); + + autoTable(doc, { + startY: 48, + head: [["Dimension", "Score", "Justification", "Confiance"]], + body: tableData, + theme: "striped", + }); + + const finalY = + (doc as unknown as { lastAutoTable?: { finalY: number } }).lastAutoTable?.finalY ?? 48; + doc.setFontSize(10); + doc.text("Synthèse :", 14, finalY + 12); + doc.text(evaluation.findings ?? "N/A", 14, finalY + 18, { maxWidth: 180 }); + doc.text("Recommandations :", 14, finalY + 28); + doc.text(evaluation.recommendations ?? "N/A", 14, finalY + 34, { maxWidth: 180 }); + + const buf = Buffer.from(doc.output("arraybuffer")); + return new NextResponse(buf, { + headers: { + "Content-Type": "application/pdf", + "Content-Disposition": `attachment; filename="evaluation-${id}.pdf"`, + }, + }); + } catch (e) { + console.error(e); + return NextResponse.json({ error: "Export failed" }, { status: 500 }); + } +} diff --git a/src/app/api/templates/data/route.ts b/src/app/api/templates/data/route.ts new file mode 100644 index 0000000..d1ebd04 --- /dev/null +++ b/src/app/api/templates/data/route.ts @@ -0,0 +1,29 @@ +import { NextResponse } from "next/server"; +import { prisma } from "@/lib/db"; + +/** Returns templates in the canonical JSON format: { templates: [{ id, name, dimensions: [{ id, title, rubric }] }] } */ +export async function GET() { + try { + const templates = await prisma.template.findMany({ + include: { + dimensions: { orderBy: { orderIndex: "asc" } }, + }, + }); + const data = { + templates: templates.map((t) => ({ + id: t.id, + name: t.name, + dimensions: t.dimensions.map((d) => ({ + id: d.slug, + title: d.title, + rubric: d.rubric, + suggestedQuestions: d.suggestedQuestions, + })), + })), + }; + return NextResponse.json(data); + } catch (e) { + console.error(e); + return NextResponse.json({ error: "Failed to fetch templates" }, { status: 500 }); + } +} diff --git a/src/app/api/templates/route.ts b/src/app/api/templates/route.ts new file mode 100644 index 0000000..c69635f --- /dev/null +++ b/src/app/api/templates/route.ts @@ -0,0 +1,29 @@ +import { NextResponse } from "next/server"; +import { Prisma } from "@prisma/client"; +import { prisma } from "@/lib/db"; + +export async function GET() { + try { + const templates = await prisma.template.findMany({ + include: { + dimensions: { orderBy: { orderIndex: "asc" } }, + }, + }); + // Prisma ORM omits suggestedQuestions — enrich via raw + const dimsRaw = (await prisma.$queryRaw( + Prisma.sql`SELECT id, "templateId", slug, title, rubric, "orderIndex", "suggestedQuestions" FROM "TemplateDimension" ORDER BY "templateId", "orderIndex"` + )) as { id: string; templateId: string; slug: string; title: string; rubric: string; orderIndex: number; suggestedQuestions: string | null }[]; + const dimMap = new Map(dimsRaw.map((d) => [d.id, d])); + const out = templates.map((t) => ({ + ...t, + dimensions: t.dimensions.map((d) => ({ + ...d, + suggestedQuestions: dimMap.get(d.id)?.suggestedQuestions ?? d.suggestedQuestions, + })), + })); + return NextResponse.json(out); + } catch (e) { + console.error(e); + return NextResponse.json({ error: "Failed to fetch templates" }, { status: 500 }); + } +} diff --git a/src/app/evaluations/[id]/page.tsx b/src/app/evaluations/[id]/page.tsx new file mode 100644 index 0000000..a07080d --- /dev/null +++ b/src/app/evaluations/[id]/page.tsx @@ -0,0 +1,361 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { useParams, useRouter } from "next/navigation"; +import { CandidateForm } from "@/components/CandidateForm"; +import { DimensionCard } from "@/components/DimensionCard"; +import { RadarChart } from "@/components/RadarChart"; +import { ExportModal } from "@/components/ExportModal"; +import { ConfirmModal } from "@/components/ConfirmModal"; +import { generateFindings, generateRecommendations, computeAverageScore } from "@/lib/export-utils"; + +interface Dimension { + id: string; + slug: string; + title: string; + rubric: string; + suggestedQuestions?: string | null; +} + +interface DimensionScore { + id: string; + dimensionId: string; + score: number | null; + justification: string | null; + examplesObserved: string | null; + confidence: string | null; + candidateNotes: string | null; + dimension: Dimension; +} + +interface Evaluation { + id: string; + candidateName: string; + candidateRole: string; + evaluatorName: string; + evaluationDate: string; + templateId: string; + template: { id: string; name: string; dimensions: Dimension[] }; + status: string; + findings: string | null; + recommendations: string | null; + dimensionScores: DimensionScore[]; +} + +export default function EvaluationDetailPage() { + const params = useParams(); + const router = useRouter(); + const id = params.id as string; + const [evaluation, setEvaluation] = useState(null); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [templates, setTemplates] = useState<{ id: string; name: string }[]>([]); + const [exportOpen, setExportOpen] = useState(false); + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); + + const fetchEval = useCallback(() => { + setLoading(true); + Promise.all([ + fetch(`/api/evaluations/${id}`).then((r) => r.json()), + fetch("/api/templates").then((r) => r.json()), + ]) + .then(([evalData, templatesData]) => { + setTemplates(Array.isArray(templatesData) ? templatesData : []); + if (evalData?.error) { + setEvaluation(null); + return; + } + try { + if (evalData?.template?.dimensions?.length > 0 && Array.isArray(templatesData)) { + const tmpl = templatesData.find((t: { id: string }) => t.id === evalData.templateId); + if (tmpl?.dimensions?.length) { + const dimMap = new Map(tmpl.dimensions.map((d: { id: string }) => [d.id, d])); + evalData.template.dimensions = evalData.template.dimensions.map((d: { id: string }) => ({ + ...d, + suggestedQuestions: d.suggestedQuestions ?? dimMap.get(d.id)?.suggestedQuestions, + })); + } + } + } catch { + /* merge failed, use evalData as-is */ + } + setEvaluation({ ...evalData, dimensionScores: evalData.dimensionScores ?? [] }); + }) + .catch(() => setEvaluation(null)) + .finally(() => setLoading(false)); + }, [id]); + + useEffect(() => { + fetchEval(); + }, [fetchEval]); + + // Draft backup to localStorage (debounced, for offline resilience) + useEffect(() => { + if (!evaluation || !id) return; + const t = setTimeout(() => { + try { + localStorage.setItem( + `eval-draft-${id}`, + JSON.stringify({ ...evaluation, evaluationDate: evaluation.evaluationDate }) + ); + } catch { + /* ignore */ + } + }, 2000); + return () => clearTimeout(t); + }, [evaluation, id]); + + const handleFormChange = (field: string, value: string) => { + if (!evaluation) return; + setEvaluation((e) => (e ? { ...e, [field]: value } : null)); + }; + + const handleScoreChange = (dimensionId: string, data: Partial) => { + if (!evaluation) return; + setEvaluation((e) => { + if (!e) return null; + const scores = e.dimensionScores.map((ds) => + ds.dimensionId === dimensionId ? { ...ds, ...data } : ds + ); + return { ...e, dimensionScores: scores }; + }); + }; + + const handleSave = async () => { + if (!evaluation) return; + setSaving(true); + try { + const res = await fetch(`/api/evaluations/${id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + candidateName: evaluation.candidateName, + candidateRole: evaluation.candidateRole, + evaluatorName: evaluation.evaluatorName, + evaluationDate: evaluation.evaluationDate, + status: evaluation.status, + findings: evaluation.findings, + recommendations: evaluation.recommendations, + dimensionScores: (evaluation.dimensionScores ?? []).map((ds) => ({ + dimensionId: ds.dimensionId, + evaluationId: id, + score: ds.score, + justification: ds.justification, + examplesObserved: ds.examplesObserved, + confidence: ds.confidence, + candidateNotes: ds.candidateNotes, + })), + }), + }); + if (res.ok) { + fetchEval(); + } else { + const data = await res.json(); + alert(data.error ?? "Save failed"); + } + } finally { + setSaving(false); + } + }; + + const handleGenerateFindings = () => { + if (!evaluation) return; + const findings = generateFindings(evaluation.dimensionScores ?? []); + const recommendations = generateRecommendations(evaluation.dimensionScores ?? []); + setEvaluation((e) => (e ? { ...e, findings, recommendations } : null)); + }; + + const allFives = evaluation?.dimensionScores?.every( + (ds) => ds.score === 5 && (!ds.justification || ds.justification.trim() === "") + ); + const showAllFivesWarning = allFives && evaluation?.status === "submitted"; + + if (loading) { + return
loading...
; + } + if (!evaluation) { + return ( +
+ Évaluation introuvable.{" "} + + ← dashboard + +
+ ); + } + + const dimensions = evaluation.template?.dimensions ?? []; + const dimensionScores = evaluation.dimensionScores ?? []; + const scoreMap = new Map(dimensionScores.map((ds) => [ds.dimensionId, ds])); + const radarData = dimensionScores + .filter((ds) => ds.score != null) + .map((ds) => { + const title = ds.dimension?.title ?? ""; + return { + dimension: title.length > 12 ? title.slice(0, 12) + "…" : title, + score: ds.score ?? 0, + fullMark: 5, + }; + }); + const avgScore = computeAverageScore(dimensionScores); + + return ( +
+
+

+ {evaluation.candidateName} / {evaluation.candidateRole} +

+
+ + +
+
+ + {showAllFivesWarning && ( +
+ ⚠ Tous les scores = 5 sans justification +
+ )} + +
+

Session

+ +
+ +
+

Dimensions

+ +
+ {dimensions.map((dim, i) => ( +
+ +
+ ))} +
+
+ +
+

Synthèse

+

+ Moyenne {avgScore.toFixed(1)}/5 +

+ +
+
+ +