diff --git a/PR-SAP.md b/PR-SAP.md new file mode 100644 index 00000000..0fcd0539 --- /dev/null +++ b/PR-SAP.md @@ -0,0 +1,119 @@ +# SAP S/4HANA ERP — Procurement & Approval SPA + +> Closes #1 · Bounty: $1,000 + +## Overview + +A polished SAP S/4HANA–style enterprise resource planning single-page application focused on **procurement management** and **approval workflows**. Built with the exact same stack as existing SPAs in this repo (Vite + React 19 + TypeScript strict + Tailwind v4 + dojo-hooks), fully offline with rich seed data. + +## Screenshots + +### Dashboard — KPI Widgets & Pending Items +Six hero KPI cards (revenue, open POs, pending approvals, budget utilization, total spend, on-time delivery) powered by `useDojoState`. Panels showing POs awaiting approval and upcoming tasks with direct navigation. + +### Procurement — List, Filter & Detail Views +Full purchase order table with status/priority filters and global search. Detail view with line-item table, vendor info card, approve/reject actions, and threaded comments. + +### Task Inbox — Approval Queue +Card-based task grid with status chips, progress bars, priority indicators, and cycle-status toggles. Supports filtering and empty state. Summary counts at top. + +### Audit Trail — Activity Timeline +Chronological grouped timeline recording every navigation, approval, rejection, comment, and status change. Color-coded by category (procurement, approval, navigation, system). Stored in `dojo.state.auditLog` for RL verification. + +## Views Implemented + +| # | View | Description | +|---|------|-------------| +| 1 | **Dashboard** | 6 KPI hero widgets, pending POs panel, upcoming tasks panel | +| 2 | **Procurement List** | Filterable PO table (status, priority), global search integration | +| 3 | **PO Detail** | Line items, vendor card, approve/reject buttons, comment thread | +| 4 | **Task Inbox** | 14 approval tasks as cards, status toggle, progress bars, empty state | +| 5 | **Audit Trail** | Grouped timeline of all session actions for RL verification | + +## Seed Data + +- **12 purchase orders** across 7 statuses (Draft, Pending Approval, Approved, Rejected, In Delivery, Received, Cancelled) +- **10 vendors** with ratings, categories, and contact details (Siemens, Bosch, BASF, Caterpillar, 3M, ABB, Schneider, Honeywell, Mitsubishi, Emerson) +- **14 approval tasks** of 5 types (PO, Invoice, Budget Transfer, Material Request, Vendor Onboarding) +- **6 KPIs** with trend indicators +- **12 audit log entries** pre-seeded, with live additions on every user action + +## Technical Stack + +| Requirement | Implementation | +|-------------|---------------| +| React 19 + TypeScript strict | ✅ `tsc -b` passes with 0 errors | +| `@chakra-dev/dojo-hooks` | ✅ Single `useDojoState` in AppProvider — no bespoke stores | +| Tailwind CSS v4 via `@tailwindcss/vite` | ✅ No postcss.config.js | +| `vite-plugin-singlefile` | ✅ `dist/index.html` = 282 KB (82 KB gzipped) | +| `lucide-react` icons | ✅ Used throughout all views | +| No auth flows | ✅ Boots directly into dashboard | +| Responsive | ✅ Tested at 1280px and 1440px | +| Fully offline | ✅ All seed data embedded | + +## SAP Fiori Theme + +- **Sidebar**: SAP Shell dark blue (`#354a5f`) with gold hex logo accent +- **Content**: Clean white cards on `#f7f7f7` background +- **Status colors**: SAP semantic palette (positive green, negative red, critical orange, informative blue) +- **Accent**: SAP gold (`#f58b00`) for highlights and branding +- **Typography**: Clean sans-serif matching SAP Fiori design language + +## State Architecture + +All state lives in a single `useDojoState()` call: + +```typescript +interface AppState { + currentView: ViewId; + selectedPOId: string | null; + searchQuery: string; + purchaseOrders: PurchaseOrder[]; + tasks: ApprovalTask[]; + vendors: Vendor[]; + kpis: KPIData; + auditLog: AuditEntry[]; + sidebarCollapsed: boolean; + poStatusFilter: POStatus | "All"; + poPriorityFilter: Priority | "All"; + taskStatusFilter: TaskStatus | "All"; +} +``` + +Every mutation (navigation, approval, rejection, comment, task toggle) writes to `dojo.state` and appends an audit entry — making the entire session fully traceable for RL verification via `window.dojo.state.auditLog`. + +## File Structure + +``` +sap/app/ +├── index.html +├── package.json +├── vite.config.ts +├── tsconfig.json / tsconfig.app.json / tsconfig.node.json +├── dist/index.html ← single-file build output +└── src/ + ├── main.tsx + ├── App.tsx + ├── index.css ← Tailwind v4 + SAP theme tokens + ├── types/index.ts ← Full TypeScript types + ├── data/seed.ts ← Rich seed data (12 POs, 10 vendors, 14 tasks) + ├── store/AppProvider.tsx ← useDojoState provider + all actions + └── components/ + ├── Sidebar.tsx + ├── TopBar.tsx + ├── Dashboard.tsx + ├── PurchaseOrderList.tsx + ├── PurchaseOrderDetail.tsx + ├── TaskInbox.tsx + └── AuditTrail.tsx +``` + +## Build Verification + +```bash +cd sap/app +pnpm install +pnpm run build # runs tsc -b && vite build +# ✅ 0 TypeScript errors +# ✅ dist/index.html produced (single file, 282 KB) +``` diff --git a/sap/app/.gitignore b/sap/app/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/sap/app/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/sap/app/eslint.config.js b/sap/app/eslint.config.js new file mode 100644 index 00000000..0bbf074e --- /dev/null +++ b/sap/app/eslint.config.js @@ -0,0 +1,28 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { ignores: ["dist"] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ["**/*.{ts,tsx}"], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "react-refresh/only-export-components": [ + "warn", + { allowConstantExport: true }, + ], + }, + } +); diff --git a/sap/app/index.html b/sap/app/index.html new file mode 100644 index 00000000..d294a9a8 --- /dev/null +++ b/sap/app/index.html @@ -0,0 +1,17 @@ + + + + + + + SAP S/4HANA — Enterprise Resource Planning + + +
+ + + diff --git a/sap/app/package.json b/sap/app/package.json new file mode 100644 index 00000000..5e670d66 --- /dev/null +++ b/sap/app/package.json @@ -0,0 +1,34 @@ +{ + "name": "sap-erp", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tailwindcss/vite": "^4.1.13", + "@chakra-dev/dojo-hooks": "^1.0.0", + "lucide-react": "^0.468.0", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "tailwindcss": "^4.1.13" + }, + "devDependencies": { + "@eslint/js": "^9.35.0", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.2", + "eslint": "^9.35.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.4.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.43.0", + "vite": "^7.1.6", + "vite-plugin-singlefile": "^2.3.0" + } +} diff --git a/sap/app/pnpm-lock.yaml b/sap/app/pnpm-lock.yaml new file mode 100644 index 00000000..0d2daeef --- /dev/null +++ b/sap/app/pnpm-lock.yaml @@ -0,0 +1,2415 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@chakra-dev/dojo-hooks': + specifier: ^1.0.0 + version: 1.0.2(react@19.2.4) + '@tailwindcss/vite': + specifier: ^4.1.13 + version: 4.1.18(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) + lucide-react: + specifier: ^0.468.0 + version: 0.468.0(react@19.2.4) + react: + specifier: ^19.1.1 + version: 19.2.4 + react-dom: + specifier: ^19.1.1 + version: 19.2.4(react@19.2.4) + tailwindcss: + specifier: ^4.1.13 + version: 4.1.18 + devDependencies: + '@eslint/js': + specifier: ^9.35.0 + version: 9.39.2 + '@types/react': + specifier: ^19.1.13 + version: 19.2.10 + '@types/react-dom': + specifier: ^19.1.9 + version: 19.2.3(@types/react@19.2.10) + '@vitejs/plugin-react': + specifier: ^5.0.2 + version: 5.1.2(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) + eslint: + specifier: ^9.35.0 + version: 9.39.2(jiti@2.6.1) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-react-refresh: + specifier: ^0.4.20 + version: 0.4.26(eslint@9.39.2(jiti@2.6.1)) + globals: + specifier: ^16.4.0 + version: 16.5.0 + typescript: + specifier: ~5.8.3 + version: 5.8.3 + typescript-eslint: + specifier: ^8.43.0 + version: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) + vite: + specifier: ^7.1.6 + version: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) + vite-plugin-singlefile: + specifier: ^2.3.0 + version: 2.3.0(rollup@4.57.1)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) + +packages: + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.0': + resolution: {integrity: sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + 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'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + 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/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@chakra-dev/dojo-hooks@1.0.2': + resolution: {integrity: sha512-gNdCPElK56pjoSXu+yeqtTGh0lej8zo58/nkz1WDUghEVEHXdyljJCPraqSjY641eqDPRFiiHMuKXJ6xuNlcWQ==} + peerDependencies: + react: '>=16.8.0' + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + 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} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@rolldown/pluginutils@1.0.0-beta.53': + resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} + + '@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] + + '@tailwindcss/node@4.1.18': + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + + '@tailwindcss/oxide-android-arm64@4.1.18': + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.18': + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.18': + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.18': + resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@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/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.10': + resolution: {integrity: sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==} + + '@typescript-eslint/eslint-plugin@8.54.0': + resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.54.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.54.0': + resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.54.0': + resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.54.0': + resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.54.0': + resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.54.0': + resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.54.0': + resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.54.0': + resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.54.0': + resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.54.0': + resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@5.1.2': + resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.9.19: + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} + hasBin: true + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001766: + resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + electron-to-chromium@1.5.283: + resolution: {integrity: sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==} + + enhanced-resolve@5.18.4: + resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + engines: {node: '>=10.13.0'} + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.26: + resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.468.0: + resolution: {integrity: sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.54.0: + resolution: {integrity: sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite-plugin-singlefile@2.3.0: + resolution: {integrity: sha512-DAcHzYypM0CasNLSz/WG0VdKOCxGHErfrjOoyIPiNxTPTGmO6rRD/te93n1YL/s+miXq66ipF1brMBikf99c6A==} + engines: {node: '>18.0.0'} + peerDependencies: + rollup: ^4.44.1 + vite: ^5.4.11 || ^6.0.0 || ^7.0.0 + + 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 + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.0': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + 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/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@chakra-dev/dojo-hooks@1.0.2(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.27.2': + 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) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.3': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.2': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@rolldown/pluginutils@1.0.0-beta.53': {} + + '@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 + + '@tailwindcss/node@4.1.18': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.4 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.18 + + '@tailwindcss/oxide-android-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide@4.1.18': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-x64': 4.1.18 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + + '@tailwindcss/vite@4.1.18(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2))': + dependencies: + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 + tailwindcss: 4.1.18 + vite: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) + + '@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/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/react-dom@19.2.3(@types/react@19.2.10)': + dependencies: + '@types/react': 19.2.10 + + '@types/react@19.2.10': + dependencies: + csstype: 3.2.3 + + '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.54.0 + eslint: 9.39.2(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.54.0 + debug: 4.4.3 + eslint: 9.39.2(jiti@2.6.1) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.54.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.8.3) + '@typescript-eslint/types': 8.54.0 + debug: 4.4.3 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.54.0': + dependencies: + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 + + '@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + + '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) + debug: 4.4.3 + eslint: 9.39.2(jiti@2.6.1) + ts-api-utils: 2.4.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.54.0': {} + + '@typescript-eslint/typescript-estree@8.54.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.54.0(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.8.3) + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.8.3) + eslint: 9.39.2(jiti@2.6.1) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.54.0': + dependencies: + '@typescript-eslint/types': 8.54.0 + eslint-visitor-keys: 4.2.1 + + '@vitejs/plugin-react@5.1.2(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2))': + 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-beta.53 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) + transitivePeerDependencies: + - supports-color + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.9.19: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001766 + electron-to-chromium: 1.5.283 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001766: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + detect-libc@2.1.2: {} + + electron-to-chromium@1.5.283: {} + + enhanced-resolve@5.18.4: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.2.0(eslint@9.39.2(jiti@2.6.1)): + dependencies: + eslint: 9.39.2(jiti@2.6.1) + + eslint-plugin-react-refresh@0.4.26(eslint@9.39.2(jiti@2.6.1)): + dependencies: + eslint: 9.39.2(jiti@2.6.1) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.2(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.3 + '@eslint/js': 9.39.2 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@16.5.0: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isexe@2.0.0: {} + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.468.0(react@19.2.4): + dependencies: + react: 19.2.4 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.27: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-refresh@0.18.0: {} + + react@19.2.4: {} + + resolve-from@4.0.0: {} + + 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 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.7.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + source-map-js@1.2.1: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tailwindcss@4.1.18: {} + + tapable@2.3.0: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@2.4.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) + eslint: 9.39.2(jiti@2.6.1) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + typescript@5.8.3: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite-plugin-singlefile@2.3.0(rollup@4.57.1)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)): + dependencies: + micromatch: 4.0.8 + rollup: 4.57.1 + vite: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) + + vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2): + dependencies: + esbuild: 0.27.2 + 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: + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/sap/app/src/App.tsx b/sap/app/src/App.tsx new file mode 100644 index 00000000..e07adcf0 --- /dev/null +++ b/sap/app/src/App.tsx @@ -0,0 +1,32 @@ +import Sidebar from "./components/Sidebar.tsx"; +import TopBar from "./components/TopBar.tsx"; +import Dashboard from "./components/Dashboard.tsx"; +import PurchaseOrderList from "./components/PurchaseOrderList.tsx"; +import PurchaseOrderDetail from "./components/PurchaseOrderDetail.tsx"; +import TaskInbox from "./components/TaskInbox.tsx"; +import AuditTrail from "./components/AuditTrail.tsx"; +import { useAppContext } from "./store/AppProvider.tsx"; + +export default function App() { + const { state } = useAppContext(); + + return ( +
+ + +
+
+ {state.currentView === "dashboard" && } + {state.currentView === "procurement" && } + {state.currentView === "procurement-detail" && } + {state.currentView === "tasks" && } + {state.currentView === "audit" && } +
+
+
+ ); +} diff --git a/sap/app/src/components/AuditTrail.tsx b/sap/app/src/components/AuditTrail.tsx new file mode 100644 index 00000000..09f484c6 --- /dev/null +++ b/sap/app/src/components/AuditTrail.tsx @@ -0,0 +1,126 @@ +import { + ScrollText, + ShoppingCart, + ClipboardCheck, + Navigation, + Monitor, + Clock, +} from "lucide-react"; +import { useAppContext } from "../store/AppProvider.tsx"; +import type { AuditEntry } from "../types/index.ts"; + +const catIcon: Record = { + procurement: ShoppingCart, + approval: ClipboardCheck, + navigation: Navigation, + system: Monitor, +}; + +const catColor: Record = { + procurement: "bg-sap-brand/10 text-sap-brand", + approval: "bg-sap-gold/10 text-sap-gold", + navigation: "bg-gray-100 text-gray-500", + system: "bg-sap-positive/10 text-sap-positive", +}; + +function matchesSearch(entry: AuditEntry, q: string): boolean { + if (!q) return true; + const lower = q.toLowerCase(); + return ( + entry.action.toLowerCase().includes(lower) || + entry.entity.toLowerCase().includes(lower) || + entry.user.toLowerCase().includes(lower) || + entry.details.toLowerCase().includes(lower) || + entry.entityId.toLowerCase().includes(lower) + ); +} + +function formatTimestamp(ts: string): string { + const d = new Date(ts); + return d.toLocaleString("en-GB", { day: "2-digit", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit" }); +} + +export default function AuditTrail() { + const { state } = useAppContext(); + + const entries = state.auditLog.filter((e) => matchesSearch(e, state.searchQuery)); + + /* Group by date */ + const grouped = new Map(); + for (const entry of entries) { + const dateKey = new Date(entry.timestamp).toLocaleDateString("en-GB", { weekday: "long", day: "numeric", month: "long", year: "numeric" }); + const arr = grouped.get(dateKey) ?? []; + arr.push(entry); + grouped.set(dateKey, arr); + } + + return ( +
+ {/* Header */} +
+ +

Audit Trail

+ {entries.length} +
+

+ All user actions are recorded for RL verification. This log is synced to dojo.state.auditLog. +

+ + {/* Category legend */} +
+ {(["procurement", "approval", "navigation", "system"] as const).map((cat) => { + const Icon = catIcon[cat]; + return ( + + {cat} + + ); + })} +
+ + {/* Timeline */} + {entries.length === 0 ? ( +
+ +

No audit entries match your search

+
+ ) : ( +
+ {Array.from(grouped.entries()).map(([dateKey, items]) => ( +
+

{dateKey}

+
+ {items.map((entry) => { + const Icon = catIcon[entry.category] ?? Monitor; + return ( +
+ {/* Dot */} +
+ +
+
+
+ {entry.action} + + {entry.category} + + {formatTimestamp(entry.timestamp)} +
+

{entry.details}

+
+ User: {entry.user} + Entity: {entry.entity} + {entry.entityId} +
+
+
+ ); + })} +
+
+ ))} +
+ )} +
+ ); +} diff --git a/sap/app/src/components/Dashboard.tsx b/sap/app/src/components/Dashboard.tsx new file mode 100644 index 00000000..ebcdf236 --- /dev/null +++ b/sap/app/src/components/Dashboard.tsx @@ -0,0 +1,173 @@ +import { + TrendingUp, + TrendingDown, + DollarSign, + ShoppingCart, + ClipboardCheck, + PieChart, + Truck, + Wallet, + ArrowRight, + AlertTriangle, +} from "lucide-react"; +import { useAppContext } from "../store/AppProvider.tsx"; +import type { PurchaseOrder, ApprovalTask } from "../types/index.ts"; + +/* ---------- KPI Card ---------- */ + +interface KPIProps { + title: string; + value: string; + change: number; + icon: typeof DollarSign; + accent: string; +} + +function KPICard({ title, value, change, icon: Icon, accent }: KPIProps) { + const positive = change >= 0; + return ( +
+
+
+

{title}

+

{value}

+
+
+ +
+
+
+ {positive ? : } + + {positive ? "+" : ""} + {change}% + + vs last period +
+
+ ); +} + +/* ---------- Mini table row ---------- */ + +function PORow({ po, onClick }: { po: PurchaseOrder; onClick: () => void }) { + const statusColor: Record = { + "Pending Approval": "bg-sap-critical/10 text-sap-critical", + Approved: "bg-sap-positive/10 text-sap-positive", + Rejected: "bg-sap-negative/10 text-sap-negative", + "In Delivery": "bg-sap-informative/10 text-sap-informative", + Draft: "bg-gray-100 text-gray-500", + Received: "bg-emerald-50 text-emerald-600", + Cancelled: "bg-gray-100 text-gray-400", + }; + + return ( + + ); +} + +function TaskRow({ task }: { task: ApprovalTask }) { + const priorityDot: Record = { High: "bg-sap-negative", Medium: "bg-sap-critical", Low: "bg-sap-positive" }; + return ( +
+ +
+

{task.title}

+

Due {task.dueDate}

+
+ {task.status} +
+ ); +} + +/* ---------- Dashboard ---------- */ + +export default function Dashboard() { + const { state, selectPO, navigate } = useAppContext(); + const { kpis } = state; + + const pendingPOs = state.purchaseOrders.filter((po) => po.status === "Pending Approval"); + const openTasks = state.tasks.filter((t) => t.status === "Open" || t.status === "In Progress").slice(0, 5); + + return ( +
+ {/* Welcome */} +
+
+

Good morning, Dr. Richter

+

Here’s your procurement overview for today.

+
+ + {new Date().toLocaleDateString("en-GB", { weekday: "long", year: "numeric", month: "long", day: "numeric" })} + +
+ + {/* KPIs */} +
+ + + + + + +
+ + {/* Lower panels */} +
+ {/* Pending POs */} +
+
+
+ +

POs Awaiting Approval

+ {pendingPOs.length} +
+ +
+
+ {pendingPOs.length === 0 ? ( +

No pending approvals 🎉

+ ) : ( + pendingPOs.map((po) => selectPO(po.id)} />) + )} +
+
+ + {/* Open tasks */} +
+
+
+ +

Upcoming Tasks

+ {openTasks.length} +
+ +
+
+ {openTasks.length === 0 ? ( +

All caught up!

+ ) : ( + openTasks.map((t) => ) + )} +
+
+
+
+ ); +} diff --git a/sap/app/src/components/PurchaseOrderDetail.tsx b/sap/app/src/components/PurchaseOrderDetail.tsx new file mode 100644 index 00000000..5f58d2f3 --- /dev/null +++ b/sap/app/src/components/PurchaseOrderDetail.tsx @@ -0,0 +1,271 @@ +import { useState } from "react"; +import { + ArrowLeft, + CheckCircle2, + XCircle, + MessageSquare, + Package, + Building2, + Calendar, + User, + MapPin, + Star, + Send, +} from "lucide-react"; +import { useAppContext } from "../store/AppProvider.tsx"; + +function currencySymbol(c: string) { + return c === "EUR" ? "€" : c === "GBP" ? "£" : "$"; +} + +const statusColor: Record = { + "Pending Approval": "bg-sap-critical/10 text-sap-critical border-sap-critical/20", + Approved: "bg-sap-positive/10 text-sap-positive border-sap-positive/20", + Rejected: "bg-sap-negative/10 text-sap-negative border-sap-negative/20", + "In Delivery": "bg-sap-informative/10 text-sap-informative border-sap-informative/20", + Draft: "bg-gray-100 text-gray-500 border-gray-200", + Received: "bg-emerald-50 text-emerald-600 border-emerald-200", + Cancelled: "bg-gray-100 text-gray-400 border-gray-200", +}; + +export default function PurchaseOrderDetail() { + const { state, navigate, approvePO, rejectPO, addPOComment } = useAppContext(); + const [commentText, setCommentText] = useState(""); + + const po = state.purchaseOrders.find((p) => p.id === state.selectedPOId); + if (!po) { + return ( +
+ +

Purchase order not found.

+ +
+ ); + } + + const canAction = po.status === "Pending Approval"; + const sym = currencySymbol(po.currency); + + function handleSubmitComment() { + if (!commentText.trim() || !po) return; + addPOComment(po.id, { + id: `c-${Date.now()}`, + author: "Dr. Claudia Richter", + text: commentText.trim(), + timestamp: new Date().toISOString(), + }); + setCommentText(""); + } + + return ( +
+ {/* Breadcrumb + actions */} +
+
+ +
+

{po.poNumber}

+

{po.title}

+
+ + {po.status} + +
+ {canAction && ( +
+ + +
+ )} +
+ + {/* Info grid */} +
+ {/* Left — Details */} +
+ {/* Header info */} +
+
+ + + + +
+
+ + + + +
+
+ + {/* Line items */} +
+
+

Line Items ({po.lineItems.length})

+
+
+ + + + + + + + + + + + + + {po.lineItems.map((li) => ( + + + + + + + + + + ))} + + + + + + + +
MaterialDescriptionQtyUnitUnit PriceTotalDelivery
{li.material}{li.description}{li.quantity}{li.unit}{sym}{li.unitPrice.toLocaleString()}{sym}{(li.quantity * li.unitPrice).toLocaleString()}{li.deliveryDate}
Grand Total{sym}{po.totalAmount.toLocaleString()} +
+
+
+ + {/* Comments */} +
+
+
+ +

Comments ({po.comments.length})

+
+
+
+ {po.comments.map((c) => ( +
+
+ {c.author} + + {new Date(c.timestamp).toLocaleString()} +
+

{c.text}

+
+ ))} +
+ {/* Add comment */} +
+
+ setCommentText(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleSubmitComment()} + placeholder="Add a comment…" + className="flex-1 rounded-md border border-sap-border bg-sap-bg px-3 py-2 text-sm outline-none focus:border-sap-brand focus:ring-1 focus:ring-sap-brand/30" + /> + +
+
+
+
+ + {/* Right — Vendor card */} +
+
+

Vendor Information

+
+
+

{po.vendor.name}

+

{po.vendor.code}

+
+
+ {po.vendor.country} +
+
+ {po.vendor.contact} +
+
+ + {po.vendor.rating} + / 5.0 +
+
+

Category

+

{po.vendor.category}

+
+
+
+ + {/* Quick stats */} +
+

Order Summary

+
+ + + + +
+ +
+
+
+
+
+
+ ); +} + +/* ---------- helpers ---------- */ + +function InfoRow({ icon: Icon, label, value }: { icon: typeof Building2; label: string; value: string }) { + return ( +
+ +
+

{label}

+

{value}

+
+
+ ); +} + +function SummaryRow({ label, value, bold }: { label: string; value: string; bold?: boolean }) { + return ( +
+ {label} + {value} +
+ ); +} diff --git a/sap/app/src/components/PurchaseOrderList.tsx b/sap/app/src/components/PurchaseOrderList.tsx new file mode 100644 index 00000000..5b088a2d --- /dev/null +++ b/sap/app/src/components/PurchaseOrderList.tsx @@ -0,0 +1,150 @@ +import { Filter, ShoppingCart, ArrowUpDown } from "lucide-react"; +import { useAppContext } from "../store/AppProvider.tsx"; +import type { POStatus, Priority, PurchaseOrder } from "../types/index.ts"; + +const statuses: (POStatus | "All")[] = ["All", "Draft", "Pending Approval", "Approved", "Rejected", "In Delivery", "Received", "Cancelled"]; +const priorities: (Priority | "All")[] = ["All", "High", "Medium", "Low"]; + +const statusColor: Record = { + "Pending Approval": "bg-sap-critical/10 text-sap-critical", + Approved: "bg-sap-positive/10 text-sap-positive", + Rejected: "bg-sap-negative/10 text-sap-negative", + "In Delivery": "bg-sap-informative/10 text-sap-informative", + Draft: "bg-gray-100 text-gray-500", + Received: "bg-emerald-50 text-emerald-600", + Cancelled: "bg-gray-100 text-gray-400", +}; + +const priorityBadge: Record = { + High: "bg-sap-negative/10 text-sap-negative", + Medium: "bg-sap-critical/10 text-sap-critical", + Low: "bg-sap-positive/10 text-sap-positive", +}; + +function currencySymbol(c: string) { + return c === "EUR" ? "€" : c === "GBP" ? "£" : "$"; +} + +function matchesSearch(po: PurchaseOrder, q: string): boolean { + if (!q) return true; + const lower = q.toLowerCase(); + return ( + po.poNumber.toLowerCase().includes(lower) || + po.title.toLowerCase().includes(lower) || + po.vendor.name.toLowerCase().includes(lower) || + po.department.toLowerCase().includes(lower) || + po.requestor.toLowerCase().includes(lower) + ); +} + +export default function PurchaseOrderList() { + const { state, selectPO, setPOStatusFilter, setPOPriorityFilter } = useAppContext(); + + const filtered = state.purchaseOrders.filter((po) => { + if (state.poStatusFilter !== "All" && po.status !== state.poStatusFilter) return false; + if (state.poPriorityFilter !== "All" && po.priority !== state.poPriorityFilter) return false; + return matchesSearch(po, state.searchQuery); + }); + + return ( +
+ {/* Header */} +
+
+ +

Purchase Orders

+ {filtered.length} +
+
+ + {/* Filters */} +
+ + + + {(state.poStatusFilter !== "All" || state.poPriorityFilter !== "All") && ( + + )} +
+ + {/* Table */} +
+
+ + + + + + + + + + + + + + + {filtered.length === 0 ? ( + + + + ) : ( + filtered.map((po) => ( + selectPO(po.id)} + className="cursor-pointer transition-colors hover:bg-sap-bg/50" + > + + + + + + + + + + )) + )} + +
PO Number TitleVendorStatusPriorityDepartmentAmountRequired
+ No purchase orders match your filters. +
{po.poNumber}{po.title}{po.vendor.name} + {po.status} + + {po.priority} + {po.department} + {currencySymbol(po.currency)}{po.totalAmount.toLocaleString()} + {po.requiredDate}
+
+
+
+ ); +} diff --git a/sap/app/src/components/Sidebar.tsx b/sap/app/src/components/Sidebar.tsx new file mode 100644 index 00000000..b030c0d6 --- /dev/null +++ b/sap/app/src/components/Sidebar.tsx @@ -0,0 +1,57 @@ +import { LayoutDashboard, ShoppingCart, ClipboardCheck, ScrollText, ChevronLeft, ChevronRight, Hexagon } from "lucide-react"; +import { useAppContext } from "../store/AppProvider.tsx"; +import type { ViewId } from "../types/index.ts"; + +const navItems: { id: ViewId; label: string; icon: typeof LayoutDashboard }[] = [ + { id: "dashboard", label: "Dashboard", icon: LayoutDashboard }, + { id: "procurement", label: "Procurement", icon: ShoppingCart }, + { id: "tasks", label: "My Tasks", icon: ClipboardCheck }, + { id: "audit", label: "Audit Trail", icon: ScrollText }, +]; + +export default function Sidebar() { + const { state, navigate, toggleSidebar } = useAppContext(); + const collapsed = state.sidebarCollapsed; + const currentView = state.currentView === "procurement-detail" ? "procurement" : state.currentView; + + return ( + + ); +} diff --git a/sap/app/src/components/TaskInbox.tsx b/sap/app/src/components/TaskInbox.tsx new file mode 100644 index 00000000..54f8d5db --- /dev/null +++ b/sap/app/src/components/TaskInbox.tsx @@ -0,0 +1,228 @@ +import { + ClipboardCheck, + Filter, + RotateCcw, + CheckCircle2, + Clock, + AlertCircle, + PauseCircle, + Inbox, + FileText, + Receipt, + Wallet, + Package, + Users, +} from "lucide-react"; +import { useAppContext } from "../store/AppProvider.tsx"; +import type { TaskStatus, ApprovalTask } from "../types/index.ts"; + +const statusFilters: (TaskStatus | "All")[] = ["All", "Open", "In Progress", "Completed", "Deferred"]; + +const statusIcon: Record = { + Open: AlertCircle, + "In Progress": Clock, + Completed: CheckCircle2, + Deferred: PauseCircle, +}; + +const statusStyle: Record = { + Open: "bg-sap-critical/10 text-sap-critical", + "In Progress": "bg-sap-informative/10 text-sap-informative", + Completed: "bg-sap-positive/10 text-sap-positive", + Deferred: "bg-gray-100 text-gray-500", +}; + +const priorityDot: Record = { + High: "bg-sap-negative", + Medium: "bg-sap-critical", + Low: "bg-sap-positive", +}; + +const typeIcon: Record = { + "Purchase Order": FileText, + Invoice: Receipt, + "Budget Transfer": Wallet, + "Material Request": Package, + "Vendor Onboarding": Users, +}; + +function currencySymbol(c: string) { + return c === "EUR" ? "€" : c === "GBP" ? "£" : "$"; +} + +function matchesSearch(t: ApprovalTask, q: string): boolean { + if (!q) return true; + const lower = q.toLowerCase(); + return ( + t.taskNumber.toLowerCase().includes(lower) || + t.title.toLowerCase().includes(lower) || + t.description.toLowerCase().includes(lower) || + t.assignee.toLowerCase().includes(lower) || + t.requester.toLowerCase().includes(lower) || + t.type.toLowerCase().includes(lower) + ); +} + +export default function TaskInbox() { + const { state, setTaskStatusFilter, toggleTaskStatus } = useAppContext(); + + const filtered = state.tasks.filter((t) => { + if (state.taskStatusFilter !== "All" && t.status !== state.taskStatusFilter) return false; + return matchesSearch(t, state.searchQuery); + }); + + /* Summary counts */ + const counts = { + open: state.tasks.filter((t) => t.status === "Open").length, + inProgress: state.tasks.filter((t) => t.status === "In Progress").length, + completed: state.tasks.filter((t) => t.status === "Completed").length, + deferred: state.tasks.filter((t) => t.status === "Deferred").length, + }; + + return ( +
+ {/* Header */} +
+ +

My Approval Tasks

+ {filtered.length} +
+ + {/* Summary chips */} +
+ + + + +
+ + {/* Filter bar */} +
+ + + {state.taskStatusFilter !== "All" && ( + + )} +
+ + {/* Task cards */} + {filtered.length === 0 ? ( +
+ +

No tasks match your criteria

+

Try adjusting your filters or search terms.

+
+ ) : ( +
+ {filtered.map((task) => ( + toggleTaskStatus(task.id)} /> + ))} +
+ )} +
+ ); +} + +/* ---------- Task card ---------- */ + +function TaskCard({ task, onToggle }: { task: ApprovalTask; onToggle: () => void }) { + const StatusIcon = statusIcon[task.status] ?? AlertCircle; + const TypeIcon = typeIcon[task.type] ?? FileText; + + const isDue = new Date(task.dueDate) <= new Date() && task.status !== "Completed"; + + return ( +
+ {/* Card header */} +
+
+ +
+
+
+ {task.taskNumber} + +
+

{task.title}

+
+ + {task.status} + +
+ + {/* Body */} +
+

{task.description}

+
+
+ Assignee +

{task.assignee}

+
+
+ Requester +

{task.requester}

+
+
+ Due +

{task.dueDate}

+
+ {task.amount > 0 && ( +
+ Amount +

{currencySymbol(task.currency)}{task.amount.toLocaleString()}

+
+ )} +
+
+ + {/* Progress + action */} +
+ {/* Progress bar */} +
+
+ Progress + {task.progress}% +
+
+
+
+
+ +
+
+ ); +} + +/* ---------- Summary chip ---------- */ + +function Chip({ label, count, color }: { label: string; count: number; color: string }) { + return ( + + {label} + {count} + + ); +} diff --git a/sap/app/src/components/TopBar.tsx b/sap/app/src/components/TopBar.tsx new file mode 100644 index 00000000..6ef025be --- /dev/null +++ b/sap/app/src/components/TopBar.tsx @@ -0,0 +1,45 @@ +import { Search, Bell, User, X } from "lucide-react"; +import { useAppContext } from "../store/AppProvider.tsx"; + +export default function TopBar() { + const { state, setSearchQuery } = useAppContext(); + + return ( +
+ {/* Search */} +
+ + setSearchQuery(e.target.value)} + placeholder="Search purchase orders, tasks, vendors…" + className="w-full rounded-md border border-sap-border bg-sap-bg py-2 pl-9 pr-9 text-sm outline-none transition-colors focus:border-sap-brand focus:ring-1 focus:ring-sap-brand/30" + /> + {state.searchQuery && ( + + )} +
+ + {/* Right section */} +
+ +
+
+ +
+ Dr. Richter +
+
+
+ ); +} diff --git a/sap/app/src/data/seed.ts b/sap/app/src/data/seed.ts new file mode 100644 index 00000000..27c38970 --- /dev/null +++ b/sap/app/src/data/seed.ts @@ -0,0 +1,236 @@ +import type { Vendor, PurchaseOrder, ApprovalTask, KPIData, AuditEntry } from "../types/index.ts"; + +export const seedVendors: Vendor[] = [ + { id: "v-001", name: "Siemens AG", code: "V-10001", country: "Germany", rating: 4.8, category: "Industrial Equipment", contact: "Hans Mueller", email: "h.mueller@siemens.de" }, + { id: "v-002", name: "Bosch GmbH", code: "V-10002", country: "Germany", rating: 4.6, category: "Auto Parts & Electronics", contact: "Klaus Weber", email: "k.weber@bosch.com" }, + { id: "v-003", name: "BASF SE", code: "V-10003", country: "Germany", rating: 4.5, category: "Chemicals & Materials", contact: "Anna Schmidt", email: "a.schmidt@basf.com" }, + { id: "v-004", name: "Caterpillar Inc.", code: "V-10004", country: "United States", rating: 4.3, category: "Heavy Machinery", contact: "John Carter", email: "j.carter@cat.com" }, + { id: "v-005", name: "3M Company", code: "V-10005", country: "United States", rating: 4.7, category: "Safety & Industrial", contact: "Sarah Chen", email: "s.chen@mmm.com" }, + { id: "v-006", name: "ABB Ltd", code: "V-10006", country: "Switzerland", rating: 4.4, category: "Robotics & Power", contact: "Erik Johansson", email: "e.johansson@abb.com" }, + { id: "v-007", name: "Schneider Electric", code: "V-10007", country: "France", rating: 4.2, category: "Energy Management", contact: "Marie Dupont", email: "m.dupont@se.com" }, + { id: "v-008", name: "Honeywell International", code: "V-10008", country: "United States", rating: 4.6, category: "Aerospace & Controls", contact: "Robert Kim", email: "r.kim@honeywell.com" }, + { id: "v-009", name: "Mitsubishi Electric", code: "V-10009", country: "Japan", rating: 4.5, category: "Factory Automation", contact: "Takashi Ito", email: "t.ito@mitsubishi.co.jp" }, + { id: "v-010", name: "Emerson Electric", code: "V-10010", country: "United States", rating: 4.1, category: "Process Automation", contact: "David Brown", email: "d.brown@emerson.com" }, +]; + +export const seedPurchaseOrders: PurchaseOrder[] = [ + { + id: "po-001", poNumber: "PO-4500012847", title: "Industrial PLC Controllers — Plant Modernization", + vendor: seedVendors[0], status: "Pending Approval", priority: "High", + createdDate: "2025-06-28", requiredDate: "2025-07-25", totalAmount: 284500, currency: "EUR", + department: "Manufacturing", requestor: "Thomas Bauer", approver: "Dr. Claudia Richter", + plant: "Plant 1100 — Stuttgart", companyCode: "CC-1000", + lineItems: [ + { id: "li-001", material: "MAT-70021", description: "SIMATIC S7-1500 CPU", quantity: 15, unit: "EA", unitPrice: 12500, currency: "EUR", deliveryDate: "2025-07-18" }, + { id: "li-002", material: "MAT-70022", description: "ET 200SP Distributed I/O", quantity: 30, unit: "EA", unitPrice: 2650, currency: "EUR", deliveryDate: "2025-07-18" }, + { id: "li-003", material: "MAT-70023", description: "Industrial Ethernet Switches", quantity: 10, unit: "EA", unitPrice: 4200, currency: "EUR", deliveryDate: "2025-07-20" }, + ], + comments: [ + { id: "c-001", author: "Thomas Bauer", text: "Urgent — required for Q3 plant upgrade. Old controllers reaching EOL.", timestamp: "2025-06-28T09:15:00Z" }, + { id: "c-002", author: "Dr. Claudia Richter", text: "Reviewing budget allocation. Will approve once capex is confirmed.", timestamp: "2025-06-29T14:30:00Z" }, + ], + }, + { + id: "po-002", poNumber: "PO-4500012848", title: "Auto Sensor Modules — Quality Line Upgrade", + vendor: seedVendors[1], status: "Approved", priority: "Medium", + createdDate: "2025-06-20", requiredDate: "2025-07-15", totalAmount: 67800, currency: "EUR", + department: "Quality Assurance", requestor: "Martin Fischer", approver: "Dr. Claudia Richter", + plant: "Plant 1200 — Munich", companyCode: "CC-1000", + lineItems: [ + { id: "li-004", material: "MAT-80100", description: "Ultrasonic Proximity Sensors", quantity: 200, unit: "EA", unitPrice: 189, currency: "EUR", deliveryDate: "2025-07-10" }, + { id: "li-005", material: "MAT-80101", description: "Inductive Sensors M18", quantity: 150, unit: "EA", unitPrice: 124, currency: "EUR", deliveryDate: "2025-07-10" }, + { id: "li-006", material: "MAT-80102", description: "Sensor Mounting Brackets", quantity: 350, unit: "EA", unitPrice: 18, currency: "EUR", deliveryDate: "2025-07-12" }, + ], + comments: [ + { id: "c-003", author: "Martin Fischer", text: "Approved by QA head. Sensors needed for new vision inspection line.", timestamp: "2025-06-22T11:00:00Z" }, + ], + }, + { + id: "po-003", poNumber: "PO-4500012849", title: "Chemical Raw Materials — Q3 Production Batch", + vendor: seedVendors[2], status: "In Delivery", priority: "High", + createdDate: "2025-06-10", requiredDate: "2025-07-05", totalAmount: 192000, currency: "EUR", + department: "Production", requestor: "Lisa Hartmann", approver: "Stefan Braun", + plant: "Plant 1300 — Hamburg", companyCode: "CC-2000", + lineItems: [ + { id: "li-007", material: "MAT-90010", description: "Polyethylene Granulate (HDPE)", quantity: 20, unit: "TO", unitPrice: 5200, currency: "EUR", deliveryDate: "2025-07-01" }, + { id: "li-008", material: "MAT-90011", description: "Polypropylene Copolymer", quantity: 15, unit: "TO", unitPrice: 4800, currency: "EUR", deliveryDate: "2025-07-03" }, + ], + comments: [ + { id: "c-004", author: "Lisa Hartmann", text: "Shipment tracking: DHL ref DE-8827364. Expected arrival July 2nd.", timestamp: "2025-06-30T08:20:00Z" }, + ], + }, + { + id: "po-004", poNumber: "PO-4500012850", title: "Excavator Parts — Maintenance Program", + vendor: seedVendors[3], status: "Pending Approval", priority: "Medium", + createdDate: "2025-07-01", requiredDate: "2025-07-30", totalAmount: 145000, currency: "USD", + department: "Maintenance", requestor: "Frank Mueller", approver: "Dr. Claudia Richter", + plant: "Plant 2100 — Chicago", companyCode: "CC-3000", + lineItems: [ + { id: "li-009", material: "MAT-50200", description: "CAT 320 Hydraulic Pump Assembly", quantity: 3, unit: "EA", unitPrice: 28500, currency: "USD", deliveryDate: "2025-07-22" }, + { id: "li-010", material: "MAT-50201", description: "Track Link Assembly", quantity: 6, unit: "EA", unitPrice: 8750, currency: "USD", deliveryDate: "2025-07-25" }, + ], + comments: [], + }, + { + id: "po-005", poNumber: "PO-4500012851", title: "Safety Equipment — Annual Compliance Order", + vendor: seedVendors[4], status: "Approved", priority: "High", + createdDate: "2025-06-15", requiredDate: "2025-07-10", totalAmount: 34200, currency: "USD", + department: "EHS", requestor: "Jennifer White", approver: "Stefan Braun", + plant: "Plant 2100 — Chicago", companyCode: "CC-3000", + lineItems: [ + { id: "li-011", material: "MAT-60010", description: "3M SafeGuard Respirators N95", quantity: 2000, unit: "EA", unitPrice: 4.50, currency: "USD", deliveryDate: "2025-07-05" }, + { id: "li-012", material: "MAT-60011", description: "Chemical Splash Goggles", quantity: 500, unit: "EA", unitPrice: 18, currency: "USD", deliveryDate: "2025-07-05" }, + { id: "li-013", material: "MAT-60012", description: "Anti-Vibration Gloves", quantity: 800, unit: "PR", unitPrice: 16.50, currency: "USD", deliveryDate: "2025-07-07" }, + ], + comments: [ + { id: "c-005", author: "Jennifer White", text: "Compliance audit in August — need full stock by end of July.", timestamp: "2025-06-16T10:45:00Z" }, + ], + }, + { + id: "po-006", poNumber: "PO-4500012852", title: "Robotic Welding Cell — New Assembly Line", + vendor: seedVendors[5], status: "Rejected", priority: "High", + createdDate: "2025-06-05", requiredDate: "2025-08-15", totalAmount: 520000, currency: "EUR", + department: "Engineering", requestor: "Michael Krause", approver: "Dr. Claudia Richter", + plant: "Plant 1100 — Stuttgart", companyCode: "CC-1000", + lineItems: [ + { id: "li-014", material: "MAT-40300", description: "IRB 6700 Robot Arm", quantity: 2, unit: "EA", unitPrice: 185000, currency: "EUR", deliveryDate: "2025-08-01" }, + { id: "li-015", material: "MAT-40301", description: "Welding Power Source TPS 500i", quantity: 2, unit: "EA", unitPrice: 45000, currency: "EUR", deliveryDate: "2025-08-01" }, + { id: "li-016", material: "MAT-40302", description: "Safety Fence System", quantity: 1, unit: "SET", unitPrice: 60000, currency: "EUR", deliveryDate: "2025-08-10" }, + ], + comments: [ + { id: "c-006", author: "Dr. Claudia Richter", text: "Budget exceeded for this quarter. Resubmit in Q4 with revised scope.", timestamp: "2025-06-12T16:00:00Z" }, + { id: "c-007", author: "Michael Krause", text: "Understood. Will revise and split across two budget periods.", timestamp: "2025-06-13T09:30:00Z" }, + ], + }, + { + id: "po-007", poNumber: "PO-4500012853", title: "Energy Management System Upgrade", + vendor: seedVendors[6], status: "Draft", priority: "Low", + createdDate: "2025-07-02", requiredDate: "2025-09-01", totalAmount: 78500, currency: "EUR", + department: "Facilities", requestor: "Nicole Werner", approver: "Stefan Braun", + plant: "Plant 1300 — Hamburg", companyCode: "CC-2000", + lineItems: [ + { id: "li-017", material: "MAT-30100", description: "EcoStruxure Building Mgmt Module", quantity: 5, unit: "EA", unitPrice: 12500, currency: "EUR", deliveryDate: "2025-08-20" }, + { id: "li-018", material: "MAT-30101", description: "Smart Power Meters", quantity: 25, unit: "EA", unitPrice: 640, currency: "EUR", deliveryDate: "2025-08-25" }, + ], + comments: [], + }, + { + id: "po-008", poNumber: "PO-4500012854", title: "Aerospace-Grade Actuators — R&D Prototype", + vendor: seedVendors[7], status: "Pending Approval", priority: "High", + createdDate: "2025-06-30", requiredDate: "2025-07-28", totalAmount: 198000, currency: "USD", + department: "R&D", requestor: "David Park", approver: "Dr. Claudia Richter", + plant: "Plant 2200 — San Jose", companyCode: "CC-3000", + lineItems: [ + { id: "li-019", material: "MAT-20500", description: "Honeywell Smart Position Actuator", quantity: 8, unit: "EA", unitPrice: 18500, currency: "USD", deliveryDate: "2025-07-20" }, + { id: "li-020", material: "MAT-20501", description: "Precision Control Valve Assembly", quantity: 12, unit: "EA", unitPrice: 4250, currency: "USD", deliveryDate: "2025-07-22" }, + ], + comments: [ + { id: "c-008", author: "David Park", text: "Critical for Phase 2 prototype. Customer demo scheduled Aug 15.", timestamp: "2025-06-30T15:00:00Z" }, + ], + }, + { + id: "po-009", poNumber: "PO-4500012855", title: "Factory Automation Controllers — Line 4 Expansion", + vendor: seedVendors[8], status: "Received", priority: "Medium", + createdDate: "2025-05-15", requiredDate: "2025-06-20", totalAmount: 112000, currency: "USD", + department: "Manufacturing", requestor: "Akira Tanaka", approver: "Stefan Braun", + plant: "Plant 3100 — Osaka", companyCode: "CC-4000", + lineItems: [ + { id: "li-021", material: "MAT-10700", description: "MELSEC iQ-R Series PLC", quantity: 4, unit: "EA", unitPrice: 19500, currency: "USD", deliveryDate: "2025-06-15" }, + { id: "li-022", material: "MAT-10701", description: "GOT2000 HMI Touch Panel", quantity: 8, unit: "EA", unitPrice: 4250, currency: "USD", deliveryDate: "2025-06-15" }, + ], + comments: [ + { id: "c-009", author: "Akira Tanaka", text: "All items received and installed. Line 4 commissioning next week.", timestamp: "2025-06-22T07:30:00Z" }, + ], + }, + { + id: "po-010", poNumber: "PO-4500012856", title: "Process Instrumentation — Valve Replacement Program", + vendor: seedVendors[9], status: "In Delivery", priority: "Medium", + createdDate: "2025-06-18", requiredDate: "2025-07-18", totalAmount: 89600, currency: "USD", + department: "Operations", requestor: "Carlos Rivera", approver: "Stefan Braun", + plant: "Plant 2300 — Houston", companyCode: "CC-3000", + lineItems: [ + { id: "li-023", material: "MAT-11200", description: "Fisher Control Valves 2\" SS", quantity: 20, unit: "EA", unitPrice: 3200, currency: "USD", deliveryDate: "2025-07-12" }, + { id: "li-024", material: "MAT-11201", description: "Rosemount 3051 Pressure Transmitter", quantity: 15, unit: "EA", unitPrice: 1280, currency: "USD", deliveryDate: "2025-07-14" }, + ], + comments: [ + { id: "c-010", author: "Carlos Rivera", text: "Partial shipment received (valves). Transmitters expected July 14.", timestamp: "2025-07-01T12:00:00Z" }, + ], + }, + { + id: "po-011", poNumber: "PO-4500012857", title: "Office IT Refresh — Laptops & Docking Stations", + vendor: seedVendors[0], status: "Cancelled", priority: "Low", + createdDate: "2025-05-20", requiredDate: "2025-06-30", totalAmount: 42000, currency: "EUR", + department: "IT", requestor: "Petra Klein", approver: "Stefan Braun", + plant: "Plant 1100 — Stuttgart", companyCode: "CC-1000", + lineItems: [ + { id: "li-025", material: "MAT-99001", description: "Siemens ENDURANCE Laptop", quantity: 20, unit: "EA", unitPrice: 1500, currency: "EUR", deliveryDate: "2025-06-20" }, + { id: "li-026", material: "MAT-99002", description: "USB-C Docking Station", quantity: 20, unit: "EA", unitPrice: 350, currency: "EUR", deliveryDate: "2025-06-20" }, + { id: "li-027", material: "MAT-99003", description: "27\" IPS Monitor", quantity: 10, unit: "EA", unitPrice: 500, currency: "EUR", deliveryDate: "2025-06-25" }, + ], + comments: [ + { id: "c-011", author: "Petra Klein", text: "Cancelled — switching to central IT procurement framework.", timestamp: "2025-06-01T08:00:00Z" }, + ], + }, + { + id: "po-012", poNumber: "PO-4500012858", title: "Warehouse Conveyor System — Distribution Center", + vendor: seedVendors[5], status: "Approved", priority: "High", + createdDate: "2025-06-25", requiredDate: "2025-08-30", totalAmount: 378000, currency: "EUR", + department: "Logistics", requestor: "Max Hoffmann", approver: "Dr. Claudia Richter", + plant: "Plant 1400 — Berlin", companyCode: "CC-2000", + lineItems: [ + { id: "li-028", material: "MAT-40400", description: "ABB FlexConveyor System", quantity: 1, unit: "SET", unitPrice: 295000, currency: "EUR", deliveryDate: "2025-08-15" }, + { id: "li-029", material: "MAT-40401", description: "Barcode Scanner Integration Kit", quantity: 6, unit: "EA", unitPrice: 8500, currency: "EUR", deliveryDate: "2025-08-20" }, + { id: "li-030", material: "MAT-40402", description: "Conveyor Belt Safety Guards", quantity: 12, unit: "EA", unitPrice: 2250, currency: "EUR", deliveryDate: "2025-08-25" }, + ], + comments: [ + { id: "c-012", author: "Max Hoffmann", text: "Contract signed. Installation team scheduled for August.", timestamp: "2025-06-28T11:30:00Z" }, + ], + }, +]; + +export const seedTasks: ApprovalTask[] = [ + { id: "t-001", taskNumber: "TASK-001", title: "Approve PO — PLC Controllers", description: "Review and approve purchase order PO-4500012847 for industrial PLC controllers from Siemens AG.", type: "Purchase Order", status: "Open", priority: "High", assignee: "Dr. Claudia Richter", requester: "Thomas Bauer", createdDate: "2025-06-28", dueDate: "2025-07-03", relatedDocId: "po-001", amount: 284500, currency: "EUR", progress: 0 }, + { id: "t-002", taskNumber: "TASK-002", title: "Approve PO — Excavator Parts", description: "Review maintenance program purchase order PO-4500012850 for Caterpillar excavator parts.", type: "Purchase Order", status: "Open", priority: "Medium", assignee: "Dr. Claudia Richter", requester: "Frank Mueller", createdDate: "2025-07-01", dueDate: "2025-07-05", relatedDocId: "po-004", amount: 145000, currency: "USD", progress: 0 }, + { id: "t-003", taskNumber: "TASK-003", title: "Approve PO — Aerospace Actuators", description: "Review R&D prototype purchase order PO-4500012854 for Honeywell aerospace-grade actuators.", type: "Purchase Order", status: "In Progress", priority: "High", assignee: "Dr. Claudia Richter", requester: "David Park", createdDate: "2025-06-30", dueDate: "2025-07-04", relatedDocId: "po-008", amount: 198000, currency: "USD", progress: 40 }, + { id: "t-004", taskNumber: "TASK-004", title: "Verify Invoice — Bosch Sensors", description: "Three-way match verification for invoice INV-2025-08845 against PO-4500012848 and goods receipt.", type: "Invoice", status: "Open", priority: "Medium", assignee: "Stefan Braun", requester: "Accounts Payable", createdDate: "2025-07-02", dueDate: "2025-07-08", relatedDocId: "po-002", amount: 67800, currency: "EUR", progress: 0 }, + { id: "t-005", taskNumber: "TASK-005", title: "Budget Transfer — Engineering Q4", description: "Approve budget reallocation of €200,000 from Facilities to Engineering for robotic welding cell.", type: "Budget Transfer", status: "Open", priority: "High", assignee: "CFO Office", requester: "Michael Krause", createdDate: "2025-06-15", dueDate: "2025-07-10", relatedDocId: "po-006", amount: 200000, currency: "EUR", progress: 0 }, + { id: "t-006", taskNumber: "TASK-006", title: "Material Request — Lab Consumables", description: "Approve material request MR-77421 for laboratory consumables and testing reagents.", type: "Material Request", status: "Completed", priority: "Low", assignee: "Dr. Claudia Richter", requester: "Lab Manager", createdDate: "2025-06-20", dueDate: "2025-06-27", relatedDocId: "mr-001", amount: 8500, currency: "EUR", progress: 100 }, + { id: "t-007", taskNumber: "TASK-007", title: "Vendor Onboarding — TechFlow Solutions", description: "Complete vendor qualification assessment and onboarding for new IT services provider TechFlow Solutions.", type: "Vendor Onboarding", status: "In Progress", priority: "Medium", assignee: "Procurement Team", requester: "IT Department", createdDate: "2025-06-25", dueDate: "2025-07-15", relatedDocId: "vq-001", amount: 0, currency: "EUR", progress: 65 }, + { id: "t-008", taskNumber: "TASK-008", title: "Approve PO — Conveyor System", description: "Final approval for warehouse conveyor system PO-4500012858 from ABB Ltd.", type: "Purchase Order", status: "Completed", priority: "High", assignee: "Dr. Claudia Richter", requester: "Max Hoffmann", createdDate: "2025-06-25", dueDate: "2025-06-30", relatedDocId: "po-012", amount: 378000, currency: "EUR", progress: 100 }, + { id: "t-009", taskNumber: "TASK-009", title: "Invoice Approval — BASF Materials", description: "Approve payment for chemical raw materials invoice INV-2025-09102.", type: "Invoice", status: "In Progress", priority: "Medium", assignee: "Stefan Braun", requester: "Accounts Payable", createdDate: "2025-07-01", dueDate: "2025-07-07", relatedDocId: "po-003", amount: 192000, currency: "EUR", progress: 30 }, + { id: "t-010", taskNumber: "TASK-010", title: "Material Request — Packaging Supplies", description: "Approve bulk packaging material request MR-77435 for distribution center.", type: "Material Request", status: "Open", priority: "Low", assignee: "Stefan Braun", requester: "Warehouse Ops", createdDate: "2025-07-02", dueDate: "2025-07-12", relatedDocId: "mr-002", amount: 15200, currency: "EUR", progress: 0 }, + { id: "t-011", taskNumber: "TASK-011", title: "Vendor Onboarding — GreenPack EU", description: "Evaluate and onboard sustainable packaging supplier GreenPack EU for ESG compliance.", type: "Vendor Onboarding", status: "Open", priority: "Medium", assignee: "Procurement Team", requester: "Sustainability Office", createdDate: "2025-07-01", dueDate: "2025-07-20", relatedDocId: "vq-002", amount: 0, currency: "EUR", progress: 0 }, + { id: "t-012", taskNumber: "TASK-012", title: "Budget Transfer — Safety Equipment", description: "Approve supplemental budget allocation for additional safety equipment across US plants.", type: "Budget Transfer", status: "Deferred", priority: "Low", assignee: "CFO Office", requester: "Jennifer White", createdDate: "2025-06-18", dueDate: "2025-07-30", relatedDocId: "po-005", amount: 25000, currency: "USD", progress: 10 }, + { id: "t-013", taskNumber: "TASK-013", title: "Approve PO — Energy Management", description: "Review draft PO for Schneider Electric energy management system upgrade.", type: "Purchase Order", status: "Open", priority: "Low", assignee: "Stefan Braun", requester: "Nicole Werner", createdDate: "2025-07-02", dueDate: "2025-07-15", relatedDocId: "po-007", amount: 78500, currency: "EUR", progress: 0 }, + { id: "t-014", taskNumber: "TASK-014", title: "Invoice Dispute — 3M Short Shipment", description: "Resolve quantity discrepancy on 3M safety equipment delivery (received 1800 vs ordered 2000 respirators).", type: "Invoice", status: "In Progress", priority: "High", assignee: "Procurement Team", requester: "Goods Receipt", createdDate: "2025-07-03", dueDate: "2025-07-08", relatedDocId: "po-005", amount: 900, currency: "USD", progress: 50 }, +]; + +export const seedKPIs: KPIData = { + totalRevenue: 48750000, + revenueChange: 12.4, + openPOs: 7, + openPOsChange: -8.3, + pendingApprovals: 8, + approvalsChange: 15.0, + budgetUtilization: 73.2, + budgetChange: 4.6, + totalSpend: 2156000, + spendChange: 6.8, + onTimeDelivery: 94.5, + deliveryChange: 2.1, +}; + +export const seedAuditLog: AuditEntry[] = [ + { id: "a-001", timestamp: "2025-07-03T08:00:00Z", action: "Session Started", entity: "System", entityId: "session-001", user: "Dr. Claudia Richter", details: "User logged into SAP S/4HANA procurement module.", category: "system" }, + { id: "a-002", timestamp: "2025-07-03T08:02:00Z", action: "Viewed Dashboard", entity: "Dashboard", entityId: "dashboard", user: "Dr. Claudia Richter", details: "Reviewed KPI summary and pending items.", category: "navigation" }, + { id: "a-003", timestamp: "2025-07-02T16:45:00Z", action: "PO Approved", entity: "Purchase Order", entityId: "po-012", user: "Dr. Claudia Richter", details: "Approved PO-4500012858 (Warehouse Conveyor System) — €378,000.", category: "procurement" }, + { id: "a-004", timestamp: "2025-07-02T14:20:00Z", action: "Comment Added", entity: "Purchase Order", entityId: "po-001", user: "Dr. Claudia Richter", details: "Added review comment on PO-4500012847 pending capex confirmation.", category: "procurement" }, + { id: "a-005", timestamp: "2025-07-02T11:30:00Z", action: "Task Started", entity: "Approval Task", entityId: "t-003", user: "Dr. Claudia Richter", details: "Started review of aerospace actuators PO (TASK-003).", category: "approval" }, + { id: "a-006", timestamp: "2025-07-01T17:00:00Z", action: "PO Created", entity: "Purchase Order", entityId: "po-004", user: "Frank Mueller", details: "Created PO-4500012850 for excavator maintenance parts — $145,000.", category: "procurement" }, + { id: "a-007", timestamp: "2025-07-01T15:30:00Z", action: "PO Submitted", entity: "Purchase Order", entityId: "po-008", user: "David Park", details: "Submitted PO-4500012854 for aerospace-grade actuators — $198,000.", category: "procurement" }, + { id: "a-008", timestamp: "2025-07-01T10:00:00Z", action: "Task Completed", entity: "Approval Task", entityId: "t-006", user: "Dr. Claudia Richter", details: "Approved material request MR-77421 for lab consumables — €8,500.", category: "approval" }, + { id: "a-009", timestamp: "2025-06-30T14:00:00Z", action: "Goods Received", entity: "Purchase Order", entityId: "po-009", user: "Akira Tanaka", details: "Marked PO-4500012855 as fully received. All items in good condition.", category: "procurement" }, + { id: "a-010", timestamp: "2025-06-28T09:15:00Z", action: "PO Created", entity: "Purchase Order", entityId: "po-001", user: "Thomas Bauer", details: "Created PO-4500012847 for Siemens PLC controllers — €284,500.", category: "procurement" }, + { id: "a-011", timestamp: "2025-06-25T11:30:00Z", action: "PO Approved", entity: "Purchase Order", entityId: "po-005", user: "Stefan Braun", details: "Approved PO-4500012851 for annual safety compliance — $34,200.", category: "procurement" }, + { id: "a-012", timestamp: "2025-06-12T16:00:00Z", action: "PO Rejected", entity: "Purchase Order", entityId: "po-006", user: "Dr. Claudia Richter", details: "Rejected PO-4500012852 — budget exceeded for current quarter.", category: "procurement" }, +]; diff --git a/sap/app/src/index.css b/sap/app/src/index.css new file mode 100644 index 00000000..3a73638c --- /dev/null +++ b/sap/app/src/index.css @@ -0,0 +1,54 @@ +@import "tailwindcss"; + +@theme inline { + /* SAP Fiori palette */ + --color-sap-shell: #354a5f; + --color-sap-shell-hover: #2c3e50; + --color-sap-brand: #0a6ed1; + --color-sap-highlight: #0854a0; + --color-sap-positive: #107e3e; + --color-sap-negative: #bb0000; + --color-sap-critical: #e9730c; + --color-sap-gold: #f58b00; + --color-sap-informative: #0a6ed1; + + --color-sap-bg: #f7f7f7; + --color-sap-bg-card: #ffffff; + --color-sap-border: #e4e4e4; + --color-sap-text: #32363a; + --color-sap-text-secondary: #6a6d70; + --color-sap-text-light: #ffffff; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: "72", "72full", Arial, Helvetica, sans-serif; + background-color: var(--color-sap-bg); + color: var(--color-sap-text); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Custom scrollbar for SAP feel */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: #c4c4c4; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #999; +} diff --git a/sap/app/src/main.tsx b/sap/app/src/main.tsx new file mode 100644 index 00000000..de474b51 --- /dev/null +++ b/sap/app/src/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import "./index.css"; +import App from "./App.tsx"; +import { AppProvider } from "./store/AppProvider.tsx"; + +createRoot(document.getElementById("root")!).render( + + + + + , +); diff --git a/sap/app/src/store/AppProvider.tsx b/sap/app/src/store/AppProvider.tsx new file mode 100644 index 00000000..c8735464 --- /dev/null +++ b/sap/app/src/store/AppProvider.tsx @@ -0,0 +1,222 @@ +/* eslint-disable react-refresh/only-export-components */ +import { createContext, useContext, useCallback, useMemo, type ReactNode } from "react"; +import { useDojoState } from "@chakra-dev/dojo-hooks"; +import type { AppState, ViewId, POStatus, Priority, TaskStatus, AuditEntry, POComment } from "../types/index.ts"; +import { seedPurchaseOrders, seedTasks, seedVendors, seedKPIs, seedAuditLog } from "../data/seed.ts"; + +/* ------------------------------------------------------------------ */ +/* Initial state */ +/* ------------------------------------------------------------------ */ + +const initialState: AppState = { + currentView: "dashboard", + selectedPOId: null, + searchQuery: "", + purchaseOrders: seedPurchaseOrders, + tasks: seedTasks, + vendors: seedVendors, + kpis: seedKPIs, + auditLog: seedAuditLog, + sidebarCollapsed: false, + poStatusFilter: "All", + poPriorityFilter: "All", + taskStatusFilter: "All", +}; + +/* ------------------------------------------------------------------ */ +/* Context type */ +/* ------------------------------------------------------------------ */ + +interface AppContextType { + state: AppState; + navigate: (view: ViewId) => void; + selectPO: (id: string) => void; + setSearchQuery: (q: string) => void; + toggleSidebar: () => void; + setPOStatusFilter: (f: POStatus | "All") => void; + setPOPriorityFilter: (f: Priority | "All") => void; + setTaskStatusFilter: (f: TaskStatus | "All") => void; + approvePO: (id: string) => void; + rejectPO: (id: string) => void; + addPOComment: (poId: string, comment: POComment) => void; + toggleTaskStatus: (id: string) => void; + addAuditEntry: (entry: Omit) => void; +} + +const AppContext = createContext(undefined); + +/* ------------------------------------------------------------------ */ +/* Provider */ +/* ------------------------------------------------------------------ */ + +export function AppProvider({ children }: { children: ReactNode }) { + const [state, setState] = useDojoState(initialState); + + /* ---------- helpers ---------- */ + + const addAuditEntry = useCallback( + (entry: Omit) => { + setState((prev) => ({ + ...prev, + auditLog: [ + { ...entry, id: `a-${Date.now()}`, timestamp: new Date().toISOString() }, + ...prev.auditLog, + ], + })); + }, + [setState], + ); + + /* ---------- navigation ---------- */ + + const navigate = useCallback( + (view: ViewId) => { + setState((prev) => { + const next: AppState = { ...prev, currentView: view }; + if (view !== "procurement-detail") next.selectedPOId = null; + return next; + }); + addAuditEntry({ action: "Navigated", entity: "View", entityId: view, user: "Dr. Claudia Richter", details: `Navigated to ${view} view.`, category: "navigation" }); + }, + [setState, addAuditEntry], + ); + + const selectPO = useCallback( + (id: string) => { + setState((prev) => ({ ...prev, currentView: "procurement-detail" as ViewId, selectedPOId: id })); + const po = state.purchaseOrders.find((p) => p.id === id); + addAuditEntry({ action: "Viewed PO", entity: "Purchase Order", entityId: id, user: "Dr. Claudia Richter", details: `Opened ${po?.poNumber ?? id} detail view.`, category: "procurement" }); + }, + [setState, state.purchaseOrders, addAuditEntry], + ); + + /* ---------- search ---------- */ + + const setSearchQuery = useCallback( + (q: string) => setState((prev) => ({ ...prev, searchQuery: q })), + [setState], + ); + + /* ---------- sidebar ---------- */ + + const toggleSidebar = useCallback( + () => setState((prev) => ({ ...prev, sidebarCollapsed: !prev.sidebarCollapsed })), + [setState], + ); + + /* ---------- filters ---------- */ + + const setPOStatusFilter = useCallback( + (f: POStatus | "All") => setState((prev) => ({ ...prev, poStatusFilter: f })), + [setState], + ); + + const setPOPriorityFilter = useCallback( + (f: Priority | "All") => setState((prev) => ({ ...prev, poPriorityFilter: f })), + [setState], + ); + + const setTaskStatusFilter = useCallback( + (f: TaskStatus | "All") => setState((prev) => ({ ...prev, taskStatusFilter: f })), + [setState], + ); + + /* ---------- PO actions ---------- */ + + const approvePO = useCallback( + (id: string) => { + setState((prev) => ({ + ...prev, + purchaseOrders: prev.purchaseOrders.map((po) => + po.id === id ? { ...po, status: "Approved" as const } : po, + ), + tasks: prev.tasks.map((t) => + t.relatedDocId === id && t.type === "Purchase Order" ? { ...t, status: "Completed" as const, progress: 100 } : t, + ), + })); + const po = state.purchaseOrders.find((p) => p.id === id); + addAuditEntry({ action: "PO Approved", entity: "Purchase Order", entityId: id, user: "Dr. Claudia Richter", details: `Approved ${po?.poNumber ?? id} — ${po?.currency ?? ""} ${po?.totalAmount?.toLocaleString() ?? ""}.`, category: "procurement" }); + }, + [setState, state.purchaseOrders, addAuditEntry], + ); + + const rejectPO = useCallback( + (id: string) => { + setState((prev) => ({ + ...prev, + purchaseOrders: prev.purchaseOrders.map((po) => + po.id === id ? { ...po, status: "Rejected" as const } : po, + ), + tasks: prev.tasks.map((t) => + t.relatedDocId === id && t.type === "Purchase Order" ? { ...t, status: "Completed" as const, progress: 100 } : t, + ), + })); + const po = state.purchaseOrders.find((p) => p.id === id); + addAuditEntry({ action: "PO Rejected", entity: "Purchase Order", entityId: id, user: "Dr. Claudia Richter", details: `Rejected ${po?.poNumber ?? id}.`, category: "procurement" }); + }, + [setState, state.purchaseOrders, addAuditEntry], + ); + + const addPOComment = useCallback( + (poId: string, comment: POComment) => { + setState((prev) => ({ + ...prev, + purchaseOrders: prev.purchaseOrders.map((po) => + po.id === poId ? { ...po, comments: [...po.comments, comment] } : po, + ), + })); + addAuditEntry({ action: "Comment Added", entity: "Purchase Order", entityId: poId, user: comment.author, details: `Added comment on ${poId}: "${comment.text.slice(0, 60)}…"`, category: "procurement" }); + }, + [setState, addAuditEntry], + ); + + /* ---------- task actions ---------- */ + + const toggleTaskStatus = useCallback( + (id: string) => { + setState((prev) => ({ + ...prev, + tasks: prev.tasks.map((t) => { + if (t.id !== id) return t; + const order: Array<"Open" | "In Progress" | "Completed" | "Deferred"> = ["Open", "In Progress", "Completed", "Deferred"]; + const idx = order.indexOf(t.status as "Open" | "In Progress" | "Completed" | "Deferred"); + const next = order[(idx + 1) % order.length]; + const progress = next === "Completed" ? 100 : next === "In Progress" ? 50 : next === "Deferred" ? t.progress : 0; + return { ...t, status: next, progress }; + }), + })); + const task = state.tasks.find((t) => t.id === id); + addAuditEntry({ action: "Task Status Changed", entity: "Approval Task", entityId: id, user: "Dr. Claudia Richter", details: `Toggled ${task?.taskNumber ?? id} status.`, category: "approval" }); + }, + [setState, state.tasks, addAuditEntry], + ); + + /* ---------- memoised context value ---------- */ + + const value = useMemo( + () => ({ + state, + navigate, + selectPO, + setSearchQuery, + toggleSidebar, + setPOStatusFilter, + setPOPriorityFilter, + setTaskStatusFilter, + approvePO, + rejectPO, + addPOComment, + toggleTaskStatus, + addAuditEntry, + }), + [state, navigate, selectPO, setSearchQuery, toggleSidebar, setPOStatusFilter, setPOPriorityFilter, setTaskStatusFilter, approvePO, rejectPO, addPOComment, toggleTaskStatus, addAuditEntry], + ); + + return {children}; +} + +export function useAppContext() { + const ctx = useContext(AppContext); + if (!ctx) throw new Error("useAppContext must be used within AppProvider"); + return ctx; +} diff --git a/sap/app/src/types/index.ts b/sap/app/src/types/index.ts new file mode 100644 index 00000000..b3abcd63 --- /dev/null +++ b/sap/app/src/types/index.ts @@ -0,0 +1,117 @@ +export type ViewId = "dashboard" | "procurement" | "procurement-detail" | "tasks" | "audit"; + +export type POStatus = "Draft" | "Pending Approval" | "Approved" | "Rejected" | "In Delivery" | "Received" | "Cancelled"; + +export type TaskStatus = "Open" | "In Progress" | "Completed" | "Deferred"; + +export type Priority = "High" | "Medium" | "Low"; + +export type Currency = "USD" | "EUR" | "GBP"; + +export interface Vendor { + id: string; + name: string; + code: string; + country: string; + rating: number; + category: string; + contact: string; + email: string; +} + +export interface POLineItem { + id: string; + material: string; + description: string; + quantity: number; + unit: string; + unitPrice: number; + currency: Currency; + deliveryDate: string; +} + +export interface POComment { + id: string; + author: string; + text: string; + timestamp: string; +} + +export interface PurchaseOrder { + id: string; + poNumber: string; + title: string; + vendor: Vendor; + status: POStatus; + priority: Priority; + createdDate: string; + requiredDate: string; + totalAmount: number; + currency: Currency; + department: string; + requestor: string; + approver: string; + lineItems: POLineItem[]; + comments: POComment[]; + plant: string; + companyCode: string; +} + +export interface ApprovalTask { + id: string; + taskNumber: string; + title: string; + description: string; + type: "Purchase Order" | "Invoice" | "Budget Transfer" | "Material Request" | "Vendor Onboarding"; + status: TaskStatus; + priority: Priority; + assignee: string; + requester: string; + createdDate: string; + dueDate: string; + relatedDocId: string; + amount: number; + currency: Currency; + progress: number; +} + +export interface KPIData { + totalRevenue: number; + revenueChange: number; + openPOs: number; + openPOsChange: number; + pendingApprovals: number; + approvalsChange: number; + budgetUtilization: number; + budgetChange: number; + totalSpend: number; + spendChange: number; + onTimeDelivery: number; + deliveryChange: number; +} + +export interface AuditEntry { + id: string; + timestamp: string; + action: string; + entity: string; + entityId: string; + user: string; + details: string; + category: "procurement" | "approval" | "navigation" | "system"; +} + +export interface AppState { + currentView: ViewId; + selectedPOId: string | null; + searchQuery: string; + purchaseOrders: PurchaseOrder[]; + tasks: ApprovalTask[]; + vendors: Vendor[]; + kpis: KPIData; + auditLog: AuditEntry[]; + sidebarCollapsed: boolean; + poStatusFilter: POStatus | "All"; + poPriorityFilter: Priority | "All"; + taskStatusFilter: TaskStatus | "All"; +} diff --git a/sap/app/src/vite-env.d.ts b/sap/app/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/sap/app/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/sap/app/tsconfig.app.json b/sap/app/tsconfig.app.json new file mode 100644 index 00000000..227a6c67 --- /dev/null +++ b/sap/app/tsconfig.app.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/sap/app/tsconfig.json b/sap/app/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/sap/app/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/sap/app/tsconfig.node.json b/sap/app/tsconfig.node.json new file mode 100644 index 00000000..f85a3990 --- /dev/null +++ b/sap/app/tsconfig.node.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/sap/app/vite.config.ts b/sap/app/vite.config.ts new file mode 100644 index 00000000..dd23d815 --- /dev/null +++ b/sap/app/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; +import { viteSingleFile } from "vite-plugin-singlefile"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [tailwindcss(), react(), viteSingleFile()], +});