Merge pull request #3 from refactor/use-cases-architecture

refactor: split actions into use cases and repositories
This commit was merged in pull request #3.
This commit is contained in:
2026-06-04 20:31:43 +00:00
137 changed files with 4112 additions and 23069 deletions
+8 -1
View File
@@ -13,4 +13,11 @@ NODE_ENV=production
DEMO_MODE=false
DOMAIN=localhost
AUTH_TRUST_HOST="http://localhost"
AUTH_SECRET=your_secret_key_here
AUTH_SECRET=your_secret_key_here
# ADMIN BOOTSTRAP
ADMIN_BOOTSTRAP_ENABLED=true
ADMIN_USERNAME=admin
ADMIN_EMAIL=admin@localhost
ADMIN_NAME=Administrator
ADMIN_PASSWORD=change-me
+6 -2
View File
@@ -18,7 +18,7 @@
/out/
# prisma
generated/
src/generated
# production
/build
@@ -44,4 +44,8 @@ yarn-error.log*
next-env.d.ts
# vscode
!.vscode
!.vscode
# Local Pi runtime state
.atl/
.pi
+13 -10
View File
@@ -1,12 +1,15 @@
{
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit",
"source.fixAll.biome": "explicit"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
}
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit",
"source.fixAll.biome": "explicit"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[jsonc]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
}
+1 -1
View File
@@ -39,4 +39,4 @@ COPY --from=builder /app/.next/static ./.next/static
EXPOSE ${PORT}
CMD ["bun", "run", "start"]
CMD ["sh", "-c", "bun run db:deploy && bun run db:seed && bun run start"]
+64 -64
View File
@@ -1,66 +1,66 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "main"
},
"files": {
"includes": [
"**",
"!**/ node_modules",
"!**/ .next",
"!src/generated/prisma",
"!src/components/ui",
"!src/styles"
],
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 80,
"attributePosition": "auto",
"bracketSameLine": false,
"bracketSpacing": true,
"expand": "auto",
"useEditorconfig": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "asNeeded",
"arrowParentheses": "always",
"bracketSameLine": false,
"quoteStyle": "double",
"attributePosition": "auto",
"bracketSpacing": true
}
},
"html": {
"formatter": {
"indentScriptAndStyle": false,
"selfCloseVoidElements": "always"
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "main"
},
"files": {
"includes": [
"**",
"!**/ node_modules",
"!**/ .next",
"!src/generated/prisma",
"!src/components/ui",
"!src/styles"
],
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 80,
"attributePosition": "auto",
"bracketSameLine": false,
"bracketSpacing": true,
"expand": "auto",
"useEditorconfig": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "asNeeded",
"arrowParentheses": "always",
"bracketSameLine": false,
"quoteStyle": "double",
"attributePosition": "auto",
"bracketSpacing": true
}
},
"html": {
"formatter": {
"indentScriptAndStyle": false,
"selfCloseVoidElements": "always"
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}
+390 -13
View File
@@ -7,7 +7,8 @@
"dependencies": {
"@base-ui/react": "^1.4.1",
"@hookform/resolvers": "^5.2.2",
"@prisma/client": "^6.10.1",
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-dialog": "^1.1.14",
@@ -19,11 +20,13 @@
"bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^1.11.0",
"dotenv": "^17.4.2",
"lucide-react": "^1.17.0",
"next": "^16.2.4",
"next-auth": "^5.0.0-beta.28",
"next-themes": "^0.4.6",
"papaparse": "^5.5.3",
"radix-ui": "^1.4.3",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-hook-form": "^7.74.0",
@@ -38,7 +41,7 @@
"@types/node": "^24.0.3",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"prisma": "^6.10.1",
"prisma": "^7.8.0",
"tailwindcss": "^4.1.10",
"tw-animate-css": "^1.3.4",
"typescript": "^5.8.3",
@@ -85,6 +88,12 @@
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
"@electric-sql/pglite": ["@electric-sql/pglite@0.4.1", "", {}, "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q=="],
"@electric-sql/pglite-socket": ["@electric-sql/pglite-socket@0.1.1", "", { "peerDependencies": { "@electric-sql/pglite": "0.4.1" }, "bin": { "pglite-server": "dist/scripts/server.js" } }, "sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw=="],
"@electric-sql/pglite-tools": ["@electric-sql/pglite-tools@0.3.1", "", { "peerDependencies": { "@electric-sql/pglite": "0.4.1" } }, "sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA=="],
"@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
"@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="],
@@ -95,6 +104,8 @@
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
"@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="],
"@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="],
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
@@ -159,6 +170,8 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="],
"@next/env": ["@next/env@16.2.4", "", {}, "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A=="],
@@ -179,24 +192,50 @@
"@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="],
"@prisma/client": ["@prisma/client@6.10.1", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-Re4pMlcUsQsUTAYMK7EJ4Bw2kg3WfZAAlr8GjORJaK4VOP6LxRQUQ1TuLnxcF42XqGkWQ36q5CQF1yVadANQ6w=="],
"@prisma/adapter-pg": ["@prisma/adapter-pg@7.8.0", "", { "dependencies": { "@prisma/driver-adapter-utils": "7.8.0", "@types/pg": "^8.16.0", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "sha512-ygb3UkerK3v8MDpXVgCISdRNDozpxh6+JVJgiIGbSr5KBgz10LLf5ejUskPGoXlsIjxsOu6nuy1JVQr2EKGSlg=="],
"@prisma/config": ["@prisma/config@6.10.1", "", { "dependencies": { "jiti": "2.4.2" } }, "sha512-kz4/bnqrOrzWo8KzYguN0cden4CzLJJ+2VSpKtF8utHS3l1JS0Lhv6BLwpOX6X9yNreTbZQZwewb+/BMPDCIYQ=="],
"@prisma/client": ["@prisma/client@7.8.0", "", { "dependencies": { "@prisma/client-runtime-utils": "7.8.0" }, "peerDependencies": { "prisma": "*", "typescript": ">=5.4.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw=="],
"@prisma/debug": ["@prisma/debug@6.10.1", "", {}, "sha512-k2YT53cWxv9OLjW4zSYTZ6Z7j0gPfCzcr2Mj99qsuvlxr8WAKSZ2NcSR0zLf/mP4oxnYG842IMj3utTgcd7CaA=="],
"@prisma/client-runtime-utils": ["@prisma/client-runtime-utils@7.8.0", "", {}, "sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw=="],
"@prisma/engines": ["@prisma/engines@6.10.1", "", { "dependencies": { "@prisma/debug": "6.10.1", "@prisma/engines-version": "6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c", "@prisma/fetch-engine": "6.10.1", "@prisma/get-platform": "6.10.1" } }, "sha512-Q07P5rS2iPwk2IQr/rUQJ42tHjpPyFcbiH7PXZlV81Ryr9NYIgdxcUrwgVOWVm5T7ap02C0dNd1dpnNcSWig8A=="],
"@prisma/config": ["@prisma/config@7.8.0", "", { "dependencies": { "c12": "3.3.4", "deepmerge-ts": "7.1.5", "effect": "3.20.0", "empathic": "2.0.0" } }, "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw=="],
"@prisma/engines-version": ["@prisma/engines-version@6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c", "", {}, "sha512-ZJFTsEqapiTYVzXya6TUKYDFnSWCNegfUiG5ik9fleQva5Sk3DNyyUi7X1+0ZxWFHwHDr6BZV5Vm+iwP+LlciA=="],
"@prisma/debug": ["@prisma/debug@7.8.0", "", {}, "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA=="],
"@prisma/fetch-engine": ["@prisma/fetch-engine@6.10.1", "", { "dependencies": { "@prisma/debug": "6.10.1", "@prisma/engines-version": "6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c", "@prisma/get-platform": "6.10.1" } }, "sha512-clmbG/Jgmrc/n6Y77QcBmAUlq9LrwI9Dbgy4pq5jeEARBpRCWJDJ7PWW1P8p0LfFU0i5fsyO7FqRzRB8mkdS4g=="],
"@prisma/dev": ["@prisma/dev@0.24.3", "", { "dependencies": { "@electric-sql/pglite": "0.4.1", "@electric-sql/pglite-socket": "0.1.1", "@electric-sql/pglite-tools": "0.3.1", "@hono/node-server": "1.19.11", "@prisma/get-platform": "7.2.0", "@prisma/query-plan-executor": "7.2.0", "@prisma/streams-local": "0.1.2", "foreground-child": "3.3.1", "get-port-please": "3.2.0", "hono": "^4.12.8", "http-status-codes": "2.3.0", "pathe": "2.0.3", "proper-lockfile": "4.1.2", "remeda": "2.33.4", "std-env": "3.10.0", "valibot": "1.2.0", "zeptomatch": "2.1.0" } }, "sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg=="],
"@prisma/get-platform": ["@prisma/get-platform@6.10.1", "", { "dependencies": { "@prisma/debug": "6.10.1" } }, "sha512-4CY5ndKylcsce9Mv+VWp5obbR2/86SHOLVV053pwIkhVtT9C9A83yqiqI/5kJM9T1v1u1qco/bYjDKycmei9HA=="],
"@prisma/driver-adapter-utils": ["@prisma/driver-adapter-utils@7.8.0", "", { "dependencies": { "@prisma/debug": "7.8.0" } }, "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A=="],
"@prisma/engines": ["@prisma/engines@7.8.0", "", { "dependencies": { "@prisma/debug": "7.8.0", "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", "@prisma/fetch-engine": "7.8.0", "@prisma/get-platform": "7.8.0" } }, "sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw=="],
"@prisma/engines-version": ["@prisma/engines-version@7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", "", {}, "sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA=="],
"@prisma/fetch-engine": ["@prisma/fetch-engine@7.8.0", "", { "dependencies": { "@prisma/debug": "7.8.0", "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", "@prisma/get-platform": "7.8.0" } }, "sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ=="],
"@prisma/get-platform": ["@prisma/get-platform@7.2.0", "", { "dependencies": { "@prisma/debug": "7.2.0" } }, "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA=="],
"@prisma/query-plan-executor": ["@prisma/query-plan-executor@7.2.0", "", {}, "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ=="],
"@prisma/streams-local": ["@prisma/streams-local@0.1.2", "", { "dependencies": { "ajv": "^8.12.0", "better-result": "^2.7.0", "env-paths": "^3.0.0", "proper-lockfile": "^4.1.2" } }, "sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg=="],
"@prisma/studio-core": ["@prisma/studio-core@0.27.3", "", { "dependencies": { "@radix-ui/react-toggle": "1.1.10", "chart.js": "4.5.1" }, "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw=="],
"@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="],
"@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.7", "", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A=="],
"@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA=="],
"@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="],
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="],
"@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g=="],
"@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="],
"@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA=="],
"@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg=="],
@@ -207,6 +246,8 @@
"@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
"@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="],
"@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw=="],
"@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
@@ -219,10 +260,26 @@
"@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="],
"@radix-ui/react-form": ["@radix-ui/react-form@0.1.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ=="],
"@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg=="],
"@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
"@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="],
"@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew=="],
"@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA=="],
"@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w=="],
"@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.8", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg=="],
"@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw=="],
"@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="],
"@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.7", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ=="],
"@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="],
@@ -231,12 +288,34 @@
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q=="],
"@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.7", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg=="],
"@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.3.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ=="],
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="],
"@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="],
"@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="],
"@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="],
"@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw=="],
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="],
"@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="],
"@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g=="],
"@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="],
"@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q=="],
"@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-toggle-group": "1.1.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg=="],
"@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw=="],
"@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
@@ -247,6 +326,8 @@
"@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="],
"@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="],
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="],
@@ -259,6 +340,8 @@
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
@@ -297,18 +380,32 @@
"@types/papaparse": ["@types/papaparse@5.3.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-T3VuKMC2H0lgsjI9buTB3uuKj3EMD2eap1MOuEQuBQ44EnDx/IkGhU6EwiTf9zG3za4SKlmwKAImdDKdNnCsXg=="],
"@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="],
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
"@types/react-dom": ["@types/react-dom@19.1.6", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw=="],
"ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
"aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
"aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.23", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g=="],
"bcryptjs": ["bcryptjs@3.0.2", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog=="],
"better-result": ["better-result@2.9.2", "", {}, "sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q=="],
"c12": ["c12@3.3.4", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.4", "defu": "^6.1.6", "dotenv": "^17.3.1", "exsolve": "^1.0.8", "giget": "^3.2.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "pkg-types": "^2.3.0", "rc9": "^3.0.1" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA=="],
"caniuse-lite": ["caniuse-lite@1.0.30001723", "", {}, "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw=="],
"chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="],
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
@@ -317,24 +414,76 @@
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
"deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="],
"defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="],
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
"effect": ["effect@3.20.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw=="],
"empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="],
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
"env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
"fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="],
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="],
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
"get-port-please": ["get-port-please@3.2.0", "", {}, "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A=="],
"giget": ["giget@3.2.0", "", { "bin": { "giget": "dist/cli.mjs" } }, "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"grammex": ["grammex@3.1.12", "", {}, "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ=="],
"graphmatch": ["graphmatch@1.1.1", "", {}, "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg=="],
"hono": ["hono@4.12.18", "", {}, "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ=="],
"http-status-codes": ["http-status-codes@2.3.0", "", {}, "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA=="],
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
"jose": ["jose@6.0.11", "", {}, "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg=="],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
@@ -357,7 +506,11 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
"lucide-react": ["lucide-react@1.11.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-UOhjdztXCgdBReRcIhsvz2siIBogfv/lhJEIViCpLt924dO+GDms9T7DNoucI23s6kEPpe988m5N0D2ajnzb2g=="],
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
"lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="],
"lucide-react": ["lucide-react@1.17.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w=="],
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
@@ -367,6 +520,10 @@
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
"mysql2": ["mysql2@3.15.3", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg=="],
"named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"next": ["next@16.2.4", "", { "dependencies": { "@next/env": "16.2.4", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.4", "@next/swc-darwin-x64": "16.2.4", "@next/swc-linux-arm64-gnu": "16.2.4", "@next/swc-linux-arm64-musl": "16.2.4", "@next/swc-linux-x64-gnu": "16.2.4", "@next/swc-linux-x64-musl": "16.2.4", "@next/swc-win32-arm64-msvc": "16.2.4", "@next/swc-win32-x64-msvc": "16.2.4", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q=="],
@@ -377,17 +534,61 @@
"oauth4webapi": ["oauth4webapi@3.5.3", "", {}, "sha512-2bnHosmBLAQpXNBLOvaJMyMkr4Yya5ohE5Q9jqyxiN+aa7GFCzvDN1RRRMrp0NkfqRR2MTaQNkcSUCCjILD9oQ=="],
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
"papaparse": ["papaparse@5.5.3", "", {}, "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="],
"pg": ["pg@8.20.0", "", { "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", "pg-protocol": "^1.13.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA=="],
"pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="],
"pg-connection-string": ["pg-connection-string@2.12.0", "", {}, "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ=="],
"pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
"pg-pool": ["pg-pool@3.13.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA=="],
"pg-protocol": ["pg-protocol@1.13.0", "", {}, "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w=="],
"pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="],
"postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="],
"postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
"postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"preact": ["preact@10.24.3", "", {}, "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="],
"preact-render-to-string": ["preact-render-to-string@6.5.11", "", { "peerDependencies": { "preact": ">=10" } }, "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw=="],
"prisma": ["prisma@6.10.1", "", { "dependencies": { "@prisma/config": "6.10.1", "@prisma/engines": "6.10.1" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-khhlC/G49E4+uyA3T3H5PRBut486HD2bDqE2+rvkU0pwk9IAqGFacLFUyIx9Uw+W2eCtf6XGwsp+/strUwMNPw=="],
"prisma": ["prisma@7.8.0", "", { "dependencies": { "@prisma/config": "7.8.0", "@prisma/dev": "0.24.3", "@prisma/engines": "7.8.0", "@prisma/studio-core": "0.27.3", "mysql2": "3.15.3", "postgres": "3.4.7" }, "peerDependencies": { "better-sqlite3": ">=9.0.0", "typescript": ">=5.4.0" }, "optionalPeers": ["better-sqlite3", "typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw=="],
"proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
"radix-ui": ["radix-ui@1.4.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-accessible-icon": "1.1.7", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-aspect-ratio": "1.1.7", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.16", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-form": "0.1.8", "@radix-ui/react-hover-card": "1.1.15", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-menubar": "1.1.16", "@radix-ui/react-navigation-menu": "1.2.14", "@radix-ui/react-one-time-password-field": "0.1.8", "@radix-ui/react-password-toggle-field": "0.1.3", "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-progress": "1.1.7", "@radix-ui/react-radio-group": "1.3.8", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.3.6", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-switch": "1.2.6", "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle-group": "1.1.11", "@radix-ui/react-toolbar": "1.1.11", "@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA=="],
"rc9": ["rc9@3.0.1", "", { "dependencies": { "defu": "^6.1.6", "destr": "^2.0.5" } }, "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ=="],
"react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="],
@@ -401,18 +602,42 @@
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"remeda": ["remeda@2.33.4", "", {}, "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
"retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="],
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
"sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="],
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
"tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="],
@@ -439,12 +664,112 @@
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
"valibot": ["valibot@1.2.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"zeptomatch": ["zeptomatch@2.1.0", "", { "dependencies": { "grammex": "^3.1.11", "graphmatch": "^1.1.0" } }, "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA=="],
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@prisma/engines/@prisma/get-platform": ["@prisma/get-platform@7.8.0", "", { "dependencies": { "@prisma/debug": "7.8.0" } }, "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw=="],
"@prisma/fetch-engine/@prisma/get-platform": ["@prisma/get-platform@7.8.0", "", { "dependencies": { "@prisma/debug": "7.8.0" } }, "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw=="],
"@prisma/get-platform/@prisma/debug": ["@prisma/debug@7.2.0", "", {}, "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw=="],
"@radix-ui/react-accordion/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-accordion/@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="],
"@radix-ui/react-alert-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-alert-dialog/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="],
"@radix-ui/react-context-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-context-menu/@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="],
"@radix-ui/react-form/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-hover-card/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-hover-card/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
"@radix-ui/react-hover-card/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="],
"@radix-ui/react-hover-card/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-menu/@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q=="],
"@radix-ui/react-menubar/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-menubar/@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="],
"@radix-ui/react-navigation-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-navigation-menu/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
"@radix-ui/react-navigation-menu/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-one-time-password-field/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-password-toggle-field/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-popover/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-popover/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
"@radix-ui/react-popover/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
"@radix-ui/react-popover/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="],
"@radix-ui/react-popover/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-popper/@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.3", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA=="],
"@radix-ui/react-radio-group/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-radio-group/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-roving-focus/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-scroll-area/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-scroll-area/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-select/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-select/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
"@radix-ui/react-select/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
"@radix-ui/react-select/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="],
"@radix-ui/react-slider/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-switch/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-tabs/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-tabs/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-toast/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-toast/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
"@radix-ui/react-toast/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-toggle/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-toggle-group/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-toolbar/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
@@ -459,10 +784,62 @@
"@types/papaparse/@types/node": ["@types/node@20.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA=="],
"c12/jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"radix-ui/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"radix-ui/@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="],
"radix-ui/@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="],
"radix-ui/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="],
"radix-ui/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
"radix-ui/@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="],
"radix-ui/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
"radix-ui/@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="],
"radix-ui/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="],
"radix-ui/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"radix-ui/@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="],
"sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"@radix-ui/react-accordion/@radix-ui/react-collapsible/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-alert-dialog/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
"@radix-ui/react-alert-dialog/@radix-ui/react-dialog/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
"@radix-ui/react-alert-dialog/@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-context-menu/@radix-ui/react-menu/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
"@radix-ui/react-context-menu/@radix-ui/react-menu/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
"@radix-ui/react-context-menu/@radix-ui/react-menu/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="],
"@radix-ui/react-context-menu/@radix-ui/react-menu/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-menubar/@radix-ui/react-menu/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
"@radix-ui/react-menubar/@radix-ui/react-menu/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
"@radix-ui/react-menubar/@radix-ui/react-menu/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="],
"@radix-ui/react-menubar/@radix-ui/react-menu/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-popper/@floating-ui/react-dom/@floating-ui/dom": ["@floating-ui/dom@1.7.1", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/utils": "^0.2.9" } }, "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ=="],
"@types/papaparse/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
+2 -2
View File
@@ -5,7 +5,7 @@
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"css": "src/styles/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
@@ -18,4 +18,4 @@
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
}
+5
View File
@@ -37,6 +37,11 @@ services:
DOMAIN: ${DOMAIN}
AUTH_TRUST_HOST: ${AUTH_TRUST_HOST}
AUTH_SECRET: ${AUTH_SECRET}
ADMIN_BOOTSTRAP_ENABLED: ${ADMIN_BOOTSTRAP_ENABLED:-"true"}
ADMIN_USERNAME: ${ADMIN_USERNAME:-"admin"}
ADMIN_EMAIL: ${ADMIN_EMAIL:-"admin@localhost"}
ADMIN_NAME: ${ADMIN_NAME:-"Administrator"}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public
depends_on:
- db
+9 -7
View File
@@ -2,6 +2,8 @@
"name": "stock-manager",
"version": "0.1.0",
"private": true,
"packageManager": "bun@1.3.14",
"type": "module",
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
@@ -14,16 +16,14 @@
"db:migrate:reset": "bunx prisma migrate reset",
"db:deploy": "bunx prisma migrate deploy",
"db:generate": "bunx prisma generate",
"db:seed": "bunx --bun prisma db seed",
"db:studio": "bunx prisma studio"
},
"prisma": {
"schema": "src/prisma/schema.prisma",
"seed": "bun src/prisma/seed.ts"
},
"dependencies": {
"@base-ui/react": "^1.4.1",
"@hookform/resolvers": "^5.2.2",
"@prisma/client": "^6.10.1",
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-dialog": "^1.1.14",
@@ -35,11 +35,13 @@
"bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^1.11.0",
"dotenv": "^17.4.2",
"lucide-react": "^1.17.0",
"next": "^16.2.4",
"next-auth": "^5.0.0-beta.28",
"next-themes": "^0.4.6",
"papaparse": "^5.5.3",
"radix-ui": "^1.4.3",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-hook-form": "^7.74.0",
@@ -54,7 +56,7 @@
"@types/node": "^24.0.3",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"prisma": "^6.10.1",
"prisma": "^7.8.0",
"tailwindcss": "^4.1.10",
"tw-animate-css": "^1.3.4",
"typescript": "^5.8.3"
+13
View File
@@ -0,0 +1,13 @@
import "dotenv/config"
import { defineConfig, env } from "prisma/config"
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
seed: "bun ./prisma/seed.ts",
},
datasource: {
url: env("DATABASE_URL"),
},
})
+80
View File
@@ -0,0 +1,80 @@
import { fileURLToPath } from "node:url"
import { getPasswordHash } from "@/lib/security"
import prisma from "../src/lib/prisma"
type BootstrapAdminInput = {
username: string
email: string
name: string
password: string
}
function getBootstrapAdminInput(): BootstrapAdminInput {
const isProduction = process.env.NODE_ENV === "production"
const username = process.env.ADMIN_USERNAME ?? "admin"
const email = process.env.ADMIN_EMAIL ?? "admin@localhost"
const name = process.env.ADMIN_NAME ?? "Administrator"
const password = process.env.ADMIN_PASSWORD
if (isProduction && !password) {
throw new Error("ADMIN_PASSWORD is required to bootstrap an admin user")
}
return {
username,
email,
name,
password: password ?? "admin",
}
}
export async function bootstrapAdmin(client: typeof prisma) {
const enabled = process.env.ADMIN_BOOTSTRAP_ENABLED !== "false"
const existingAdmin = await client.user.findFirst({
where: {
role: "ADMIN",
isActive: true,
},
select: {
id: true,
},
})
if (existingAdmin || !enabled) return
const admin = getBootstrapAdminInput()
await client.user.upsert({
where: {
email: admin.email,
},
update: {
role: "ADMIN",
isActive: true,
},
create: {
name: admin.name,
username: admin.username,
email: admin.email,
role: "ADMIN",
password: await getPasswordHash(admin.password),
isActive: true,
},
})
}
async function main() {
try {
await bootstrapAdmin(prisma)
} finally {
await prisma.$disconnect()
}
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main().catch((error) => {
console.error(error)
process.exit(1)
})
}
@@ -146,9 +146,6 @@ CREATE INDEX "Category_name_idx" ON "Category"("name");
-- CreateIndex
CREATE INDEX "Item_categoryId_idx" ON "Item"("categoryId");
-- CreateIndex
CREATE INDEX "Item_name_idx" ON "Item"("name");
-- CreateIndex
CREATE UNIQUE INDEX "Asset_serialNumber_key" ON "Asset"("serialNumber");
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE UNIQUE INDEX "Item_name_key" ON "Item"("name");
+184
View File
@@ -0,0 +1,184 @@
// This is your Prisma schema file,
// learn more about the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
binaryTargets = ["native", "debian-openssl-1.1.x"]
}
datasource db {
provider = "postgresql"
}
enum UserRole {
ADMIN
MANAGER
STAFF
VIEWER
}
model User {
id String @id @default(uuid())
username String @unique
name String
email String @unique
password String
role UserRole @default(STAFF)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
movements Movement[]
assignments Assignment[]
}
enum RecipientDepartment {
IT
ENGINEERING
LOGISTICS
TRAFFIC
DRIVER
ADMINISTRATION
SALES
OTHER
}
model Recipient {
id String @id @default(uuid())
username String @unique
firstName String
lastName String
department RecipientDepartment?
email String? @unique
phone String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
assignments Assignment[]
movements Movement[]
@@index([lastName, firstName])
@@index([department])
}
model Category {
id String @id @default(uuid())
name String @unique
description String?
isActive Boolean @default(true)
items Item[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([name])
}
enum ItemStatus {
AVAILABLE
ASSIGNED
RESERVED
IN_REPAIR
BROKEN
STOLEN
DISPOSED
}
model Item {
id String @id @default(uuid())
name String @unique
description String?
categoryId String
category Category @relation(fields: [categoryId], references: [id])
stock Int @default(0)
minStock Int?
maxStock Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
movements Movement[]
assignments Assignment[]
assets Asset[]
@@index([categoryId])
}
model Asset {
id String @id @default(uuid())
itemId String?
item Item? @relation(fields: [itemId], references: [id])
serialNumber String @unique
deliveryNote String?
status ItemStatus @default(AVAILABLE)
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
movements Movement[]
assignment Assignment?
@@index([serialNumber])
@@index([itemId])
@@index([status])
}
model Assignment {
id String @id @default(uuid())
quantity Int?
notes String?
itemId String?
item Item? @relation(fields: [itemId], references: [id])
assetId String? @unique
asset Asset? @relation(fields: [assetId], references: [id])
recipientId String?
recipient Recipient? @relation(fields: [recipientId], references: [id], onDelete: Cascade, onUpdate: Cascade)
assignmentDate DateTime @default(now())
returnDate DateTime?
createdBy String
createdUser User @relation(fields: [createdBy], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
movement Movement[]
@@index([itemId])
@@index([assetId])
@@index([recipientId])
@@index([createdBy])
}
enum MovementType {
IN
OUT
ASSIGNMENT
RETURN
ADJUSTMENT
DELETED
}
model Movement {
id String @id @default(uuid())
type MovementType @default(IN)
quantity Int
details String?
notes String?
itemId String?
item Item? @relation(fields: [itemId], references: [id])
assetId String?
asset Asset? @relation(fields: [assetId], references: [id])
previousStock Int?
newStock Int?
recipientId String?
recipient Recipient? @relation(fields: [recipientId], references: [id])
assignmentId String?
assignment Assignment? @relation(fields: [assignmentId], references: [id])
userId String
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
@@index([itemId])
@@index([assetId])
@@index([recipientId])
@@index([type])
@@index([userId])
}
+17
View File
@@ -0,0 +1,17 @@
import prisma from "../src/lib/prisma"
import { bootstrapAdmin } from "./bootstrap-admin"
async function main() {
await bootstrapAdmin(prisma)
}
main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error(e)
await prisma.$disconnect()
process.exit(1)
})
+93
View File
@@ -0,0 +1,93 @@
"use server"
import { revalidatePath } from "next/cache"
import { flattenError } from "zod"
import {
type CreateAssetFormType,
createAssetSchema,
type UpdateAssetFormType,
updateAssetSchema,
} from "@/schemas/asset.schema"
import { getAuthenticatedUserId } from "@/services/auth.service"
import {
createAssetUseCase,
updateAssetUseCase,
} from "@/use-cases/asset.use-cases"
export async function createAssetAction(formData: CreateAssetFormType) {
try {
const validatedFields = createAssetSchema.safeParse(formData)
if (!validatedFields.success) {
return {
errors: flattenError(validatedFields.error).fieldErrors,
}
}
const userId = await getAuthenticatedUserId()
const result = await createAssetUseCase({
...validatedFields.data,
actorId: userId,
})
if (!result.success) {
return result
}
revalidatePath("/inventory/assets")
revalidatePath("/inventory/items")
revalidatePath("/assignments")
revalidatePath("/movements")
return {
success: true,
message: "Asset created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
message: "Error creating asset",
}
}
}
export async function updateAssetAction(formData: UpdateAssetFormType) {
const validatedFields = updateAssetSchema.safeParse(formData)
if (!validatedFields.success) {
return {
errors: flattenError(validatedFields.error).fieldErrors,
}
}
try {
const userId = await getAuthenticatedUserId()
const result = await updateAssetUseCase({
...validatedFields.data,
actorId: userId,
})
if (!result.success) {
return result
}
revalidatePath("/inventory/assets")
revalidatePath("/inventory/items")
revalidatePath("/assignments")
revalidatePath("/movements")
return {
success: true,
message: "Asset updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
message: "Error updating asset",
}
}
}
+118
View File
@@ -0,0 +1,118 @@
"use server"
import { revalidatePath } from "next/cache"
import { getAuthenticatedUserId } from "@/services/auth.service"
import {
createAssignmentUseCase,
returnAssignmentUseCase,
updateAssignmentUseCase,
} from "@/use-cases/assignment.use-cases"
import {
assignmentSchema,
type CreateAssignmentFormType,
type ReturnAssignmentFormType,
type UpdateAssignmentFormType,
updateAssignmentSchema,
} from "@/schemas/assignment.schema"
export async function createAssignment(formData: CreateAssignmentFormType) {
const createdBy = await getAuthenticatedUserId()
const validatedFields = assignmentSchema.safeParse({
...formData,
createdBy,
})
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await createAssignmentUseCase({
...validatedFields.data,
actorId: createdBy,
})
if (!result.success) {
return result
}
revalidatePath("/assignments")
return {
success: true,
message: "Assignment created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
errors: { error: ["Error creating assignment"] },
}
}
}
export async function updateAssignment(formData: UpdateAssignmentFormType) {
const validatedFields = updateAssignmentSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const createdBy = await getAuthenticatedUserId()
const result = await updateAssignmentUseCase({
...validatedFields.data,
actorId: createdBy,
})
if (!result.success) {
return result
}
revalidatePath("/assignments")
return {
success: true,
message: "Assignment updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
errors: { error: ["Error updating assignment"] },
}
}
}
export async function returnAssignment(formData: ReturnAssignmentFormType) {
const { id } = formData
const userId = await getAuthenticatedUserId()
const result = await returnAssignmentUseCase({
id,
actorId: userId,
})
if (!result.success) {
return {
...result,
message: "Error returning assignment",
}
}
revalidatePath("/assignments")
return {
success: true as const,
message: "Assignment returned successfully",
}
}
@@ -3,7 +3,7 @@
import { AuthError } from "next-auth"
import { signIn } from "@/lib/auth"
import type { SignInFormType } from "@/lib/schemas/auth.schemas"
import type { SignInFormType } from "@/schemas/auth.schema"
export async function signInAction(values: SignInFormType) {
const { username, password } = values
@@ -7,8 +7,12 @@ import {
createCategorySchema,
type UpdateCategoryFormType,
updateCategorySchema,
} from "@/lib/schemas/category.schemas"
import { CategoryService } from "@/services/category.service"
} from "@/schemas/category.schema"
import {
createCategoryUseCase,
deleteCategoryUseCase,
updateCategoryUseCase,
} from "@/use-cases/category.use-cases"
export async function createCategoryAction(formData: CreateCategoryFormType) {
const validatedFields = createCategorySchema.safeParse(formData)
@@ -21,21 +25,12 @@ export async function createCategoryAction(formData: CreateCategoryFormType) {
}
try {
const existingCategory = await CategoryService.findByName(
validatedFields.data.name,
)
const result = await createCategoryUseCase(validatedFields.data)
if (existingCategory) {
return {
success: false,
errors: {
name: ["Category already exists"],
},
}
if (!result.success) {
return result
}
await CategoryService.create(validatedFields.data)
revalidatePath("/inventory/categories")
return {
@@ -64,36 +59,13 @@ export async function updateCategoryAction(formData: UpdateCategoryFormType) {
}
}
const { id, name } = validatedFields.data
try {
const existingCategory = await CategoryService.findById(id)
const result = await updateCategoryUseCase(validatedFields.data)
if (!existingCategory) {
return {
success: false,
errors: { id: ["Category not found"] },
}
if (!result.success) {
return result
}
if (existingCategory.name === name) {
return {
success: false,
errors: { name: ["Category name is the same as the old one"] },
}
}
if (await CategoryService.findByName(name)) {
return {
success: false,
errors: { name: ["Category already exists"] },
}
}
await CategoryService.update(id, {
name,
})
revalidatePath("/inventory/categories")
return {
@@ -113,40 +85,27 @@ export async function deleteCategoryAction(formData: FormData) {
const { id } = Object.fromEntries(formData) as { id: string }
try {
const existingCategory = await CategoryService.findAllWithItemsCount()
const category = existingCategory.find((category) => category.id === id)
const result = await deleteCategoryUseCase(id)
if (!category) {
if (!result.success) {
return {
success: false,
errors: {
id: ["Category not found"],
},
...result,
message: "Failed to delete category",
}
}
if (category._count.items && category._count.items > 0) {
return {
success: false,
errors: {
id: ["Category has items"],
},
}
}
await CategoryService.delete(id)
revalidatePath("/inventory/categories")
return {
success: true,
success: true as const,
message: "Category deleted successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
success: false as const,
message: "Failed to delete category",
errors: {},
}
}
}
@@ -4,8 +4,15 @@ import { revalidatePath } from "next/cache"
import Papa from "papaparse"
import { flattenError } from "zod"
import { type ImportFormType, importSchema } from "@/lib/schemas/import.schemas"
import type { CreateMovementFormType } from "@/lib/schemas/movement.schemas"
import { type ImportFormType, importSchema } from "@/schemas/import.schema"
import type { CreateMovementFormType } from "@/schemas/movement.schema"
import { AssetService } from "@/services/asset.service"
import { AssignmentService } from "@/services/assignment.service"
import { getAuthenticatedUserId } from "@/services/auth.service"
import { CategoryService } from "@/services/category.service"
import { ItemService } from "@/services/item.service"
import { MovementService } from "@/services/movement.service"
import { RecipientService } from "@/services/recipient.service"
import type {
Asset,
Assignment,
@@ -13,13 +20,7 @@ import type {
ImportItem,
Item,
Recipient,
} from "@/lib/types"
import { AssetService } from "@/services/asset.service"
import { AssignmentService } from "@/services/assignment.service"
import { CategoryService } from "@/services/category.service"
import { ItemService } from "@/services/item.service"
import { MovementService } from "@/services/movement.service"
import { RecipientService } from "@/services/recipient.service"
} from "@/types"
export async function importItems(formData: ImportFormType) {
const validatedFields = importSchema.safeParse(formData)
@@ -31,6 +32,7 @@ export async function importItems(formData: ImportFormType) {
}
const { file, categoryId } = validatedFields.data
const userId = await getAuthenticatedUserId()
if (!file) {
return {
@@ -313,6 +315,7 @@ export async function importItems(formData: ImportFormType) {
assetId: newAsset?.id || "",
recipientId: newRecipient?.id || "",
assignmentDate: new Date(),
createdBy: userId,
})
}
@@ -332,7 +335,10 @@ export async function importItems(formData: ImportFormType) {
movementData.recipientId = newRecipient.id
}
await MovementService.create(movementData)
await MovementService.create({
...movementData,
userId,
})
}
revalidatePath("/inventory/items")
+116
View File
@@ -0,0 +1,116 @@
"use server"
import { revalidatePath } from "next/cache"
import {
type CreateItemFormType,
createItemSchema,
type UpdateItemFormType,
updateItemSchema,
} from "@/schemas/item.schema"
import { getAuthenticatedUserId } from "@/services/auth.service"
import {
createItemUseCase,
deleteItemUseCase,
updateItemUseCase,
} from "@/use-cases/item.use-cases"
export async function createItemAction(formData: CreateItemFormType) {
const validatedFields = createItemSchema.safeParse(formData)
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const userId = await getAuthenticatedUserId()
const result = await createItemUseCase({
...validatedFields.data,
actorId: userId,
})
if (!result.success) {
return result
}
revalidatePath("/inventory/items")
revalidatePath("/movements")
return {
success: true,
message: "Item created successfully!",
}
} catch (error) {
console.error("Database error:", error)
return {
error: "Error creating item",
}
}
}
export async function updateItemAction(formData: UpdateItemFormType) {
const validatedFields = updateItemSchema.safeParse(formData)
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const userId = await getAuthenticatedUserId()
const result = await updateItemUseCase({
...validatedFields.data,
actorId: userId,
})
if (!result.success) {
return result
}
revalidatePath("/inventory/items")
revalidatePath("/movements")
return {
success: true,
message: "Item updated successfully!",
}
} catch (error) {
console.error("Database error:", error)
return {
error: "Failed to update item",
}
}
}
export async function deleteItemAction(formData: FormData) {
const { id } = Object.fromEntries(formData) as { id: string }
try {
const result = await deleteItemUseCase(id)
if (!result.success) {
return {
...result,
message: "Failed to delete item",
}
}
revalidatePath("/inventory/items")
return {
success: true as const,
message: "Item deleted successfully!",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false as const,
message: "Failed to delete item",
errors: {},
}
}
}
+76
View File
@@ -0,0 +1,76 @@
"use server"
import { revalidatePath } from "next/cache"
import {
type CreateRecipientFormType,
createRecipientSchema,
type UpdateRecipientFormType,
updateRecipientSchema,
} from "@/schemas/recipient.schema"
import {
createRecipientUseCase,
updateRecipientUseCase,
} from "@/use-cases/recipient.use-cases"
export async function createNewRecipient(formData: CreateRecipientFormType) {
const validatedFields = createRecipientSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await createRecipientUseCase(validatedFields.data)
if (!result.success) {
return result
}
revalidatePath("/recipients")
return {
success: true,
message: "Recipient created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
message: "Failed to create recipient",
}
}
}
export async function updateRecipient(formData: UpdateRecipientFormType) {
const validatedFields = updateRecipientSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await updateRecipientUseCase(validatedFields.data)
if (!result.success) {
return result
}
revalidatePath("/recipients")
return {
success: true,
message: "Recipient updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
message: "Failed to update recipient",
}
}
}
+144
View File
@@ -0,0 +1,144 @@
"use server"
import { revalidatePath } from "next/cache"
import { flattenError } from "zod"
import {
type CreateUserFormType,
createUserSchema,
type ResetUserPasswordFormType,
resetUserPasswordSchema,
type SetUserActiveFormType,
setUserActiveSchema,
type UpdateUserFormType,
updateUserSchema,
} from "@/schemas/user.schema"
import { requireRole } from "@/services/auth.service"
import {
createUserUseCase,
resetUserPasswordUseCase,
setUserActiveUseCase,
updateUserUseCase,
} from "@/use-cases/user.use-cases"
const USERS_PATH = "/admin/users"
export async function createUserAction(formData: CreateUserFormType) {
await requireRole("ADMIN")
const validatedFields = createUserSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await createUserUseCase(validatedFields.data)
if (!result.success) {
return result
}
revalidatePath(USERS_PATH)
return { success: true, message: "User created successfully" }
} catch (error) {
console.error("Database error:", error)
return { success: false, message: "Failed to create user" }
}
}
export async function updateUserAction(formData: UpdateUserFormType) {
const session = await requireRole("ADMIN")
const validatedFields = updateUserSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: flattenError(validatedFields.error).fieldErrors,
}
}
try {
const result = await updateUserUseCase({
...validatedFields.data,
actorId: session.user.id,
})
if (!result.success) {
return result
}
revalidatePath(USERS_PATH)
return { success: true, message: "User updated successfully" }
} catch (error) {
console.error("Database error:", error)
return { success: false, message: "Failed to update user" }
}
}
export async function setUserActiveAction(formData: SetUserActiveFormType) {
const session = await requireRole("ADMIN")
const validatedFields = setUserActiveSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: flattenError(validatedFields.error).fieldErrors,
}
}
try {
const result = await setUserActiveUseCase({
...validatedFields.data,
actorId: session.user.id,
})
if (!result.success) {
return result
}
revalidatePath(USERS_PATH)
return { success: true, message: "User status updated successfully" }
} catch (error) {
console.error("Database error:", error)
return { success: false, message: "Failed to update user status" }
}
}
export async function resetUserPasswordAction(
formData: ResetUserPasswordFormType,
) {
await requireRole("ADMIN")
const validatedFields = resetUserPasswordSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await resetUserPasswordUseCase(validatedFields.data)
if (!result.success) {
return result
}
revalidatePath(USERS_PATH)
return { success: true, message: "Password reset successfully" }
} catch (error) {
console.error("Database error:", error)
return { success: false, message: "Failed to reset password" }
}
}
@@ -4,10 +4,9 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter, useSearchParams } from "next/navigation"
import { useState } from "react"
import { useForm } from "react-hook-form"
import { signInAction } from "@/actions/auth.actions"
import { Button } from "@/components/ui/button"
import { signInAction } from "@/lib/actions/auth.actions"
import { type SignInFormType, signInSchema } from "@/lib/schemas/auth.schemas"
import { type SignInFormType, signInSchema } from "@/schemas/auth.schema"
export default function SignInForm() {
const router = useRouter()
+13
View File
@@ -0,0 +1,13 @@
import type { ReactNode } from "react"
import { requireRole } from "@/services/auth.service"
export default async function AdminLayout({
children,
}: {
children: ReactNode
}) {
await requireRole("ADMIN")
return children
}
@@ -0,0 +1,32 @@
import { notFound } from "next/navigation"
import { getUserProfileById } from "@/services/user.service"
import EditUserForm from "../../_components/edit.user.form"
import ResetUserPasswordForm from "../../_components/reset.user.password.form"
export default async function EditUserPage({
params,
}: {
params: Promise<{ userId: string }>
}) {
const { userId } = await params
const user = await getUserProfileById(userId)
if (!user) {
notFound()
}
return (
<div className="flex flex-col gap-8">
<div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">Edit User</h1>
</div>
<EditUserForm user={user} />
<section className="flex flex-col gap-4 border-t pt-6">
<h2 className="text-xl font-semibold">Reset password</h2>
<ResetUserPasswordForm userId={user.id} />
</section>
</div>
)
}
@@ -0,0 +1,141 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import type { UseFormRegisterReturn } from "react-hook-form"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { updateUserAction } from "@/actions/user.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import {
type UpdateUserFormType,
updateUserSchema,
} from "@/schemas/user.schema"
import type { UserWithoutPassword } from "@/services/user.service"
export default function EditUserForm({ user }: { user: UserWithoutPassword }) {
const router = useRouter()
const {
register,
handleSubmit,
setError,
formState: { errors, isSubmitting, isSubmitSuccessful },
} = useForm<UpdateUserFormType>({
resolver: zodResolver(updateUserSchema),
defaultValues: {
id: user.id,
name: user.name,
username: user.username,
email: user.email,
role: user.role,
isActive: user.isActive,
},
})
const onSubmit = async (formData: UpdateUserFormType) => {
const response = await updateUserAction(formData)
if (response?.errors) {
Object.entries(response.errors).forEach(([fieldName, messages]) => {
messages.forEach((message: string) => {
setError(fieldName as keyof UpdateUserFormType, {
type: "server",
message,
})
toast.error(message)
})
})
return
}
if (response?.success) {
toast.success(response.message)
router.push("/admin/users")
}
}
return (
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
<input type="hidden" {...register("id")} />
<UserTextInput
error={errors.name?.message}
id="name"
label="Name"
placeholder="Full name"
register={register("name")}
/>
<UserTextInput
error={errors.username?.message}
id="username"
label="Username"
placeholder="username"
register={register("username")}
/>
<UserTextInput
error={errors.email?.message}
id="email"
label="Email"
placeholder="user@example.com"
register={register("email")}
type="email"
/>
<div className="flex flex-col gap-2">
<label htmlFor="role" className="mb-2 block text-lg">
Role
</label>
<select
id="role"
{...register("role")}
className="w-full rounded-lg border px-4 py-2"
>
<option value="ADMIN">Admin</option>
<option value="MANAGER">Manager</option>
<option value="STAFF">Staff</option>
<option value="VIEWER">Viewer</option>
</select>
</div>
<label className="flex items-center gap-2">
<input type="checkbox" {...register("isActive")} />
Active user
</label>
<SubmitButton
isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful}
>
Update User
</SubmitButton>
</form>
)
}
function UserTextInput({
error,
id,
label,
placeholder,
register,
type = "text",
}: {
error?: string
id: string
label: string
placeholder: string
register: UseFormRegisterReturn
type?: string
}) {
return (
<div className="flex flex-col gap-2">
<label htmlFor={id} className="mb-2 block text-lg">
{label}
</label>
<input
type={type}
id={id}
placeholder={placeholder}
{...register}
className={`w-full rounded-lg border px-4 py-2 ${error ? "border-error" : ""}`}
/>
{error && <p className="text-error">{error}</p>}
</div>
)
}
@@ -0,0 +1,145 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import type { UseFormRegisterReturn } from "react-hook-form"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { createUserAction } from "@/actions/user.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import {
type CreateUserFormType,
createUserSchema,
} from "@/schemas/user.schema"
export default function NewUserForm() {
const router = useRouter()
const {
register,
handleSubmit,
setError,
formState: { errors, isSubmitting, isSubmitSuccessful },
} = useForm<CreateUserFormType>({
resolver: zodResolver(createUserSchema),
defaultValues: {
role: "STAFF",
isActive: true,
},
})
const onSubmit = async (formData: CreateUserFormType) => {
const response = await createUserAction(formData)
if (response?.errors) {
Object.entries(response.errors).forEach(([fieldName, messages]) => {
messages.forEach((message: string) => {
setError(fieldName as keyof CreateUserFormType, {
type: "server",
message,
})
toast.error(message)
})
})
return
}
if (response?.success) {
toast.success(response.message)
router.push("/admin/users")
}
}
return (
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
<UserTextInput
error={errors.name?.message}
id="name"
label="Name"
placeholder="Full name"
register={register("name")}
/>
<UserTextInput
error={errors.username?.message}
id="username"
label="Username"
placeholder="username"
register={register("username")}
/>
<UserTextInput
error={errors.email?.message}
id="email"
label="Email"
placeholder="user@example.com"
register={register("email")}
type="email"
/>
<UserTextInput
error={errors.password?.message}
id="password"
label="Password"
placeholder="Minimum 8 characters"
register={register("password")}
type="password"
/>
<RoleSelect register={register("role")} />
<SubmitButton
isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful}
>
Create User
</SubmitButton>
</form>
)
}
function UserTextInput({
error,
id,
label,
placeholder,
register,
type = "text",
}: {
error?: string
id: string
label: string
placeholder: string
register: UseFormRegisterReturn
type?: string
}) {
return (
<div className="flex flex-col gap-2">
<label htmlFor={id} className="mb-2 block text-lg">
{label}
</label>
<input
type={type}
id={id}
placeholder={placeholder}
{...register}
className={`w-full rounded-lg border px-4 py-2 ${error ? "border-error" : ""}`}
/>
{error && <p className="text-error">{error}</p>}
</div>
)
}
function RoleSelect({ register }: { register: UseFormRegisterReturn }) {
return (
<div className="flex flex-col gap-2">
<label htmlFor="role" className="mb-2 block text-lg">
Role
</label>
<select
id="role"
{...register}
className="w-full rounded-lg border px-4 py-2"
>
<option value="ADMIN">Admin</option>
<option value="MANAGER">Manager</option>
<option value="STAFF">Staff</option>
<option value="VIEWER">Viewer</option>
</select>
</div>
)
}
@@ -0,0 +1,75 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { resetUserPasswordAction } from "@/actions/user.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import {
type ResetUserPasswordFormType,
resetUserPasswordSchema,
} from "@/schemas/user.schema"
export default function ResetUserPasswordForm({ userId }: { userId: string }) {
const {
register,
handleSubmit,
reset,
setError,
formState: { errors, isSubmitting, isSubmitSuccessful },
} = useForm<ResetUserPasswordFormType>({
resolver: zodResolver(resetUserPasswordSchema),
defaultValues: {
id: userId,
},
})
const onSubmit = async (formData: ResetUserPasswordFormType) => {
const response = await resetUserPasswordAction(formData)
if (response?.errors) {
Object.entries(response.errors).forEach(([fieldName, messages]) => {
messages.forEach((message: string) => {
setError(fieldName as keyof ResetUserPasswordFormType, {
type: "server",
message,
})
toast.error(message)
})
})
return
}
if (response?.success) {
toast.success(response.message)
reset({ id: userId, password: "" })
}
}
return (
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
<input type="hidden" {...register("id")} />
<div className="flex flex-col gap-2">
<label htmlFor="password" className="mb-2 block text-lg">
New password
</label>
<input
type="password"
id="password"
placeholder="Minimum 8 characters"
{...register("password")}
className={`w-full rounded-lg border px-4 py-2 ${errors.password ? "border-error" : ""}`}
/>
{errors.password && (
<p className="text-error">{errors.password.message}</p>
)}
</div>
<SubmitButton
isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful}
>
Reset Password
</SubmitButton>
</form>
)
}
@@ -0,0 +1,12 @@
import NewUserForm from "../_components/new.user.form"
export default function NewUserPage() {
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">New User</h1>
</div>
<NewUserForm />
</div>
)
}
+96
View File
@@ -0,0 +1,96 @@
import { Pencil } from "lucide-react"
import Link from "next/link"
import PageHeader from "@/components/common/pageheader"
import PaginationButtons from "@/components/common/pagination"
import { Button } from "@/components/ui/button"
import { getUsers } from "@/services/user.service"
export default async function UsersPage(props: {
searchParams?: Promise<{
page?: string
search?: string
}>
}) {
const searchParams = await props.searchParams
const currentPage = searchParams?.page ? parseInt(searchParams.page, 10) : 1
const search = searchParams?.search || ""
const { data: users, totalPages } = await getUsers({
page: currentPage,
pageSize: 10,
search,
})
return (
<div className="flex flex-col gap-4">
<PageHeader
title="Users"
link="/admin/users/new"
search={search}
data={users}
/>
{users.length === 0 && currentPage === 1 && (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4">
No users found.
</div>
</div>
)}
{users.length > 0 && (
<div className="overflow-x-auto">
<table className="text-muted-foreground w-full text-left text-sm">
<thead className="border-b">
<tr>
<th scope="col" className="p-4">
Name
</th>
<th scope="col" className="p-4">
Username
</th>
<th scope="col" className="p-4">
Email
</th>
<th scope="col" className="p-4">
Role
</th>
<th scope="col" className="p-4">
Status
</th>
<th scope="col" className="p-4">
Actions
</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id} className="border-b">
<td className="p-4">{user.name}</td>
<td className="p-4">{user.username}</td>
<td className="p-4">{user.email}</td>
<td className="p-4">{user.role}</td>
<td className="p-4">
{user.isActive ? "Active" : "Inactive"}
</td>
<td className="p-4">
<Link href={`/admin/users/${user.id}/edit`} passHref>
<Button variant="outline" size="icon">
<Pencil />
</Button>
</Link>
</td>
</tr>
))}
</tbody>
<tfoot className="border-t">
<tr>
<td colSpan={6} className="p-4 text-center text-sm">
<PaginationButtons totalPages={totalPages} />
</td>
</tr>
</tfoot>
</table>
</div>
)}
</div>
)
}
@@ -1,18 +1,18 @@
import type { UpdateAssignmentFormType } from "@/lib/schemas/assignment.schemas"
import type { Item } from "@/lib/types"
import type { UpdateAssignmentFormType } from "@/schemas/assignment.schema"
import { AssetService } from "@/services/asset.service"
import { AssignmentService } from "@/services/assignment.service"
import { ItemService } from "@/services/item.service"
import { RecipientService } from "@/services/recipient.service"
import type { Item } from "@/types"
import AssignmentForm from "../../_components/edit.assignment.form"
export default async function EditAssignmentPage({
params,
}: {
params: Promise<{ assignamentId: string }>
params: Promise<{ assignmentId: string }>
}) {
const { assignamentId } = await params
const assignment = await AssignmentService.findById(assignamentId)
const { assignmentId } = await params
const assignment = await AssignmentService.findById(assignmentId)
const recipients = await RecipientService.findAll()
const items = await ItemService.findAllWithStock()
const assets = await AssetService.findAll()
@@ -4,14 +4,13 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { updateAssignment } from "@/actions/assignment.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { updateAssignment } from "@/lib/actions/assignament.actions"
import {
type UpdateAssignmentFormType,
updateAssignmentSchema,
} from "@/lib/schemas/assignment.schemas"
import type { Asset, Item, Recipient } from "@/lib/types"
} from "@/schemas/assignment.schema"
import type { Asset, Item, Recipient } from "@/types"
interface Props {
recipients: Recipient[]
@@ -5,14 +5,13 @@ import { useRouter } from "next/navigation"
import { useMemo } from "react"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { createAssignment } from "@/actions/assignment.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { createAssignment } from "@/lib/actions/assignament.actions"
import {
type CreateAssignmentFormType,
createAssignmentSchema,
} from "@/lib/schemas/assignment.schemas"
import type { Asset, Item, Recipient } from "@/lib/types"
} from "@/schemas/assignment.schema"
import type { Asset, Item, Recipient } from "@/types"
interface Props {
recipients: Recipient[]
@@ -4,10 +4,9 @@ import { ArrowLeft } from "lucide-react"
import { useRouter } from "next/navigation"
import { useTransition } from "react"
import { toast } from "sonner"
import { returnAssignment } from "@/actions/assignment.actions"
import { Button } from "@/components/ui/button"
import { returnAssignment } from "@/lib/actions/assignament.actions"
import type { ReturnAssignmentFormType } from "@/lib/schemas/assignment.schemas"
import type { ReturnAssignmentFormType } from "@/schemas/assignment.schema"
export default function ReturnButton({
assignmentId,
+7 -1
View File
@@ -46,6 +46,9 @@ export default async function AssignmentsPage(props: {
<th scope="col" className="p-4">
Serial Number
</th>
<th scope="col" className="p-4">
Quantity
</th>
<th scope="col" className="p-4">
Actions
</th>
@@ -74,6 +77,9 @@ export default async function AssignmentsPage(props: {
<td className="p-4">
{assignment?.asset?.serialNumber || "N/A"}
</td>
<td className="p-4">
{assignment?.quantity}
</td>
<td className="p-4">
<div className="flex gap-2">
<Link
@@ -92,7 +98,7 @@ export default async function AssignmentsPage(props: {
</tbody>
<tfoot className="border-t">
<tr>
<td colSpan={4} className="p-4 text-center text-sm">
<td colSpan={5} className="p-4 text-center text-sm">
<PaginationButtons totalPages={totalPages} />
</td>
</tr>
@@ -5,11 +5,10 @@ import { useRouter } from "next/navigation"
import type { ChangeEvent } from "react"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { importItems } from "@/actions/import.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { importItems } from "@/lib/actions/import.actions"
import { type ImportFormType, importSchema } from "@/lib/schemas/import.schemas"
import type { CategorySummary } from "@/lib/types"
import { type ImportFormType, importSchema } from "@/schemas/import.schema"
import type { CategorySummary } from "@/types"
export default function ImportForm({
categories,
+1 -1
View File
@@ -15,7 +15,7 @@ export default async function ImportPage() {
<h1 className="text-2xl font-bold">Mass Import</h1>
</div>
<div className="flex items-center justify-end gap-4">
{ENVIRONMENT === "demo" && (
{(ENVIRONMENT === "development" || ENVIRONMENT === "demo") && (
<Link href="/sample_data.csv" download>
<Button variant="outline">
<Download />
@@ -1,9 +1,9 @@
"use server"
import type { AssetWithAssignment } from "@/lib/types"
import { AssetService } from "@/services/asset.service"
import { ItemService } from "@/services/item.service"
import { RecipientService } from "@/services/recipient.service"
import type { AssetWithAssignment } from "@/types"
import EditAssetForm from "../../_components/edit.asset.form"
@@ -4,20 +4,19 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { updateAssetAction } from "@/actions/asset.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { ItemStatus } from "@/generated/prisma/client"
import { updateAssetAction } from "@/lib/actions/asset.actions"
import { ITEM_STATUS } from "@/lib/constants"
import {
type UpdateAssetFormType,
updateAssetSchema,
} from "@/lib/schemas/asset.schemas"
} from "@/schemas/asset.schema"
import type {
AssetWithAssignment,
Item,
Recipient,
UpdateAssetStatus,
} from "@/lib/types"
} from "@/types"
interface EditAssetFormProps {
asset: AssetWithAssignment
@@ -42,11 +41,11 @@ export default function EditAssetForm({
resolver: zodResolver(updateAssetSchema),
defaultValues: {
id: asset.id,
itemId: asset.itemId ?? "",
itemId: asset.itemId ?? undefined,
serialNumber: asset.serialNumber,
deliveryNote: asset.deliveryNote ?? "",
deliveryNote: asset.deliveryNote ?? undefined,
status: asset.status as UpdateAssetStatus,
recipientId: asset.assignment?.recipientId ?? "",
recipientId: asset.assignment?.recipientId ?? undefined,
},
shouldFocusError: true,
mode: "onSubmit",
@@ -138,7 +137,7 @@ export default function EditAssetForm({
className="w-full rounded-lg border px-4 py-2"
>
<option value="">Select a status</option>
{Object.values(ItemStatus).map((status) => (
{Object.values(ITEM_STATUS).map((status) => (
<option key={status} value={status}>
{status}
</option>
@@ -4,15 +4,14 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { createAssetAction } from "@/actions/asset.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { ItemStatus } from "@/generated/prisma/client"
import { createAssetAction } from "@/lib/actions/asset.actions"
import { ITEM_STATUS } from "@/lib/constants"
import {
type CreateAssetFormType,
createAssetSchema,
} from "@/lib/schemas/asset.schemas"
import type { ItemWithoutStock, Recipient } from "@/lib/types"
} from "@/schemas/asset.schema"
import type { ItemWithoutStock, Recipient } from "@/types"
interface NewAssetFormProps {
items: ItemWithoutStock[]
@@ -123,7 +122,7 @@ export default function NewAssetForm({ items, recipients }: NewAssetFormProps) {
className="w-full rounded-lg border px-4 py-2"
>
<option value="">Select a status</option>
{Object.values(ItemStatus).map((status) => (
{Object.values(ITEM_STATUS).map((status) => (
<option key={status} value={status}>
{status}
</option>
@@ -4,9 +4,8 @@ import { Trash } from "lucide-react"
import { useRouter } from "next/navigation"
import { useTransition } from "react"
import { toast } from "sonner"
import { deleteCategoryAction } from "@/actions/category.actions"
import { Button } from "@/components/ui/button"
import { deleteCategoryAction } from "@/lib/actions/category.actions"
export default function DeleteCategoryButton({
categoryId,
@@ -4,14 +4,13 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { updateCategoryAction } from "@/actions/category.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { updateCategoryAction } from "@/lib/actions/category.actions"
import {
type UpdateCategoryFormType,
updateCategorySchema,
} from "@/lib/schemas/category.schemas"
import type { CategorySummary } from "@/lib/types"
} from "@/schemas/category.schema"
import type { CategorySummary } from "@/types"
export default function EditCategoryForm({
category,
@@ -4,13 +4,12 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { createCategoryAction } from "@/actions/category.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { createCategoryAction } from "@/lib/actions/category.actions"
import {
type CreateCategoryFormType,
createCategorySchema,
} from "@/lib/schemas/category.schemas"
} from "@/schemas/category.schema"
export default function NewCategoryForm() {
const router = useRouter()
@@ -4,9 +4,8 @@ import { Trash } from "lucide-react"
import { useRouter } from "next/navigation"
import { useTransition } from "react"
import { toast } from "sonner"
import { deleteItemAction } from "@/actions/item.actions"
import { Button } from "@/components/ui/button"
import { deleteItemAction } from "@/lib/actions/item.actions"
export default function DeleteItemButton({ itemId }: { itemId: string }) {
const router = useRouter()
@@ -4,14 +4,13 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { createItemAction } from "@/actions/item.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { createItemAction } from "@/lib/actions/item.actions"
import {
type CreateItemFormType,
createItemSchema,
} from "@/lib/schemas/item.schemas"
import type { CategorySummary } from "@/lib/types"
} from "@/schemas/item.schema"
import type { CategorySummary } from "@/types"
export default function NewItemForm({
categories,
@@ -4,14 +4,13 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { updateItemAction } from "@/actions/item.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { updateItemAction } from "@/lib/actions/item.actions"
import {
type UpdateItemFormType,
updateItemSchema,
} from "@/lib/schemas/item.schemas"
import type { CategorySummary, ItemWithAssetCount } from "@/lib/types"
} from "@/schemas/item.schema"
import type { CategorySummary, ItemWithAssetCount } from "@/types"
export default function UpdateItemForm({
categories,
+4 -1
View File
@@ -3,15 +3,18 @@ import { Toaster } from "sonner"
import Navbar from "@/components/layout/navbar"
import AppSidebar from "@/components/layout/sidebar"
import { SidebarProvider } from "@/components/ui/sidebar"
import { auth } from "@/lib/auth"
export default async function LayoutDashboard({
children,
}: {
children: React.ReactNode
}) {
const session = await auth()
return (
<SidebarProvider>
<AppSidebar />
<AppSidebar userRole={session?.user.role} />
<main className="w-full">
<Navbar />
<div className="flex-1 p-6">{children}</div>
@@ -4,19 +4,18 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { SubmitButton } from "@/components/forms/submitButton"
import { RecipientDepartment } from "@/generated/prisma/client"
import {
createNewRecipient,
updateRecipient,
} from "@/lib/actions/recipient.actions"
} from "@/actions/recipient.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { RECIPIENT_DEPARTMENTS } from "@/lib/constants"
import {
type CreateRecipientFormType,
recipientSchema,
type UpdateRecipientFormType,
} from "@/lib/schemas/recipients.schemas"
import type { Recipient } from "@/lib/types"
} from "@/schemas/recipient.schema"
import type { Recipient } from "@/types"
interface RecipientFormProps {
initialData?: Recipient
@@ -130,7 +129,7 @@ export default function RecipientForm({
className="w-full rounded-lg border px-4 py-2"
>
<option value="">Select a department</option>
{Object.keys(RecipientDepartment).map((department) => (
{Object.keys(RECIPIENT_DEPARTMENTS).map((department) => (
<option key={department} value={department}>
{department}
</option>
+11
View File
@@ -0,0 +1,11 @@
import Link from "next/link"
export default function ForbiddenPage() {
return (
<main>
<h1>Acceso denegado</h1>
<p>No tienes permisos para acceder a esta sección.</p>
<Link href="/">Volver al inicio</Link>
</main>
)
}
+19 -5
View File
@@ -5,12 +5,12 @@ import {
Clipboard,
Home,
Package,
Shield,
ShoppingCart,
User,
} from "lucide-react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import {
Sidebar,
SidebarContent,
@@ -21,6 +21,7 @@ import {
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar"
import type { UserRole } from "@/generated/prisma/client"
import { SidebarSection } from "./sidebar/sidebarSection"
@@ -72,9 +73,22 @@ const items = [
]
export default function AppSidebar({
userRole,
...props
}: React.ComponentProps<typeof Sidebar>) {
}: React.ComponentProps<typeof Sidebar> & { userRole?: UserRole }) {
const pathname = usePathname()
const visibleItems =
userRole === "ADMIN"
? [
...items,
{
type: "item",
title: "Users",
url: "/admin/users",
icon: Shield,
},
]
: items
return (
<Sidebar {...props}>
@@ -88,7 +102,7 @@ export default function AppSidebar({
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => {
{visibleItems.map((item) => {
if (item.type === "item") {
const isActive =
item.url === "/"
@@ -96,7 +110,7 @@ export default function AppSidebar({
: pathname.startsWith(item.url)
return (
<SidebarMenuItem key={`item-${item}`}>
<SidebarMenuItem key={`item-${item.title}`}>
<SidebarMenuButton asChild isActive={isActive}>
<Link href={item.url}>
<item.icon className="mr-2 h-4 w-4" />
@@ -109,7 +123,7 @@ export default function AppSidebar({
if (item.type === "section") {
return (
<SidebarSection
key={`section-${item}`}
key={`section-${item.title}`}
title={item.title}
icon={item.icon}
items={item.items}
+2 -2
View File
@@ -1,6 +1,6 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import type * as React from "react"
import { cn } from "@/lib/utils"
@@ -35,7 +35,7 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
},
}
)
function Button({
+1 -1
View File
@@ -1,4 +1,4 @@
import type * as React from "react"
import * as React from "react"
import { cn } from "@/lib/utils"
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"
import type * as React from "react"
import * as React from "react"
import { CheckIcon } from "lucide-react"
import { Checkbox as CheckboxPrimitive } from "radix-ui"
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"
import type * as React from "react"
import * as React from "react"
import { XIcon } from "lucide-react"
import { Dialog as DialogPrimitive } from "radix-ui"
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"
import type * as React from "react"
import * as React from "react"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
+1 -1
View File
@@ -1,4 +1,4 @@
import type * as React from "react"
import * as React from "react"
import { cn } from "@/lib/utils"
+1 -1
View File
@@ -1,4 +1,4 @@
import type * as React from "react"
import * as React from "react"
import {
ChevronLeftIcon,
ChevronRightIcon,
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"
import type * as React from "react"
import * as React from "react"
import { Separator as SeparatorPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"
import type * as React from "react"
import * as React from "react"
import { XIcon } from "lucide-react"
import { Dialog as SheetPrimitive } from "radix-ui"
+1 -1
View File
@@ -480,7 +480,7 @@ const sidebarMenuButtonVariants = cva(
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
"bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]",
},
size: {
default: "h-8 text-sm",
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"
import type * as React from "react"
import * as React from "react"
import { Tooltip as TooltipPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
-1
View File
@@ -1 +0,0 @@
export * from "./index"
-4
View File
@@ -1,4 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
module.exports = { ...require('.') }
-1
View File
@@ -1 +0,0 @@
export * from "./index"
-4
View File
@@ -1,4 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
module.exports = { ...require('.') }
-1
View File
@@ -1 +0,0 @@
export * from "./default"
File diff suppressed because one or more lines are too long
@@ -1,301 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
Object.defineProperty(exports, "__esModule", { value: true });
const {
Decimal,
objectEnumValues,
makeStrictEnum,
Public,
getRuntime,
skip
} = require('./runtime/index-browser.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.10.1
* Query Engine version: 9b628578b3b7cae625e8c927178f15a170e74a9c
*/
Prisma.prismaVersion = {
client: "6.10.1",
engine: "9b628578b3b7cae625e8c927178f15a170e74a9c"
}
Prisma.PrismaClientKnownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)};
Prisma.PrismaClientUnknownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientRustPanicError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientInitializationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientValidationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.empty = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.join = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.raw = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.defineExtension = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = objectEnumValues.instances.DbNull
Prisma.JsonNull = objectEnumValues.instances.JsonNull
Prisma.AnyNull = objectEnumValues.instances.AnyNull
Prisma.NullTypes = {
DbNull: objectEnumValues.classes.DbNull,
JsonNull: objectEnumValues.classes.JsonNull,
AnyNull: objectEnumValues.classes.AnyNull
}
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
});
exports.Prisma.UserScalarFieldEnum = {
id: 'id',
username: 'username',
name: 'name',
email: 'email',
password: 'password',
role: 'role',
isActive: 'isActive',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.RecipientScalarFieldEnum = {
id: 'id',
username: 'username',
firstName: 'firstName',
lastName: 'lastName',
department: 'department',
email: 'email',
phone: 'phone',
isActive: 'isActive',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.CategoryScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
isActive: 'isActive',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.ItemScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
categoryId: 'categoryId',
stock: 'stock',
minStock: 'minStock',
maxStock: 'maxStock',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
deletedAt: 'deletedAt'
};
exports.Prisma.AssetScalarFieldEnum = {
id: 'id',
itemId: 'itemId',
serialNumber: 'serialNumber',
deliveryNote: 'deliveryNote',
status: 'status',
notes: 'notes',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.AssignmentScalarFieldEnum = {
id: 'id',
quantity: 'quantity',
notes: 'notes',
itemId: 'itemId',
assetId: 'assetId',
recipientId: 'recipientId',
assignmentDate: 'assignmentDate',
returnDate: 'returnDate',
createdBy: 'createdBy',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.MovementScalarFieldEnum = {
id: 'id',
type: 'type',
quantity: 'quantity',
details: 'details',
notes: 'notes',
itemId: 'itemId',
assetId: 'assetId',
previousStock: 'previousStock',
newStock: 'newStock',
recipientId: 'recipientId',
assignmentId: 'assignmentId',
userId: 'userId',
createdAt: 'createdAt'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.QueryMode = {
default: 'default',
insensitive: 'insensitive'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.UserRole = exports.$Enums.UserRole = {
ADMIN: 'ADMIN',
MANAGER: 'MANAGER',
STAFF: 'STAFF',
VIEWER: 'VIEWER'
};
exports.RecipientDepartment = exports.$Enums.RecipientDepartment = {
IT: 'IT',
ENGINEERING: 'ENGINEERING',
LOGISTICS: 'LOGISTICS',
TRAFFIC: 'TRAFFIC',
DRIVER: 'DRIVER',
ADMINISTRATION: 'ADMINISTRATION',
SALES: 'SALES',
OTHER: 'OTHER'
};
exports.ItemStatus = exports.$Enums.ItemStatus = {
AVAILABLE: 'AVAILABLE',
ASSIGNED: 'ASSIGNED',
RESERVED: 'RESERVED',
IN_REPAIR: 'IN_REPAIR',
BROKEN: 'BROKEN',
STOLEN: 'STOLEN',
DISPOSED: 'DISPOSED'
};
exports.MovementType = exports.$Enums.MovementType = {
IN: 'IN',
OUT: 'OUT',
ASSIGNMENT: 'ASSIGNMENT',
RETURN: 'RETURN',
ADJUSTMENT: 'ADJUSTMENT',
DELETED: 'DELETED'
};
exports.Prisma.ModelName = {
User: 'User',
Recipient: 'Recipient',
Category: 'Category',
Item: 'Item',
Asset: 'Asset',
Assignment: 'Assignment',
Movement: 'Movement'
};
/**
* This is a stub Prisma Client that will error at runtime if called.
*/
class PrismaClient {
constructor() {
return new Proxy(this, {
get(target, prop) {
let message
const runtime = getRuntime()
if (runtime.isEdge) {
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
- Use Prisma Accelerate: https://pris.ly/d/accelerate
- Use Driver Adapters: https://pris.ly/d/driver-adapters
`;
} else {
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
}
message += `
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
throw new Error(message)
}
})
}
}
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-146
View File
@@ -1,146 +0,0 @@
{
"name": "prisma-client-8fa07f1ca1555b6abbe80c6f50fa3e992025f58ff8812e13aca6a56a4a773a8c",
"main": "index.js",
"types": "index.d.ts",
"browser": "index-browser.js",
"exports": {
"./client": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./package.json": "./package.json",
".": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./edge": {
"types": "./edge.d.ts",
"require": "./edge.js",
"import": "./edge.js",
"default": "./edge.js"
},
"./react-native": {
"types": "./react-native.d.ts",
"require": "./react-native.js",
"import": "./react-native.js",
"default": "./react-native.js"
},
"./extension": {
"types": "./extension.d.ts",
"require": "./extension.js",
"import": "./extension.js",
"default": "./extension.js"
},
"./index-browser": {
"types": "./index.d.ts",
"require": "./index-browser.js",
"import": "./index-browser.js",
"default": "./index-browser.js"
},
"./index": {
"types": "./index.d.ts",
"require": "./index.js",
"import": "./index.js",
"default": "./index.js"
},
"./wasm": {
"types": "./wasm.d.ts",
"require": "./wasm.js",
"import": "./wasm.mjs",
"default": "./wasm.mjs"
},
"./runtime/client": {
"types": "./runtime/client.d.ts",
"require": "./runtime/client.js",
"import": "./runtime/client.mjs",
"default": "./runtime/client.mjs"
},
"./runtime/library": {
"types": "./runtime/library.d.ts",
"require": "./runtime/library.js",
"import": "./runtime/library.mjs",
"default": "./runtime/library.mjs"
},
"./runtime/binary": {
"types": "./runtime/binary.d.ts",
"require": "./runtime/binary.js",
"import": "./runtime/binary.mjs",
"default": "./runtime/binary.mjs"
},
"./runtime/wasm-engine-edge": {
"types": "./runtime/wasm-engine-edge.d.ts",
"require": "./runtime/wasm-engine-edge.js",
"import": "./runtime/wasm-engine-edge.mjs",
"default": "./runtime/wasm-engine-edge.mjs"
},
"./runtime/wasm-compiler-edge": {
"types": "./runtime/wasm-compiler-edge.d.ts",
"require": "./runtime/wasm-compiler-edge.js",
"import": "./runtime/wasm-compiler-edge.mjs",
"default": "./runtime/wasm-compiler-edge.mjs"
},
"./runtime/edge": {
"types": "./runtime/edge.d.ts",
"require": "./runtime/edge.js",
"import": "./runtime/edge-esm.js",
"default": "./runtime/edge-esm.js"
},
"./runtime/react-native": {
"types": "./runtime/react-native.d.ts",
"require": "./runtime/react-native.js",
"import": "./runtime/react-native.js",
"default": "./runtime/react-native.js"
},
"./generator-build": {
"require": "./generator-build/index.js",
"import": "./generator-build/index.js",
"default": "./generator-build/index.js"
},
"./sql": {
"require": {
"types": "./sql.d.ts",
"node": "./sql.js",
"default": "./sql.js"
},
"import": {
"types": "./sql.d.ts",
"node": "./sql.mjs",
"default": "./sql.mjs"
},
"default": "./sql.js"
},
"./*": "./*"
},
"version": "6.10.1",
"sideEffects": false
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-370
View File
@@ -1,370 +0,0 @@
declare class AnyNull extends NullTypesEnumValue {
#private;
}
declare type Args<T, F extends Operation> = T extends {
[K: symbol]: {
types: {
operations: {
[K in F]: {
args: any;
};
};
};
};
} ? T[symbol]['types']['operations'][F]['args'] : any;
declare class DbNull extends NullTypesEnumValue {
#private;
}
export declare function Decimal(n: Decimal.Value): Decimal;
export declare namespace Decimal {
export type Constructor = typeof Decimal;
export type Instance = Decimal;
export type Rounding = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
export type Modulo = Rounding | 9;
export type Value = string | number | Decimal;
// http://mikemcl.github.io/decimal.js/#constructor-properties
export interface Config {
precision?: number;
rounding?: Rounding;
toExpNeg?: number;
toExpPos?: number;
minE?: number;
maxE?: number;
crypto?: boolean;
modulo?: Modulo;
defaults?: boolean;
}
}
export declare class Decimal {
readonly d: number[];
readonly e: number;
readonly s: number;
constructor(n: Decimal.Value);
absoluteValue(): Decimal;
abs(): Decimal;
ceil(): Decimal;
clampedTo(min: Decimal.Value, max: Decimal.Value): Decimal;
clamp(min: Decimal.Value, max: Decimal.Value): Decimal;
comparedTo(n: Decimal.Value): number;
cmp(n: Decimal.Value): number;
cosine(): Decimal;
cos(): Decimal;
cubeRoot(): Decimal;
cbrt(): Decimal;
decimalPlaces(): number;
dp(): number;
dividedBy(n: Decimal.Value): Decimal;
div(n: Decimal.Value): Decimal;
dividedToIntegerBy(n: Decimal.Value): Decimal;
divToInt(n: Decimal.Value): Decimal;
equals(n: Decimal.Value): boolean;
eq(n: Decimal.Value): boolean;
floor(): Decimal;
greaterThan(n: Decimal.Value): boolean;
gt(n: Decimal.Value): boolean;
greaterThanOrEqualTo(n: Decimal.Value): boolean;
gte(n: Decimal.Value): boolean;
hyperbolicCosine(): Decimal;
cosh(): Decimal;
hyperbolicSine(): Decimal;
sinh(): Decimal;
hyperbolicTangent(): Decimal;
tanh(): Decimal;
inverseCosine(): Decimal;
acos(): Decimal;
inverseHyperbolicCosine(): Decimal;
acosh(): Decimal;
inverseHyperbolicSine(): Decimal;
asinh(): Decimal;
inverseHyperbolicTangent(): Decimal;
atanh(): Decimal;
inverseSine(): Decimal;
asin(): Decimal;
inverseTangent(): Decimal;
atan(): Decimal;
isFinite(): boolean;
isInteger(): boolean;
isInt(): boolean;
isNaN(): boolean;
isNegative(): boolean;
isNeg(): boolean;
isPositive(): boolean;
isPos(): boolean;
isZero(): boolean;
lessThan(n: Decimal.Value): boolean;
lt(n: Decimal.Value): boolean;
lessThanOrEqualTo(n: Decimal.Value): boolean;
lte(n: Decimal.Value): boolean;
logarithm(n?: Decimal.Value): Decimal;
log(n?: Decimal.Value): Decimal;
minus(n: Decimal.Value): Decimal;
sub(n: Decimal.Value): Decimal;
modulo(n: Decimal.Value): Decimal;
mod(n: Decimal.Value): Decimal;
naturalExponential(): Decimal;
exp(): Decimal;
naturalLogarithm(): Decimal;
ln(): Decimal;
negated(): Decimal;
neg(): Decimal;
plus(n: Decimal.Value): Decimal;
add(n: Decimal.Value): Decimal;
precision(includeZeros?: boolean): number;
sd(includeZeros?: boolean): number;
round(): Decimal;
sine() : Decimal;
sin() : Decimal;
squareRoot(): Decimal;
sqrt(): Decimal;
tangent() : Decimal;
tan() : Decimal;
times(n: Decimal.Value): Decimal;
mul(n: Decimal.Value) : Decimal;
toBinary(significantDigits?: number): string;
toBinary(significantDigits: number, rounding: Decimal.Rounding): string;
toDecimalPlaces(decimalPlaces?: number): Decimal;
toDecimalPlaces(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
toDP(decimalPlaces?: number): Decimal;
toDP(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
toExponential(decimalPlaces?: number): string;
toExponential(decimalPlaces: number, rounding: Decimal.Rounding): string;
toFixed(decimalPlaces?: number): string;
toFixed(decimalPlaces: number, rounding: Decimal.Rounding): string;
toFraction(max_denominator?: Decimal.Value): Decimal[];
toHexadecimal(significantDigits?: number): string;
toHexadecimal(significantDigits: number, rounding: Decimal.Rounding): string;
toHex(significantDigits?: number): string;
toHex(significantDigits: number, rounding?: Decimal.Rounding): string;
toJSON(): string;
toNearest(n: Decimal.Value, rounding?: Decimal.Rounding): Decimal;
toNumber(): number;
toOctal(significantDigits?: number): string;
toOctal(significantDigits: number, rounding: Decimal.Rounding): string;
toPower(n: Decimal.Value): Decimal;
pow(n: Decimal.Value): Decimal;
toPrecision(significantDigits?: number): string;
toPrecision(significantDigits: number, rounding: Decimal.Rounding): string;
toSignificantDigits(significantDigits?: number): Decimal;
toSignificantDigits(significantDigits: number, rounding: Decimal.Rounding): Decimal;
toSD(significantDigits?: number): Decimal;
toSD(significantDigits: number, rounding: Decimal.Rounding): Decimal;
toString(): string;
truncated(): Decimal;
trunc(): Decimal;
valueOf(): string;
static abs(n: Decimal.Value): Decimal;
static acos(n: Decimal.Value): Decimal;
static acosh(n: Decimal.Value): Decimal;
static add(x: Decimal.Value, y: Decimal.Value): Decimal;
static asin(n: Decimal.Value): Decimal;
static asinh(n: Decimal.Value): Decimal;
static atan(n: Decimal.Value): Decimal;
static atanh(n: Decimal.Value): Decimal;
static atan2(y: Decimal.Value, x: Decimal.Value): Decimal;
static cbrt(n: Decimal.Value): Decimal;
static ceil(n: Decimal.Value): Decimal;
static clamp(n: Decimal.Value, min: Decimal.Value, max: Decimal.Value): Decimal;
static clone(object?: Decimal.Config): Decimal.Constructor;
static config(object: Decimal.Config): Decimal.Constructor;
static cos(n: Decimal.Value): Decimal;
static cosh(n: Decimal.Value): Decimal;
static div(x: Decimal.Value, y: Decimal.Value): Decimal;
static exp(n: Decimal.Value): Decimal;
static floor(n: Decimal.Value): Decimal;
static hypot(...n: Decimal.Value[]): Decimal;
static isDecimal(object: any): object is Decimal;
static ln(n: Decimal.Value): Decimal;
static log(n: Decimal.Value, base?: Decimal.Value): Decimal;
static log2(n: Decimal.Value): Decimal;
static log10(n: Decimal.Value): Decimal;
static max(...n: Decimal.Value[]): Decimal;
static min(...n: Decimal.Value[]): Decimal;
static mod(x: Decimal.Value, y: Decimal.Value): Decimal;
static mul(x: Decimal.Value, y: Decimal.Value): Decimal;
static noConflict(): Decimal.Constructor; // Browser only
static pow(base: Decimal.Value, exponent: Decimal.Value): Decimal;
static random(significantDigits?: number): Decimal;
static round(n: Decimal.Value): Decimal;
static set(object: Decimal.Config): Decimal.Constructor;
static sign(n: Decimal.Value): number;
static sin(n: Decimal.Value): Decimal;
static sinh(n: Decimal.Value): Decimal;
static sqrt(n: Decimal.Value): Decimal;
static sub(x: Decimal.Value, y: Decimal.Value): Decimal;
static sum(...n: Decimal.Value[]): Decimal;
static tan(n: Decimal.Value): Decimal;
static tanh(n: Decimal.Value): Decimal;
static trunc(n: Decimal.Value): Decimal;
static readonly default?: Decimal.Constructor;
static readonly Decimal?: Decimal.Constructor;
static readonly precision: number;
static readonly rounding: Decimal.Rounding;
static readonly toExpNeg: number;
static readonly toExpPos: number;
static readonly minE: number;
static readonly maxE: number;
static readonly crypto: boolean;
static readonly modulo: Decimal.Modulo;
static readonly ROUND_UP: 0;
static readonly ROUND_DOWN: 1;
static readonly ROUND_CEIL: 2;
static readonly ROUND_FLOOR: 3;
static readonly ROUND_HALF_UP: 4;
static readonly ROUND_HALF_DOWN: 5;
static readonly ROUND_HALF_EVEN: 6;
static readonly ROUND_HALF_CEIL: 7;
static readonly ROUND_HALF_FLOOR: 8;
static readonly EUCLID: 9;
}
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
[K in keyof A]: Exact<A[K], W[K]>;
} : W) : never) | (A extends Narrowable ? A : never);
export declare function getRuntime(): GetRuntimeOutput;
declare type GetRuntimeOutput = {
id: RuntimeName;
prettyName: string;
isEdge: boolean;
};
declare class JsonNull extends NullTypesEnumValue {
#private;
}
/**
* Generates more strict variant of an enum which, unlike regular enum,
* throws on non-existing property access. This can be useful in following situations:
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
* - enum values are generated dynamically from DMMF.
*
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
* will result in `undefined` value being used, which will be accepted. Using strict enum
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
*
* Note: if you need to check for existence of a value in the enum you can still use either
* `in` operator or `hasOwnProperty` function.
*
* @param definition
* @returns
*/
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
declare type Narrowable = string | number | bigint | boolean | [];
declare class NullTypesEnumValue extends ObjectEnumValue {
_getNamespace(): string;
}
/**
* Base class for unique values of object-valued enums.
*/
declare abstract class ObjectEnumValue {
constructor(arg?: symbol);
abstract _getNamespace(): string;
_getName(): string;
toString(): string;
}
export declare const objectEnumValues: {
classes: {
DbNull: typeof DbNull;
JsonNull: typeof JsonNull;
AnyNull: typeof AnyNull;
};
instances: {
DbNull: DbNull;
JsonNull: JsonNull;
AnyNull: AnyNull;
};
};
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
declare namespace Public {
export {
validator
}
}
export { Public }
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
export { }
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-199
View File
@@ -1,199 +0,0 @@
// This is your Prisma schema file,
// learn more about the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
output = "../generated/prisma/client"
binaryTargets = ["native", "debian-openssl-1.1.x"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// Enum para los roles de usuario en la aplicación
enum UserRole {
ADMIN // Administrador con control total
MANAGER // Gerente con permisos de gestión
STAFF // Personal con permisos operativos
VIEWER // Solo lectura
}
// Modelo para los usuarios de la aplicación
model User {
id String @id @default(uuid())
username String @unique // Nombre de usuario único para login
name String // Nombre completo del usuario
email String @unique // Email único para el usuario
password String // Contraseña (debe ser hasheada)
role UserRole @default(STAFF) // Rol del usuario, por defecto STAFF
isActive Boolean @default(true) // Indica si la cuenta está activa
createdAt DateTime @default(now()) // Fecha de creación del registro
updatedAt DateTime @updatedAt // Fecha de última actualización del registro
movements Movement[] // Relación con los movimientos realizados por este usuario
assignments Assignment[] // Relación con las asignaciones creadas por este usuario
}
// Enum para los departamentos de los destinatarios
enum RecipientDepartment {
IT
ENGINEERING // Cambiado de ING a ENGINEERING para mayor claridad
LOGISTICS // Cambiado de LNG a LOGISTICS
TRAFFIC
DRIVER
ADMINISTRATION
SALES
OTHER
}
// Modelo para los destinatarios de los ítems (personas o áreas a las que se asignan cosas)
model Recipient {
id String @id @default(uuid())
username String @unique // Nombre de usuario o identificador único del destinatario
firstName String // Nombre del destinatario
lastName String // Apellido del destinatario
department RecipientDepartment? // Departamento del destinatario, usando el enum
email String? @unique // Email opcional del destinatario
phone String? // Teléfono opcional del destinatario
isActive Boolean @default(true) // Indica si el destinatario está activo
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt // Fecha de última actualización del registro
assignments Assignment[] // Relación con las asignaciones de ítems a este destinatario
movements Movement[] // Relación con los movimientos de ítems relacionados con este destinatario
@@index([lastName, firstName]) // Índice para búsquedas por nombre y apellido
@@index([department]) // Nuevo índice para búsquedas por departamento
}
// Modelo para las categorías de ítems
model Category {
id String @id @default(uuid())
name String @unique // Nombre único de la categoría
description String? // Descripción opcional de la categoría
isActive Boolean @default(true) // Indica si la categoría está activa
items Item[] // Relación con los productos (ítems genéricos) de esta categoría
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt // Fecha de última actualización del registro
@@index([name]) // Índice para búsquedas por nombre de categoría
}
// Enum para el estado de un ítem (ya sea Item o Asset)
enum ItemStatus {
AVAILABLE // Disponible en stock
ASSIGNED // Asignado a un destinatario
RESERVED // Reservado para una asignación futura
IN_REPAIR // En reparación
BROKEN // Averiado e inutilizable
STOLEN // Robado
DISPOSED // Dado de baja/eliminado del inventario
}
// Modelo para ítems genéricos (sin número de serie individual, se gestionan por cantidad)
model Item {
id String @id @default(uuid())
name String // Nombre del producto
description String? // Descripción opcional del producto
categoryId String // ID de la categoría a la que pertenece el producto
category Category @relation(fields: [categoryId], references: [id]) // Relación con la categoría
stock Int @default(0) // Cantidad actual en stock
minStock Int? // Nivel mínimo de stock para alertas
maxStock Int? // Nivel máximo de stock deseado
createdAt DateTime @default(now()) // Fecha de creación del registro
updatedAt DateTime @updatedAt // Fecha de última actualización del registro
deletedAt DateTime? // Fecha de eliminación del registro
movements Movement[] // Relación con los movimientos de este producto
assignments Assignment[] // Relación con las asignaciones de este producto (por cantidad)
assets Asset[] // Relación con los activos serializados que son de este tipo de producto
@@index([categoryId]) // Índice para búsquedas por categoría
@@index([name]) // Índice para búsquedas por nombre de producto
}
// Modelo para ítems serializados (activos individuales con número de serie único)
model Asset {
id String @id @default(uuid())
itemId String? // ID del producto genérico al que pertenece este activo (opcional)
item Item? @relation(fields: [itemId], references: [id]) // Relación con el producto genérico
serialNumber String @unique // Número de serie único y obligatorio para el activo
deliveryNote String? // Número de albarán de entrega (opcional, puede ser para la entrada inicial)
status ItemStatus @default(AVAILABLE) // Estado actual del activo individual
notes String? // Notas adicionales sobre el activo
createdAt DateTime @default(now()) // Fecha de creación del registro
updatedAt DateTime @updatedAt // Fecha de última actualización del registro
movements Movement[] // Relación con los movimientos de este activo
assignment Assignment? // Relación 1:1 con la asignación activa de este activo (un activo solo puede estar en una asignación activa)
@@index([serialNumber]) // Índice para búsquedas por número de serie
@@index([itemId]) // Índice para búsquedas por producto asociado
@@index([status]) // Índice para búsquedas por estado del activo
}
// Modelo para registrar las asignaciones de ítems a destinatarios
model Assignment {
id String @id @default(uuid())
quantity Int? // Cantidad asignada (solo si es un Item, para Asset sería 1 implícitamente)
notes String? // Notas sobre la asignación
itemId String? // ID del producto genérico asignado (si no es un activo serializado)
item Item? @relation(fields: [itemId], references: [id]) // Relación con el producto
assetId String? @unique // ID del activo serializado asignado (si es un activo, debe ser único para asignaciones activas)
asset Asset? @relation(fields: [assetId], references: [id]) // Relación con el activo
recipientId String? // ID del destinatario de la asignación
recipient Recipient? @relation(fields: [recipientId], references: [id], onDelete: Cascade, onUpdate: Cascade) // Relación con el destinatario
assignmentDate DateTime @default(now()) // Fecha en que se realizó la asignación
returnDate DateTime? // Fecha en que se devolvió la asignación (si aplica)
createdBy String // ID del usuario que realizó la asignación
createdUser User @relation(fields: [createdBy], references: [id]) // Relación con el usuario que creó la asignación
createdAt DateTime @default(now()) // Fecha de creación del registro de asignación
updatedAt DateTime @updatedAt // Fecha de última actualización del registro
movement Movement[] // Relación con los movimientos generados por esta asignación (ej. OUT, RETURN)
// Índices para mejorar el rendimiento de las consultas
@@index([itemId])
@@index([assetId])
@@index([recipientId])
@@index([createdBy])
}
// Enum para los tipos de movimiento de stock
enum MovementType {
IN // Entrada de stock (compra, fabricación)
OUT // Salida de stock (venta, consumo)
ASSIGNMENT // Movimiento asociado a una asignación
RETURN // Movimiento asociado a la devolución de una asignación
ADJUSTMENT // Ajuste de inventario (corrección, pérdida)
DELETED // Eliminación de un ítem
}
// Modelo para el histórico de todos los movimientos de stock/activos
model Movement {
id String @id @default(uuid())
type MovementType @default(IN) // Tipo de movimiento
quantity Int // Cantidad del movimiento (para Item, para Asset sería 1)
details String? // Detalles adicionales del movimiento
notes String? // Notas sobre el movimiento
itemId String? // ID del producto afectado (si es un Item)
item Item? @relation(fields: [itemId], references: [id]) // Relación con el producto
assetId String? // ID del activo afectado (si es un Asset)
asset Asset? @relation(fields: [assetId], references: [id]) // Relación con el activo
previousStock Int? // Stock antes de este movimiento (solo para Item)
newStock Int? // Stock después de este movimiento (solo para Item)
recipientId String? // ID del destinatario relacionado con el movimiento (si aplica)
recipient Recipient? @relation(fields: [recipientId], references: [id]) // Relación con el destinatario
assignmentId String? // ID de la asignación relacionada (si el movimiento es ASSIGNMENT o RETURN)
assignment Assignment? @relation(fields: [assignmentId], references: [id]) // Relación con la asignación
userId String // ID del usuario que realizó el movimiento
user User @relation(fields: [userId], references: [id]) // Relación con el usuario
createdAt DateTime @default(now()) // Fecha en que se registró el movimiento
// Índices para mejorar el rendimiento de las consultas
@@index([itemId])
@@index([assetId])
@@index([recipientId])
@@index([type])
@@index([userId])
}
-1
View File
@@ -1 +0,0 @@
export * from "./index"
-301
View File
@@ -1,301 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
Object.defineProperty(exports, "__esModule", { value: true });
const {
Decimal,
objectEnumValues,
makeStrictEnum,
Public,
getRuntime,
skip
} = require('./runtime/index-browser.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.10.1
* Query Engine version: 9b628578b3b7cae625e8c927178f15a170e74a9c
*/
Prisma.prismaVersion = {
client: "6.10.1",
engine: "9b628578b3b7cae625e8c927178f15a170e74a9c"
}
Prisma.PrismaClientKnownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)};
Prisma.PrismaClientUnknownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientRustPanicError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientInitializationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientValidationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.empty = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.join = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.raw = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.defineExtension = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = objectEnumValues.instances.DbNull
Prisma.JsonNull = objectEnumValues.instances.JsonNull
Prisma.AnyNull = objectEnumValues.instances.AnyNull
Prisma.NullTypes = {
DbNull: objectEnumValues.classes.DbNull,
JsonNull: objectEnumValues.classes.JsonNull,
AnyNull: objectEnumValues.classes.AnyNull
}
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
});
exports.Prisma.UserScalarFieldEnum = {
id: 'id',
username: 'username',
name: 'name',
email: 'email',
password: 'password',
role: 'role',
isActive: 'isActive',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.RecipientScalarFieldEnum = {
id: 'id',
username: 'username',
firstName: 'firstName',
lastName: 'lastName',
department: 'department',
email: 'email',
phone: 'phone',
isActive: 'isActive',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.CategoryScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
isActive: 'isActive',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.ItemScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
categoryId: 'categoryId',
stock: 'stock',
minStock: 'minStock',
maxStock: 'maxStock',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
deletedAt: 'deletedAt'
};
exports.Prisma.AssetScalarFieldEnum = {
id: 'id',
itemId: 'itemId',
serialNumber: 'serialNumber',
deliveryNote: 'deliveryNote',
status: 'status',
notes: 'notes',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.AssignmentScalarFieldEnum = {
id: 'id',
quantity: 'quantity',
notes: 'notes',
itemId: 'itemId',
assetId: 'assetId',
recipientId: 'recipientId',
assignmentDate: 'assignmentDate',
returnDate: 'returnDate',
createdBy: 'createdBy',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.MovementScalarFieldEnum = {
id: 'id',
type: 'type',
quantity: 'quantity',
details: 'details',
notes: 'notes',
itemId: 'itemId',
assetId: 'assetId',
previousStock: 'previousStock',
newStock: 'newStock',
recipientId: 'recipientId',
assignmentId: 'assignmentId',
userId: 'userId',
createdAt: 'createdAt'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.QueryMode = {
default: 'default',
insensitive: 'insensitive'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.UserRole = exports.$Enums.UserRole = {
ADMIN: 'ADMIN',
MANAGER: 'MANAGER',
STAFF: 'STAFF',
VIEWER: 'VIEWER'
};
exports.RecipientDepartment = exports.$Enums.RecipientDepartment = {
IT: 'IT',
ENGINEERING: 'ENGINEERING',
LOGISTICS: 'LOGISTICS',
TRAFFIC: 'TRAFFIC',
DRIVER: 'DRIVER',
ADMINISTRATION: 'ADMINISTRATION',
SALES: 'SALES',
OTHER: 'OTHER'
};
exports.ItemStatus = exports.$Enums.ItemStatus = {
AVAILABLE: 'AVAILABLE',
ASSIGNED: 'ASSIGNED',
RESERVED: 'RESERVED',
IN_REPAIR: 'IN_REPAIR',
BROKEN: 'BROKEN',
STOLEN: 'STOLEN',
DISPOSED: 'DISPOSED'
};
exports.MovementType = exports.$Enums.MovementType = {
IN: 'IN',
OUT: 'OUT',
ASSIGNMENT: 'ASSIGNMENT',
RETURN: 'RETURN',
ADJUSTMENT: 'ADJUSTMENT',
DELETED: 'DELETED'
};
exports.Prisma.ModelName = {
User: 'User',
Recipient: 'Recipient',
Category: 'Category',
Item: 'Item',
Asset: 'Asset',
Assignment: 'Assignment',
Movement: 'Movement'
};
/**
* This is a stub Prisma Client that will error at runtime if called.
*/
class PrismaClient {
constructor() {
return new Proxy(this, {
get(target, prop) {
let message
const runtime = getRuntime()
if (runtime.isEdge) {
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
- Use Prisma Accelerate: https://pris.ly/d/accelerate
- Use Driver Adapters: https://pris.ly/d/driver-adapters
`;
} else {
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
}
message += `
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
throw new Error(message)
}
})
}
}
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)
-198
View File
@@ -1,198 +0,0 @@
"use server"
import { revalidatePath } from "next/cache"
import {
createAssignment,
updateAssignment,
} from "@/lib/actions/assignament.actions"
import {
type CreateAssetFormType,
createAssetSchema,
type UpdateAssetFormType,
updateAssetSchema,
} from "@/lib/schemas/asset.schemas"
import { AssetService } from "@/services/asset.service"
import { AssignmentService } from "@/services/assignment.service"
import { ItemService } from "@/services/item.service"
import { MovementService } from "@/services/movement.service"
export async function createAssetAction(formData: CreateAssetFormType) {
try {
const validatedFields = createAssetSchema.safeParse(formData)
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
const { itemId, serialNumber, deliveryNote, status, notes, recipientId } =
validatedFields.data
const item = await ItemService.findByIdWithCategory(itemId)
if (!item) {
return {
success: false,
errors: { itemId: ["Item not found"] },
}
}
const existentAsset = await AssetService.findBySerialNumber(serialNumber)
if (existentAsset) {
return {
success: false,
errors: {
serialNumber: ["This serial number already exists"],
},
}
}
const newAsset = await AssetService.create({
item: { connect: { id: itemId } },
serialNumber,
deliveryNote,
status,
notes,
})
await MovementService.create({
itemId,
assetId: newAsset?.id,
quantity: 1,
type: status === "ASSIGNED" ? "ASSIGNMENT" : "IN",
})
if (status === "AVAILABLE") {
await ItemService.update(itemId, {
stock: item.stock + 1,
name: item.name,
category: { connect: { id: item.category?.id } },
})
}
if (status === "ASSIGNED" && recipientId) {
await AssignmentService.create({
notes: "",
itemId,
assetId: newAsset?.id,
quantity: 1,
recipientId,
assignmentDate: new Date(),
})
}
revalidatePath("/inventory/assets")
return {
success: true,
message: "Asset created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
message: "Error creating asset",
}
}
}
export async function updateAssetAction(formData: UpdateAssetFormType) {
const validatedFields = updateAssetSchema.safeParse(formData)
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
const { id, itemId, serialNumber, deliveryNote, status, notes, recipientId } =
validatedFields.data
try {
const item = await ItemService.findByIdWithCategory(itemId)
if (!item) {
return {
success: false,
errors: { itemId: ["Item not found"] },
}
}
const existentAsset = await AssetService.findBySerialNumber(serialNumber)
if (
existentAsset &&
id !== existentAsset.id &&
existentAsset.serialNumber === serialNumber
) {
return {
success: false,
errors: {
serialNumber: ["This serial number already exists"],
},
}
}
await AssetService.update(id, {
item: { connect: { id: itemId } },
serialNumber,
deliveryNote,
status,
notes,
})
await MovementService.create({
itemId,
assetId: id,
quantity: 1,
type: status === "ASSIGNED" ? "ASSIGNMENT" : "IN",
})
if (status === "AVAILABLE") {
await ItemService.update(itemId, {
stock: item.stock + 1,
name: item.name,
category: { connect: { id: item.category?.id } },
})
}
if (status === "ASSIGNED" && recipientId) {
if (!recipientId) {
return {
success: false,
errors: { recipientId: ["Recipient is required for assignment"] },
}
}
if (existentAsset?.assignment) {
await updateAssignment({
id: existentAsset.assignment.id,
recipientId,
quantity: 1,
})
} else {
await createAssignment({
itemId,
assetId: id,
quantity: 1,
recipientId,
assignmentDate: new Date(),
})
}
}
return {
success: true,
message: "Asset updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
message: "Error updating asset",
}
}
}
-298
View File
@@ -1,298 +0,0 @@
"use server"
import { revalidatePath } from "next/cache"
import prisma from "@/lib/prisma"
import { AssetService } from "@/services/asset.service"
import { AssignmentService } from "@/services/assignment.service"
import { getAuthenticatedUserId } from "@/services/auth.service"
import { ItemService } from "@/services/item.service"
import { MovementService } from "@/services/movement.service"
import {
assignmentSchema,
type CreateAssignmentFormType,
type ReturnAssignmentFormType,
type UpdateAssignmentFormType,
updateAssignmentSchema,
} from "../schemas/assignment.schemas"
export async function getAssignments() {
return await AssignmentService.findAllWithRecipient()
}
export async function getAssignmentById(id: string) {
return await AssignmentService.findById(id)
}
export async function createAssignment(formData: CreateAssignmentFormType) {
const createdBy = await getAuthenticatedUserId()
const validatedFields = assignmentSchema.safeParse({
...formData,
createdBy,
})
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
const { itemId, assetId, quantity, recipientId } = validatedFields.data
if (!itemId || !recipientId || quantity <= 0) return
try {
const item = await ItemService.findById(itemId)
if (!item) {
return {
success: false,
errors: { itemId: ["Item not found"] },
}
}
if (item.stock < quantity) {
return {
success: false,
errors: { quantity: ["Item does not have enough stock"] },
}
}
if (assetId) {
const asset = await AssetService.findById(assetId)
if (!asset) {
return {
success: false,
errors: { assetId: ["Asset not found"] },
}
}
if (asset.itemId !== item.id) {
return {
success: false,
errors: { assetId: ["Asset does not belong to item"] },
}
}
}
await ItemService.updateStock(itemId, -quantity)
if (assetId && recipientId) {
await AssetService.update(assetId, {
status: "ASSIGNED",
})
}
const createdAssignment = await AssignmentService.create({
itemId,
assetId: assetId || undefined,
quantity,
recipientId,
})
await MovementService.create({
itemId,
assetId: assetId || undefined,
quantity,
type: "ASSIGNMENT",
recipientId,
assignmentId: createdAssignment.id,
})
revalidatePath("/assignments")
return {
success: true,
message: "Assignment created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
errors: { error: ["Error creating assignment"] },
}
}
}
export async function updateAssignment(formData: UpdateAssignmentFormType) {
const validatedFields = updateAssignmentSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const { id, recipientId, itemId, assetId, quantity } = validatedFields.data
const assignment = await prisma.assignment.findUnique({
where: { id },
})
if (!assignment) {
return {
success: false,
errors: { id: ["Assignment not found"] },
}
}
if (itemId) {
const item = await prisma.item.findUnique({
where: { id: itemId },
})
if (!item) {
return {
success: false,
errors: { itemId: ["Item not found"] },
}
}
// if (item.stock < quantity) {
// return {
// success: false,
// errors: { quantity: ["Item does not have enough stock"] },
// }
// }
}
if (assetId) {
const asset = await prisma.asset.findUnique({
where: { id: assetId },
})
if (!asset) {
return {
success: false,
errors: { assetId: ["Asset not found"] },
}
}
if (asset.itemId !== itemId) {
await prisma.asset.update({
where: { id: assetId },
data: { itemId },
})
return {
success: false,
errors: { assetId: ["Asset does not belong to item"] },
}
}
}
const createdBy = await getAuthenticatedUserId()
if (assignment.recipientId !== recipientId) {
await MovementService.create({
type: "RETURN",
quantity: assignment.quantity || 1,
itemId: assignment.itemId || undefined,
assetId: assignment.assetId || undefined,
recipientId: assignment.recipientId || undefined,
assignmentId: id,
})
await MovementService.create({
type: "ASSIGNMENT",
quantity,
itemId,
assetId: assetId || undefined,
recipientId,
assignmentId: id,
})
await prisma.assignment.update({
data: {
...validatedFields.data,
createdBy,
recipientId,
itemId,
assetId,
quantity,
returnDate: null,
},
where: { id },
})
} else {
await prisma.assignment.update({
where: { id },
data: {
...validatedFields.data,
itemId,
assetId,
quantity,
returnDate: new Date(),
},
})
}
revalidatePath("/assignments")
return {
success: true,
message: "Assignment updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
errors: { error: ["Error creating assignment"] },
}
}
}
export async function returnAssignment(formData: ReturnAssignmentFormType) {
const { id } = formData
const assignment = await AssignmentService.findById(id)
if (!assignment) {
return {
success: false,
errors: { id: ["Assignment not found"] },
}
}
if (assignment.returnDate) {
return {
success: false,
errors: { id: ["Assignment already returned"] },
}
}
if (assignment.itemId && assignment.quantity) {
await ItemService.update(assignment.itemId, {
stock: { increment: assignment.quantity || 1 },
})
}
if (assignment.assetId) {
await AssetService.update(assignment.assetId, {
status: "AVAILABLE",
})
}
await AssignmentService.delete(id)
await MovementService.create({
type: "RETURN",
quantity: assignment.quantity || 1,
itemId: assignment.itemId || undefined,
assetId: assignment.assetId || undefined,
assignmentId: id,
})
revalidatePath("/assignments")
return {
success: true,
message: "Assignment returned successfully",
}
}
-182
View File
@@ -1,182 +0,0 @@
"use server"
import { revalidatePath } from "next/cache"
import prisma from "@/lib/prisma"
import {
type CreateItemFormType,
createItemSchema,
type UpdateItemFormType,
updateItemSchema,
} from "@/lib/schemas/item.schemas"
import { getAuthenticatedUserId } from "@/services/auth.service"
import { ItemService } from "@/services/item.service"
export async function createItemAction(formData: CreateItemFormType) {
const validatedFields = createItemSchema.safeParse(formData)
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
const { name, categoryId, stock } = validatedFields.data
if (stock < 0) {
return {
success: false,
errors: {
stock: ["Stock cannot be negative"],
},
}
}
try {
const existingItem = await ItemService.findByName(name)
if (existingItem) {
return {
success: false,
errors: {
name: ["An item with this name already exists"],
},
}
}
const item = await ItemService.create({
name,
category: { connect: { id: categoryId } },
stock: stock || 0,
})
if (stock > 0) {
await prisma.movement.create({
data: {
type: "IN",
itemId: item.id,
userId: await getAuthenticatedUserId(),
quantity: stock,
},
})
}
revalidatePath("/inventory/items")
return {
success: true,
message: "Item created successfully!",
}
} catch (error) {
console.error("Database error:", error)
return {
error: "Error creating item",
}
}
}
export async function updateItemAction(formData: UpdateItemFormType) {
const validatedFields = updateItemSchema.safeParse(formData)
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
const { id, stock, name, categoryId } = validatedFields.data
try {
const existingItem = await ItemService.findByIdWithAssetCount(id)
if (!existingItem) {
return {
success: false,
errors: {
id: ["Item not found"],
},
}
}
const existingItemByName = await ItemService.findByName(name)
if (existingItemByName && existingItemByName.id !== id) {
return {
success: false,
errors: { name: ["An item with this name already exists"] },
}
}
await ItemService.update(id, {
stock: stock || existingItem.stock,
name: name || existingItem.name,
category: { connect: { id: categoryId } },
})
const quantity = stock - existingItem.stock
if (stock && stock > existingItem.stock) {
await prisma.movement.create({
data: {
type: "IN",
itemId: id,
quantity,
userId: await getAuthenticatedUserId(),
},
})
}
return {
success: true,
message: "Item updated successfully!",
}
} catch (error) {
console.error("Database error:", error)
return {
error: "Failed to update item",
}
}
}
export async function deleteItemAction(formData: FormData) {
const { id } = Object.fromEntries(formData) as { id: string }
try {
const existingItem = await ItemService.findByIdWithAssetCount(id)
if (!existingItem) {
return {
success: false,
errors: { id: ["Item not found"] },
}
}
if (existingItem._count.assets > 0) {
return {
success: false,
errors: { id: ["Item has assets, you cannot delete it"] },
}
}
if (existingItem.stock > 0) {
return {
success: false,
errors: { id: ["Item has stock, you cannot delete it"] },
}
}
await ItemService.delete(id)
revalidatePath("/inventory/items")
return {
success: true,
message: "Item deleted successfully!",
}
} catch (error) {
console.error("Database error:", error)
return {
error: "Failed to delete item",
}
}
}
-15
View File
@@ -1,15 +0,0 @@
import {
type CreateMovementFormType,
createMovementSchema,
} from "../schemas/movement.schemas"
export async function createMovementAction(data: CreateMovementFormType) {
const validatedFields = createMovementSchema.safeParse(data)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
}
-138
View File
@@ -1,138 +0,0 @@
"use server"
import { revalidatePath } from "next/cache"
import prisma from "@/lib/prisma"
import {
type CreateRecipientFormType,
createRecipientSchema,
type UpdateRecipientFormType,
updateRecipientSchema,
} from "@/lib/schemas/recipients.schemas"
import { RecipientService } from "@/services/recipient.service"
export async function createNewRecipient(formData: CreateRecipientFormType) {
const validatedFields = createRecipientSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
const { username, firstName, lastName, department, email, phone } =
validatedFields.data
try {
const existingRecipientUsername =
await RecipientService.findByUsername(username)
if (existingRecipientUsername) {
return {
success: false,
errors: {
username: ["Username already exists"],
},
}
}
const existingRecipientEmail = await RecipientService.findByEmail(
email || "",
)
if (existingRecipientEmail) {
return {
success: false,
errors: {
email: ["Email already exists"],
},
}
}
await RecipientService.create({
username: username || (firstName[0] + lastName).toLowerCase(),
firstName,
lastName,
department: department,
email: email || null,
phone: phone || null,
})
revalidatePath("/recipients")
return {
success: true,
message: "Recipient created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
message: "Failed to create recipient",
}
}
}
export async function updateRecipient(formData: UpdateRecipientFormType) {
const validatedFields = updateRecipientSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
const { id, username, firstName, lastName, department, email, phone } =
validatedFields.data
try {
const existingRecipient = await RecipientService.findByUsername(username)
if (existingRecipient && existingRecipient.id !== id) {
return {
success: false,
errors: {
username: ["Username already exists"],
},
}
}
const existingRecipientEmail = await RecipientService.findByEmail(
email || "",
)
if (existingRecipientEmail && existingRecipientEmail.id !== id) {
return {
success: false,
errors: {
email: ["Email already exists"],
},
}
}
await prisma.recipient.update({
where: { id },
data: {
username,
firstName,
lastName,
department: department,
email: email || null,
phone: phone || null,
},
})
revalidatePath("/recipients")
return {
success: true,
message: "Recipient updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
message: "Failed to update recipient",
}
}
}
+5 -3
View File
@@ -4,12 +4,11 @@ import { ZodError } from "zod"
import type { UserRole } from "@/generated/prisma/client"
import { SIGN_IN_URL, TOKEN_EXPIRATION_SECONDS } from "@/lib/constants"
import { signInSchema } from "@/lib/schemas/auth.schemas"
import { verifyPassword } from "@/lib/security"
import { signInSchema } from "@/schemas/auth.schema"
import { getUserByUsername } from "@/services/user.service"
declare module "next-auth" {
// eslint-disable-next-line no-unused-vars
interface Session {
user: {
id: string
@@ -17,7 +16,6 @@ declare module "next-auth" {
} & DefaultSession["user"]
}
// eslint-disable-next-line no-unused-vars
interface User {
role: UserRole
}
@@ -46,6 +44,10 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
throw new Error("Invalid username or password")
}
if (!user.isActive) {
throw new Error("Invalid username or password")
}
if (!(await verifyPassword(data.password, user.password)))
throw new Error("Invalid username or password")
+22 -1
View File
@@ -3,7 +3,28 @@ const isDemo = process.env.DEMO_MODE === "true"
export const ENVIRONMENT = isDemo
? "demo"
: process.env.NODE_ENV || "development"
export const SIGN_IN_URL = "/login"
export const TOKEN_EXPIRATION_SECONDS = 60 * 60 * 2 // 2 hour
export const RECIPIENT_DEPARTMENTS = {
IT: "IT",
ENGINEERING: "ENGINEERING",
LOGISTICS: "LOGISTICS",
TRAFFIC: "TRAFFIC",
DRIVER: "DRIVER",
ADMINISTRATION: "ADMINISTRATION",
SALES: "SALES",
OTHER: "OTHER",
} as const
export const ITEM_STATUS = {
AVAILABLE: "AVAILABLE",
ASSIGNED: "ASSIGNED",
RESERVED: "RESERVED",
IN_REPAIR: "IN_REPAIR",
BROKEN: "BROKEN",
STOLEN: "STOLEN",
DISPOSED: "DISPOSED",
} as const
+4 -5
View File
@@ -1,10 +1,9 @@
import type { Prisma } from "@/generated/prisma/client"
import type { PaginatedResult } from "@/lib/types"
import type { PaginatedResult } from "@/types"
//eslint-disable-next-line
type PaginationArgs<T> = {
model: any // prisma.<model>
where?: Prisma.Enumerable<any> // filtros
type PaginationArgs<_T> = {
model: any
where?: Prisma.Enumerable<any>
page?: number
pageSize?: number
orderBy?: any
+11 -5
View File
@@ -1,11 +1,17 @@
import { PrismaClient } from "@/generated/prisma/client"
import { PrismaPg } from "@prisma/adapter-pg"
import { PrismaClient } from "../generated/prisma/client"
const globalForPrisma = global as unknown as {
prisma: PrismaClient
const rawDatabaseUrl = process.env.DATABASE_URL
const databaseUrl = (rawDatabaseUrl ?? "").trim()
if (!databaseUrl) {
throw new Error("DATABASE_URL is required")
}
const prisma = globalForPrisma.prisma || new PrismaClient()
const adapter = new PrismaPg({
connectionString: databaseUrl,
})
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma
const prisma = new PrismaClient({ adapter })
export { prisma }
export default prisma
-35
View File
@@ -1,35 +0,0 @@
import { z } from "zod"
export const createItemSchema = z.object({
name: z.string().min(1, {
error: "Name is required"
}),
categoryId: z.string().min(1, {
error: "Category is required"
}),
stock: z.coerce
.number()
.int()
.nonnegative()
.min(0, {
error: "Stock is required"
}),
})
export type CreateItemFormType = z.infer<typeof createItemSchema>
export const updateItemSchema = createItemSchema.extend({
id: z.string().min(1, {
error: "Item is required"
}),
})
export type UpdateItemFormType = z.infer<typeof updateItemSchema>
export const getItemByIdSchema = z.object({
id: z.string().min(1, {
error: "Item is required"
}),
})
export type GetItemByIdFormType = z.infer<typeof getItemByIdSchema>
-24
View File
@@ -1,24 +0,0 @@
import type { Movement as PrismaMovement } from "@/generated/prisma/client"
import type { Asset } from "./asset"
import type { Item } from "./item"
import type { Recipient } from "./recipient"
export type Movement = PrismaMovement
export type MovementSummary = Pick<Movement, "id" | "type" | "quantity">
export type MovementWithItem = Movement & {
item: Item | null
}
export type MovementWithItemAndAsset = Movement & {
item: Item | null
asset: Asset | null
}
export type MovementWithRecipientItemAsset = Movement & {
recipient: Recipient | null
item: Item | null
asset: Asset | null
}
-199
View File
@@ -1,199 +0,0 @@
// This is your Prisma schema file,
// learn more about the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
output = "../generated/prisma/client"
binaryTargets = ["native", "debian-openssl-1.1.x"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// Enum para los roles de usuario en la aplicación
enum UserRole {
ADMIN // Administrador con control total
MANAGER // Gerente con permisos de gestión
STAFF // Personal con permisos operativos
VIEWER // Solo lectura
}
// Modelo para los usuarios de la aplicación
model User {
id String @id @default(uuid())
username String @unique // Nombre de usuario único para login
name String // Nombre completo del usuario
email String @unique // Email único para el usuario
password String // Contraseña (debe ser hasheada)
role UserRole @default(STAFF) // Rol del usuario, por defecto STAFF
isActive Boolean @default(true) // Indica si la cuenta está activa
createdAt DateTime @default(now()) // Fecha de creación del registro
updatedAt DateTime @updatedAt // Fecha de última actualización del registro
movements Movement[] // Relación con los movimientos realizados por este usuario
assignments Assignment[] // Relación con las asignaciones creadas por este usuario
}
// Enum para los departamentos de los destinatarios
enum RecipientDepartment {
IT
ENGINEERING // Cambiado de ING a ENGINEERING para mayor claridad
LOGISTICS // Cambiado de LNG a LOGISTICS
TRAFFIC
DRIVER
ADMINISTRATION
SALES
OTHER
}
// Modelo para los destinatarios de los ítems (personas o áreas a las que se asignan cosas)
model Recipient {
id String @id @default(uuid())
username String @unique // Nombre de usuario o identificador único del destinatario
firstName String // Nombre del destinatario
lastName String // Apellido del destinatario
department RecipientDepartment? // Departamento del destinatario, usando el enum
email String? @unique // Email opcional del destinatario
phone String? // Teléfono opcional del destinatario
isActive Boolean @default(true) // Indica si el destinatario está activo
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt // Fecha de última actualización del registro
assignments Assignment[] // Relación con las asignaciones de ítems a este destinatario
movements Movement[] // Relación con los movimientos de ítems relacionados con este destinatario
@@index([lastName, firstName]) // Índice para búsquedas por nombre y apellido
@@index([department]) // Nuevo índice para búsquedas por departamento
}
// Modelo para las categorías de ítems
model Category {
id String @id @default(uuid())
name String @unique // Nombre único de la categoría
description String? // Descripción opcional de la categoría
isActive Boolean @default(true) // Indica si la categoría está activa
items Item[] // Relación con los productos (ítems genéricos) de esta categoría
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt // Fecha de última actualización del registro
@@index([name]) // Índice para búsquedas por nombre de categoría
}
// Enum para el estado de un ítem (ya sea Item o Asset)
enum ItemStatus {
AVAILABLE // Disponible en stock
ASSIGNED // Asignado a un destinatario
RESERVED // Reservado para una asignación futura
IN_REPAIR // En reparación
BROKEN // Averiado e inutilizable
STOLEN // Robado
DISPOSED // Dado de baja/eliminado del inventario
}
// Modelo para ítems genéricos (sin número de serie individual, se gestionan por cantidad)
model Item {
id String @id @default(uuid())
name String // Nombre del producto
description String? // Descripción opcional del producto
categoryId String // ID de la categoría a la que pertenece el producto
category Category @relation(fields: [categoryId], references: [id]) // Relación con la categoría
stock Int @default(0) // Cantidad actual en stock
minStock Int? // Nivel mínimo de stock para alertas
maxStock Int? // Nivel máximo de stock deseado
createdAt DateTime @default(now()) // Fecha de creación del registro
updatedAt DateTime @updatedAt // Fecha de última actualización del registro
deletedAt DateTime? // Fecha de eliminación del registro
movements Movement[] // Relación con los movimientos de este producto
assignments Assignment[] // Relación con las asignaciones de este producto (por cantidad)
assets Asset[] // Relación con los activos serializados que son de este tipo de producto
@@index([categoryId]) // Índice para búsquedas por categoría
@@index([name]) // Índice para búsquedas por nombre de producto
}
// Modelo para ítems serializados (activos individuales con número de serie único)
model Asset {
id String @id @default(uuid())
itemId String? // ID del producto genérico al que pertenece este activo (opcional)
item Item? @relation(fields: [itemId], references: [id]) // Relación con el producto genérico
serialNumber String @unique // Número de serie único y obligatorio para el activo
deliveryNote String? // Número de albarán de entrega (opcional, puede ser para la entrada inicial)
status ItemStatus @default(AVAILABLE) // Estado actual del activo individual
notes String? // Notas adicionales sobre el activo
createdAt DateTime @default(now()) // Fecha de creación del registro
updatedAt DateTime @updatedAt // Fecha de última actualización del registro
movements Movement[] // Relación con los movimientos de este activo
assignment Assignment? // Relación 1:1 con la asignación activa de este activo (un activo solo puede estar en una asignación activa)
@@index([serialNumber]) // Índice para búsquedas por número de serie
@@index([itemId]) // Índice para búsquedas por producto asociado
@@index([status]) // Índice para búsquedas por estado del activo
}
// Modelo para registrar las asignaciones de ítems a destinatarios
model Assignment {
id String @id @default(uuid())
quantity Int? // Cantidad asignada (solo si es un Item, para Asset sería 1 implícitamente)
notes String? // Notas sobre la asignación
itemId String? // ID del producto genérico asignado (si no es un activo serializado)
item Item? @relation(fields: [itemId], references: [id]) // Relación con el producto
assetId String? @unique // ID del activo serializado asignado (si es un activo, debe ser único para asignaciones activas)
asset Asset? @relation(fields: [assetId], references: [id]) // Relación con el activo
recipientId String? // ID del destinatario de la asignación
recipient Recipient? @relation(fields: [recipientId], references: [id], onDelete: Cascade, onUpdate: Cascade) // Relación con el destinatario
assignmentDate DateTime @default(now()) // Fecha en que se realizó la asignación
returnDate DateTime? // Fecha en que se devolvió la asignación (si aplica)
createdBy String // ID del usuario que realizó la asignación
createdUser User @relation(fields: [createdBy], references: [id]) // Relación con el usuario que creó la asignación
createdAt DateTime @default(now()) // Fecha de creación del registro de asignación
updatedAt DateTime @updatedAt // Fecha de última actualización del registro
movement Movement[] // Relación con los movimientos generados por esta asignación (ej. OUT, RETURN)
// Índices para mejorar el rendimiento de las consultas
@@index([itemId])
@@index([assetId])
@@index([recipientId])
@@index([createdBy])
}
// Enum para los tipos de movimiento de stock
enum MovementType {
IN // Entrada de stock (compra, fabricación)
OUT // Salida de stock (venta, consumo)
ASSIGNMENT // Movimiento asociado a una asignación
RETURN // Movimiento asociado a la devolución de una asignación
ADJUSTMENT // Ajuste de inventario (corrección, pérdida)
DELETED // Eliminación de un ítem
}
// Modelo para el histórico de todos los movimientos de stock/activos
model Movement {
id String @id @default(uuid())
type MovementType @default(IN) // Tipo de movimiento
quantity Int // Cantidad del movimiento (para Item, para Asset sería 1)
details String? // Detalles adicionales del movimiento
notes String? // Notas sobre el movimiento
itemId String? // ID del producto afectado (si es un Item)
item Item? @relation(fields: [itemId], references: [id]) // Relación con el producto
assetId String? // ID del activo afectado (si es un Asset)
asset Asset? @relation(fields: [assetId], references: [id]) // Relación con el activo
previousStock Int? // Stock antes de este movimiento (solo para Item)
newStock Int? // Stock después de este movimiento (solo para Item)
recipientId String? // ID del destinatario relacionado con el movimiento (si aplica)
recipient Recipient? @relation(fields: [recipientId], references: [id]) // Relación con el destinatario
assignmentId String? // ID de la asignación relacionada (si el movimiento es ASSIGNMENT o RETURN)
assignment Assignment? @relation(fields: [assignmentId], references: [id]) // Relación con la asignación
userId String // ID del usuario que realizó el movimiento
user User @relation(fields: [userId], references: [id]) // Relación con el usuario
createdAt DateTime @default(now()) // Fecha en que se registró el movimiento
// Índices para mejorar el rendimiento de las consultas
@@index([itemId])
@@index([assetId])
@@index([recipientId])
@@index([type])
@@index([userId])
}

Some files were not shown because too many files have changed in this diff Show More