diff --git a/ipi-survey-platform/.gitignore b/ipi-survey-platform/.gitignore
new file mode 100644
index 0000000..2aef6f1
--- /dev/null
+++ b/ipi-survey-platform/.gitignore
@@ -0,0 +1,53 @@
+# These are some examples of commonly ignored file patterns.
+# You should customize this list as applicable to your project.
+# Learn more about .gitignore:
+# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
+
+# Node artifact files
+node_modules/
+dist/
+
+# Compiled Java class files
+*.class
+
+# Compiled Python bytecode
+*.py[cod]
+
+# Log files
+*.log
+
+# Package files
+*.jar
+
+# Maven
+target/
+dist/
+
+# JetBrains IDE
+.idea/
+
+# Unit test reports
+TEST*.xml
+
+# Generated by MacOS
+.DS_Store
+
+# Generated by Windows
+Thumbs.db
+
+# Applications
+*.app
+*.exe
+*.war
+
+# Large media files
+*.mp4
+*.tiff
+*.avi
+*.flv
+*.mov
+*.wmv
+
+
+
+.env
\ No newline at end of file
diff --git a/ipi-survey-platform/README.md b/ipi-survey-platform/README.md
new file mode 100644
index 0000000..18bc70e
--- /dev/null
+++ b/ipi-survey-platform/README.md
@@ -0,0 +1,16 @@
+# React + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## React Compiler
+
+The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
diff --git a/ipi-survey-platform/eslint.config.js b/ipi-survey-platform/eslint.config.js
new file mode 100644
index 0000000..cee1e2c
--- /dev/null
+++ b/ipi-survey-platform/eslint.config.js
@@ -0,0 +1,29 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{js,jsx}'],
+ extends: [
+ js.configs.recommended,
+ reactHooks.configs['recommended-latest'],
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ ecmaFeatures: { jsx: true },
+ sourceType: 'module',
+ },
+ },
+ rules: {
+ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
+ },
+ },
+])
diff --git a/ipi-survey-platform/index.html b/ipi-survey-platform/index.html
new file mode 100644
index 0000000..960dd47
--- /dev/null
+++ b/ipi-survey-platform/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ ipi-survey-platform
+
+
+
+
+
+
diff --git a/ipi-survey-platform/package-lock.json b/ipi-survey-platform/package-lock.json
new file mode 100644
index 0000000..8b9f59e
--- /dev/null
+++ b/ipi-survey-platform/package-lock.json
@@ -0,0 +1,3906 @@
+{
+ "name": "ipi-survey-platform",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ipi-survey-platform",
+ "version": "0.0.0",
+ "dependencies": {
+ "axios": "^1.12.2",
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "react-router-dom": "^7.9.4"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.36.0",
+ "@tailwindcss/postcss": "^4.1.14",
+ "@types/react": "^19.1.16",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.4",
+ "autoprefixer": "^10.4.21",
+ "eslint": "^9.36.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.22",
+ "globals": "^16.4.0",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^4.1.14",
+ "vite": "^7.1.7"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz",
+ "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
+ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.3",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.4",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.4",
+ "@babel/types": "^7.28.4",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
+ "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.3",
+ "@babel/types": "^7.28.2",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
+ "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.4"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
+ "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.3",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.4",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
+ "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz",
+ "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz",
+ "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz",
+ "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz",
+ "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz",
+ "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz",
+ "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz",
+ "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz",
+ "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz",
+ "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz",
+ "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz",
+ "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz",
+ "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz",
+ "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz",
+ "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz",
+ "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz",
+ "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz",
+ "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz",
+ "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz",
+ "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz",
+ "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz",
+ "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz",
+ "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz",
+ "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz",
+ "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz",
+ "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz",
+ "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz",
+ "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.16.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz",
+ "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.37.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz",
+ "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz",
+ "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.16.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.38",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz",
+ "integrity": "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz",
+ "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz",
+ "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz",
+ "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz",
+ "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz",
+ "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz",
+ "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz",
+ "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz",
+ "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz",
+ "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz",
+ "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz",
+ "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz",
+ "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz",
+ "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz",
+ "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz",
+ "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz",
+ "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz",
+ "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz",
+ "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz",
+ "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz",
+ "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz",
+ "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz",
+ "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.14.tgz",
+ "integrity": "sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.6.0",
+ "lightningcss": "1.30.1",
+ "magic-string": "^0.30.19",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.14"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.14.tgz",
+ "integrity": "sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.4",
+ "tar": "^7.5.1"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.14",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.14",
+ "@tailwindcss/oxide-darwin-x64": "4.1.14",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.14",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.14",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.14",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.14",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.14",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.14",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.14",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.14",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.14"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.14.tgz",
+ "integrity": "sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.14.tgz",
+ "integrity": "sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.14.tgz",
+ "integrity": "sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.14.tgz",
+ "integrity": "sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.14.tgz",
+ "integrity": "sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.14.tgz",
+ "integrity": "sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.14.tgz",
+ "integrity": "sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.14.tgz",
+ "integrity": "sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.14.tgz",
+ "integrity": "sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.14.tgz",
+ "integrity": "sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.5.0",
+ "@emnapi/runtime": "^1.5.0",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.0.5",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.14.tgz",
+ "integrity": "sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.14.tgz",
+ "integrity": "sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/postcss": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.14.tgz",
+ "integrity": "sha512-BdMjIxy7HUNThK87C7BC8I1rE8BVUsfNQSI5siQ4JK3iIa3w0XyVvVL9SXLWO//CtYTcp1v7zci0fYwJOjB+Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "@tailwindcss/node": "4.1.14",
+ "@tailwindcss/oxide": "4.1.14",
+ "postcss": "^8.4.41",
+ "tailwindcss": "4.1.14"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.2",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
+ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.2",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz",
+ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.4.tgz",
+ "integrity": "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.4",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.38",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.21",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
+ "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.24.4",
+ "caniuse-lite": "^1.0.30001702",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
+ "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.8.16",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz",
+ "integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.26.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
+ "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.8.9",
+ "caniuse-lite": "^1.0.30001746",
+ "electron-to-chromium": "^1.5.227",
+ "node-releases": "^2.0.21",
+ "update-browserslist-db": "^1.1.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001750",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz",
+ "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.236",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.236.tgz",
+ "integrity": "sha512-Yi43booTJp2VteesVja4xdcDrAcVFItkD6RrxhMSuGx1kShh+raMwnKyIGNOTl68reqSxPPls6bB8LgIcrK9Gg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.3",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
+ "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz",
+ "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.11",
+ "@esbuild/android-arm": "0.25.11",
+ "@esbuild/android-arm64": "0.25.11",
+ "@esbuild/android-x64": "0.25.11",
+ "@esbuild/darwin-arm64": "0.25.11",
+ "@esbuild/darwin-x64": "0.25.11",
+ "@esbuild/freebsd-arm64": "0.25.11",
+ "@esbuild/freebsd-x64": "0.25.11",
+ "@esbuild/linux-arm": "0.25.11",
+ "@esbuild/linux-arm64": "0.25.11",
+ "@esbuild/linux-ia32": "0.25.11",
+ "@esbuild/linux-loong64": "0.25.11",
+ "@esbuild/linux-mips64el": "0.25.11",
+ "@esbuild/linux-ppc64": "0.25.11",
+ "@esbuild/linux-riscv64": "0.25.11",
+ "@esbuild/linux-s390x": "0.25.11",
+ "@esbuild/linux-x64": "0.25.11",
+ "@esbuild/netbsd-arm64": "0.25.11",
+ "@esbuild/netbsd-x64": "0.25.11",
+ "@esbuild/openbsd-arm64": "0.25.11",
+ "@esbuild/openbsd-x64": "0.25.11",
+ "@esbuild/openharmony-arm64": "0.25.11",
+ "@esbuild/sunos-x64": "0.25.11",
+ "@esbuild/win32-arm64": "0.25.11",
+ "@esbuild/win32-ia32": "0.25.11",
+ "@esbuild/win32-x64": "0.25.11"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.37.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz",
+ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.4.0",
+ "@eslint/core": "^0.16.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.37.0",
+ "@eslint/plugin-kit": "^0.4.0",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
+ "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.23",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.23.tgz",
+ "integrity": "sha512-G4j+rv0NmbIR45kni5xJOrYvCtyD3/7LjpVH8MPPcudXDcNu8gv+4ATTDXTtbRR8rTCM5HxECvCSsRmxKnWDsA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
+ "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
+ "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "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"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
+ "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
+ "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
+ "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
+ "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
+ "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
+ "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
+ "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
+ "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
+ "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
+ "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.19",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
+ "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
+ "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.23",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz",
+ "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
+ "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
+ "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.0"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.9.4",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz",
+ "integrity": "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.9.4",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.4.tgz",
+ "integrity": "sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.9.4"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.52.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
+ "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.52.4",
+ "@rollup/rollup-android-arm64": "4.52.4",
+ "@rollup/rollup-darwin-arm64": "4.52.4",
+ "@rollup/rollup-darwin-x64": "4.52.4",
+ "@rollup/rollup-freebsd-arm64": "4.52.4",
+ "@rollup/rollup-freebsd-x64": "4.52.4",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.52.4",
+ "@rollup/rollup-linux-arm-musleabihf": "4.52.4",
+ "@rollup/rollup-linux-arm64-gnu": "4.52.4",
+ "@rollup/rollup-linux-arm64-musl": "4.52.4",
+ "@rollup/rollup-linux-loong64-gnu": "4.52.4",
+ "@rollup/rollup-linux-ppc64-gnu": "4.52.4",
+ "@rollup/rollup-linux-riscv64-gnu": "4.52.4",
+ "@rollup/rollup-linux-riscv64-musl": "4.52.4",
+ "@rollup/rollup-linux-s390x-gnu": "4.52.4",
+ "@rollup/rollup-linux-x64-gnu": "4.52.4",
+ "@rollup/rollup-linux-x64-musl": "4.52.4",
+ "@rollup/rollup-openharmony-arm64": "4.52.4",
+ "@rollup/rollup-win32-arm64-msvc": "4.52.4",
+ "@rollup/rollup-win32-ia32-msvc": "4.52.4",
+ "@rollup/rollup-win32-x64-gnu": "4.52.4",
+ "@rollup/rollup-win32-x64-msvc": "4.52.4",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.14",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
+ "integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tar": {
+ "version": "7.5.1",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz",
+ "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.1.0",
+ "yallist": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tar/node_modules/yallist": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.1.10",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz",
+ "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/ipi-survey-platform/package.json b/ipi-survey-platform/package.json
new file mode 100644
index 0000000..0481683
--- /dev/null
+++ b/ipi-survey-platform/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "ipi-survey-platform",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "axios": "^1.12.2",
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "react-router-dom": "^7.9.4"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.36.0",
+ "@tailwindcss/postcss": "^4.1.14",
+ "@types/react": "^19.1.16",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.4",
+ "autoprefixer": "^10.4.21",
+ "eslint": "^9.36.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.22",
+ "globals": "^16.4.0",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^4.1.14",
+ "vite": "^7.1.7"
+ }
+}
diff --git a/ipi-survey-platform/postcss.config.js b/ipi-survey-platform/postcss.config.js
new file mode 100644
index 0000000..06cd769
--- /dev/null
+++ b/ipi-survey-platform/postcss.config.js
@@ -0,0 +1,5 @@
+import tailwind from '@tailwindcss/postcss'
+
+export default {
+ plugins: [tailwind],
+}
diff --git a/ipi-survey-platform/public/vite.svg b/ipi-survey-platform/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/ipi-survey-platform/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ipi-survey-platform/src/App.css b/ipi-survey-platform/src/App.css
new file mode 100644
index 0000000..b9d355d
--- /dev/null
+++ b/ipi-survey-platform/src/App.css
@@ -0,0 +1,42 @@
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
diff --git a/ipi-survey-platform/src/App.jsx b/ipi-survey-platform/src/App.jsx
new file mode 100644
index 0000000..6cda3c5
--- /dev/null
+++ b/ipi-survey-platform/src/App.jsx
@@ -0,0 +1,35 @@
+import React from 'react'
+import { Routes, Route, Navigate } from 'react-router-dom'
+import Dashboard from './pages/Dashboard/Dashboard'
+import AdminDashboard from './pages/Admin/AdminDashboard'
+import AdminUsers from './pages/Admin/AdminUsers'
+import Validations from './pages/Admin/Validations'
+import ValidationReview from './pages/Admin/ValidationReview'
+import Configuration from './pages/Admin/Configuration'
+import Survey from './pages/Survey/Survey'
+import Login from './pages/Login/Login'
+import History from './pages/History/History'
+import ChangePassword from './pages/ChangePassword/ChangePassword'
+import ForgotPassword from './pages/ForgotPassword/ForgotPassword'
+
+function App() {
+ return (
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ )
+}
+
+export default App
diff --git a/ipi-survey-platform/src/assets/images/Active - User.svg b/ipi-survey-platform/src/assets/images/Active - User.svg
new file mode 100644
index 0000000..7487a42
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/Active - User.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/BackVector.svg b/ipi-survey-platform/src/assets/images/BackVector.svg
new file mode 100644
index 0000000..2d4e715
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/BackVector.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/Bell.svg b/ipi-survey-platform/src/assets/images/Bell.svg
new file mode 100644
index 0000000..f1cf58a
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/Bell.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/CaretDoubleLeft.svg b/ipi-survey-platform/src/assets/images/CaretDoubleLeft.svg
new file mode 100644
index 0000000..ca3a64e
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/CaretDoubleLeft.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/CaretDoubleRight.svg b/ipi-survey-platform/src/assets/images/CaretDoubleRight.svg
new file mode 100644
index 0000000..43071d5
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/CaretDoubleRight.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/CaretDown - active.svg b/ipi-survey-platform/src/assets/images/CaretDown - active.svg
new file mode 100644
index 0000000..58ed51c
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/CaretDown - active.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/CaretDown-black.svg b/ipi-survey-platform/src/assets/images/CaretDown-black.svg
new file mode 100644
index 0000000..65537cf
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/CaretDown-black.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/CaretDown.svg b/ipi-survey-platform/src/assets/images/CaretDown.svg
new file mode 100644
index 0000000..7163c83
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/CaretDown.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/CaretLeft.svg b/ipi-survey-platform/src/assets/images/CaretLeft.svg
new file mode 100644
index 0000000..4a79d7a
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/CaretLeft.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/CaretRight.svg b/ipi-survey-platform/src/assets/images/CaretRight.svg
new file mode 100644
index 0000000..3a4c136
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/CaretRight.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/CheckCircle-active.svg b/ipi-survey-platform/src/assets/images/CheckCircle-active.svg
new file mode 100644
index 0000000..6a052fa
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/CheckCircle-active.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/CheckCircle-inactive.svg b/ipi-survey-platform/src/assets/images/CheckCircle-inactive.svg
new file mode 100644
index 0000000..f0d03de
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/CheckCircle-inactive.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/CircleFrame.svg b/ipi-survey-platform/src/assets/images/CircleFrame.svg
new file mode 100644
index 0000000..21cfc1b
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/CircleFrame.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/DownloadSimple.svg b/ipi-survey-platform/src/assets/images/DownloadSimple.svg
new file mode 100644
index 0000000..701c1d9
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/DownloadSimple.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/Establishment.svg b/ipi-survey-platform/src/assets/images/Establishment.svg
new file mode 100644
index 0000000..83c7be5
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/Establishment.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/Establishmentid.svg b/ipi-survey-platform/src/assets/images/Establishmentid.svg
new file mode 100644
index 0000000..42d94f0
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/Establishmentid.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/FCSCLogo.svg b/ipi-survey-platform/src/assets/images/FCSCLogo.svg
new file mode 100644
index 0000000..99b10cd
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/FCSCLogo.svg
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/Inactive - user.svg b/ipi-survey-platform/src/assets/images/Inactive - user.svg
new file mode 100644
index 0000000..6c92a61
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/Inactive - user.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/PencilSimple - inactive.svg b/ipi-survey-platform/src/assets/images/PencilSimple - inactive.svg
new file mode 100644
index 0000000..45e78bc
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/PencilSimple - inactive.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/PencilSimple.svg b/ipi-survey-platform/src/assets/images/PencilSimple.svg
new file mode 100644
index 0000000..81cdd0d
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/PencilSimple.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/PrimaryButton.svg b/ipi-survey-platform/src/assets/images/PrimaryButton.svg
new file mode 100644
index 0000000..052badf
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/PrimaryButton.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/Rectangle.svg b/ipi-survey-platform/src/assets/images/Rectangle.svg
new file mode 100644
index 0000000..3f09c0d
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/Rectangle.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/Trash - Inactive.svg b/ipi-survey-platform/src/assets/images/Trash - Inactive.svg
new file mode 100644
index 0000000..68b620d
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/Trash - Inactive.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/Trash - active.svg b/ipi-survey-platform/src/assets/images/Trash - active.svg
new file mode 100644
index 0000000..8488199
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/Trash - active.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/Upload-import.svg b/ipi-survey-platform/src/assets/images/Upload-import.svg
new file mode 100644
index 0000000..eb90ee9
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/Upload-import.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/UploadSimple.svg b/ipi-survey-platform/src/assets/images/UploadSimple.svg
new file mode 100644
index 0000000..eb90ee9
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/UploadSimple.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/User-active.svg b/ipi-survey-platform/src/assets/images/User-active.svg
new file mode 100644
index 0000000..193c475
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/User-active.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/User-inactive.svg b/ipi-survey-platform/src/assets/images/User-inactive.svg
new file mode 100644
index 0000000..8270b7d
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/User-inactive.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/UserGear.svg b/ipi-survey-platform/src/assets/images/UserGear.svg
new file mode 100644
index 0000000..9385f4f
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/UserGear.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/Vector.svg b/ipi-survey-platform/src/assets/images/Vector.svg
new file mode 100644
index 0000000..0603e95
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/Vector.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/Vectorblack.svg b/ipi-survey-platform/src/assets/images/Vectorblack.svg
new file mode 100644
index 0000000..5b856fd
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/Vectorblack.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/caret-up.svg b/ipi-survey-platform/src/assets/images/caret-up.svg
new file mode 100644
index 0000000..3d1e539
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/caret-up.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/check-circle-fill 1-approved.svg b/ipi-survey-platform/src/assets/images/check-circle-fill 1-approved.svg
new file mode 100644
index 0000000..8e4d0d3
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/check-circle-fill 1-approved.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/check-circle-fill 1.svg b/ipi-survey-platform/src/assets/images/check-circle-fill 1.svg
new file mode 100644
index 0000000..bc21ed3
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/check-circle-fill 1.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/checkbox.svg b/ipi-survey-platform/src/assets/images/checkbox.svg
new file mode 100644
index 0000000..ce00749
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/checkbox.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/configrationicon-active.svg b/ipi-survey-platform/src/assets/images/configrationicon-active.svg
new file mode 100644
index 0000000..398c5f5
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/configrationicon-active.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/configrationicon-inactive.svg b/ipi-survey-platform/src/assets/images/configrationicon-inactive.svg
new file mode 100644
index 0000000..4de4a53
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/configrationicon-inactive.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/cuida_dashboard-outline-inactive.svg b/ipi-survey-platform/src/assets/images/cuida_dashboard-outline-inactive.svg
new file mode 100644
index 0000000..d3d4a21
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/cuida_dashboard-outline-inactive.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/cuida_dashboard-outline.svg b/ipi-survey-platform/src/assets/images/cuida_dashboard-outline.svg
new file mode 100644
index 0000000..6b50e21
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/cuida_dashboard-outline.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/delete.svg b/ipi-survey-platform/src/assets/images/delete.svg
new file mode 100644
index 0000000..dd2a520
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/delete.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/emailid.svg b/ipi-survey-platform/src/assets/images/emailid.svg
new file mode 100644
index 0000000..5df1641
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/emailid.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/emirates.svg b/ipi-survey-platform/src/assets/images/emirates.svg
new file mode 100644
index 0000000..61ae1af
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/emirates.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/femaleemployess.svg b/ipi-survey-platform/src/assets/images/femaleemployess.svg
new file mode 100644
index 0000000..de203a1
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/femaleemployess.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/history.svg b/ipi-survey-platform/src/assets/images/history.svg
new file mode 100644
index 0000000..f3a346c
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/history.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/hugeicons_reset-password-inactive.svg b/ipi-survey-platform/src/assets/images/hugeicons_reset-password-inactive.svg
new file mode 100644
index 0000000..57aaeb5
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/hugeicons_reset-password-inactive.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/hugeicons_reset-password.svg b/ipi-survey-platform/src/assets/images/hugeicons_reset-password.svg
new file mode 100644
index 0000000..de4ecd3
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/hugeicons_reset-password.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/ic_baseline-plus.svg b/ipi-survey-platform/src/assets/images/ic_baseline-plus.svg
new file mode 100644
index 0000000..2073302
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/ic_baseline-plus.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/info-fill 1.svg b/ipi-survey-platform/src/assets/images/info-fill 1.svg
new file mode 100644
index 0000000..c92c5f3
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/info-fill 1.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/maleemployes.svg b/ipi-survey-platform/src/assets/images/maleemployes.svg
new file mode 100644
index 0000000..77bfa4e
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/maleemployes.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/managesubmission - inactive.svg b/ipi-survey-platform/src/assets/images/managesubmission - inactive.svg
new file mode 100644
index 0000000..095a1fc
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/managesubmission - inactive.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/material-symbols_search-rounded.svg b/ipi-survey-platform/src/assets/images/material-symbols_search-rounded.svg
new file mode 100644
index 0000000..fb25d13
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/material-symbols_search-rounded.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/mdi_history.svg b/ipi-survey-platform/src/assets/images/mdi_history.svg
new file mode 100644
index 0000000..54c2275
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/mdi_history.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/nonuae.svg b/ipi-survey-platform/src/assets/images/nonuae.svg
new file mode 100644
index 0000000..142a685
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/nonuae.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/qlementine-icons_preview-16.svg b/ipi-survey-platform/src/assets/images/qlementine-icons_preview-16.svg
new file mode 100644
index 0000000..dbefd0a
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/qlementine-icons_preview-16.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/totalemployess.svg b/ipi-survey-platform/src/assets/images/totalemployess.svg
new file mode 100644
index 0000000..ec11da9
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/totalemployess.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/uae.svg b/ipi-survey-platform/src/assets/images/uae.svg
new file mode 100644
index 0000000..d3146b4
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/uae.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/uil_calender.svg b/ipi-survey-platform/src/assets/images/uil_calender.svg
new file mode 100644
index 0000000..4d7a9af
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/uil_calender.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/warning-circle-fill 1.svg b/ipi-survey-platform/src/assets/images/warning-circle-fill 1.svg
new file mode 100644
index 0000000..7c9171c
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/warning-circle-fill 1.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/warning-fill 1.svg b/ipi-survey-platform/src/assets/images/warning-fill 1.svg
new file mode 100644
index 0000000..2d70be2
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/warning-fill 1.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/images/wpf_survey.svg b/ipi-survey-platform/src/assets/images/wpf_survey.svg
new file mode 100644
index 0000000..ae5b59f
--- /dev/null
+++ b/ipi-survey-platform/src/assets/images/wpf_survey.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ipi-survey-platform/src/assets/react.svg b/ipi-survey-platform/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/ipi-survey-platform/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ipi-survey-platform/src/assets/styles/index.css b/ipi-survey-platform/src/assets/styles/index.css
new file mode 100644
index 0000000..3391499
--- /dev/null
+++ b/ipi-survey-platform/src/assets/styles/index.css
@@ -0,0 +1,19 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ body {
+ @apply font-sans text-gray-800 bg-gray-50;
+ }
+}
+
+@layer components {
+ .btn-primary {
+ @apply bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200;
+ }
+
+ .btn-secondary {
+ @apply bg-secondary-600 hover:bg-secondary-700 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200;
+ }
+}
\ No newline at end of file
diff --git a/ipi-survey-platform/src/components/admin/AdminHeader.jsx b/ipi-survey-platform/src/components/admin/AdminHeader.jsx
new file mode 100644
index 0000000..8b2b0a6
--- /dev/null
+++ b/ipi-survey-platform/src/components/admin/AdminHeader.jsx
@@ -0,0 +1,155 @@
+import React from 'react';
+import { NavLink, useNavigate } from 'react-router-dom';
+import Logo from '../../assets/images/FCSCLogo.svg';
+import DashboardIconActive from '../../assets/images/cuida_dashboard-outline.svg';
+import DashboardIconInactive from '../../assets/images/cuida_dashboard-outline-inactive.svg';
+import CheckCircleActive from '../../assets/images/CheckCircle-active.svg';
+import ConfigActive from '../../assets/images/configrationicon-active.svg';
+import ConfigInactive from '../../assets/images/configrationicon-inactive.svg';
+import UserActive from '../../assets/images/User-active.svg';
+import UserInactive from '../../assets/images/User-inactive.svg';
+import ManageSubmissionInactive from '../../assets/images/managesubmission - inactive.svg';
+import BellIconAsset from '../../assets/images/Bell.svg';
+
+const NavItem = ({ label, to, iconActive, iconInactive, end = false }) => (
+
+ `inline-flex items-center gap-2 pb-1 border-b-2 text-[14px] leading-5 ` +
+ (isActive ? 'text-[#92722A] font-medium border-[#92722A]' : 'text-[#232528] border-transparent hover:text-gray-900')
+ }
+ >
+ {({ isActive }) => (
+ <>
+ {iconActive && iconInactive ? (
+
+ ) : (
+
+ )}
+ {label}
+ >
+ )}
+
+);
+
+const AdminHeader = () => {
+ const [open, setOpen] = React.useState(false);
+ const [profileOpen, setProfileOpen] = React.useState(false);
+ const [userName, setUserName] = React.useState('User');
+ const [userEmail, setUserEmail] = React.useState('user@example.com');
+ const navigate = useNavigate();
+
+ React.useEffect(() => {
+ try {
+ const stored = localStorage.getItem('user_profile');
+ if (stored) {
+ const parsed = JSON.parse(stored);
+ if (parsed?.name || parsed?.username) {
+ setUserName(parsed.name || parsed.username);
+ }
+ if (parsed?.email) {
+ setUserEmail(parsed.email);
+ }
+ }
+ } catch {
+ // ignore corrupt storage
+ }
+ }, []);
+
+ const handleLogout = () => {
+ try {
+ localStorage.removeItem('auth_token');
+ localStorage.removeItem('user_profile');
+ } catch {
+ // ignore storage errors
+ }
+ setProfileOpen(false);
+ navigate('/login');
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
setProfileOpen((value) => !value)}
+ className="h-9 w-9 rounded-full bg-[#F2F2F2] grid place-items-center text-[13px] font-medium text-[#232528]"
+ aria-haspopup="menu"
+ aria-expanded={profileOpen}
+ >
+ {userName?.slice(0, 1)?.toUpperCase() || 'U'}
+
+ {profileOpen && (
+
+
+
{userName}
+
{userEmail}
+
+
{
+ setProfileOpen(false);
+ navigate('/change-password');
+ }}
+ >
+ Change Password
+
+
+ Logout
+
+
+ )}
+
+
+
+
setOpen((v) => !v)}
+ >
+
+
+
+
+
+
+ {open && (
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default AdminHeader;
diff --git a/ipi-survey-platform/src/components/admin/StatCard.jsx b/ipi-survey-platform/src/components/admin/StatCard.jsx
new file mode 100644
index 0000000..2e79009
--- /dev/null
+++ b/ipi-survey-platform/src/components/admin/StatCard.jsx
@@ -0,0 +1,18 @@
+import React from 'react';
+
+const StatCard = ({ icon = null, label, value, width = 200.4 }) => {
+ const resolvedWidth = typeof width === 'number' ? `${width}px` : width;
+
+ return (
+
+ {icon}
+ {label}
+ {value}
+
+ );
+};
+
+export default StatCard;
diff --git a/ipi-survey-platform/src/components/admin/SubmissionTable.jsx b/ipi-survey-platform/src/components/admin/SubmissionTable.jsx
new file mode 100644
index 0000000..2acc4b2
--- /dev/null
+++ b/ipi-survey-platform/src/components/admin/SubmissionTable.jsx
@@ -0,0 +1,189 @@
+import React from 'react';
+import Table from '../common/Table';
+import SearchIcon from '../../assets/images/material-symbols_search-rounded.svg';
+
+const submissions = [
+ {
+ establishment: 'CASCADE MARINE FOODS LLC',
+ emirate: 'Sharjah',
+ productCount: '5',
+ year: '2025',
+ quarter: 'Q4',
+ lastSubmission: '10/12/2024',
+ deadline: '20/12/2024',
+ },
+ {
+ establishment: 'Khazan meat factory',
+ emirate: 'Sharjah',
+ productCount: '8',
+ year: '2025',
+ quarter: 'Q3',
+ lastSubmission: '15/11/2024',
+ deadline: '15/12/2024',
+ },
+ {
+ establishment: 'SPINNEYS FRESH FOOD INDUSTRIES L.L.C',
+ emirate: 'Dubai',
+ productCount: '6',
+ year: '2025',
+ quarter: 'Q2',
+ lastSubmission: '12/12/2024',
+ deadline: '10/12/2024',
+ },
+ {
+ establishment: 'FRESHLY FROZEN FOODS FACTORY L.L.C',
+ emirate: 'Dubai',
+ productCount: '8',
+ year: '2025',
+ quarter: 'Q1',
+ lastSubmission: '08/12/2024',
+ deadline: '18/12/2024',
+ },
+ {
+ establishment: 'SAHAR FOOD INDUSTRY (L.L.C)',
+ emirate: 'Dubai',
+ productCount: '8',
+ year: '2025',
+ quarter: 'Q4',
+ lastSubmission: '08/12/2024',
+ deadline: '18/12/2024',
+ },
+ {
+ establishment: 'CONTINENTAL FOOD PROCESSING LLC',
+ emirate: 'Ajman',
+ productCount: '8',
+ year: '2025',
+ quarter: 'Q3',
+ lastSubmission: '08/12/2024',
+ deadline: '18/12/2024',
+ },
+ {
+ establishment: 'AL KHAZNA POULTRY FARM',
+ emirate: 'Abu Dhabi',
+ productCount: '8',
+ year: '2025',
+ quarter: 'Q2',
+ lastSubmission: '08/12/2024',
+ deadline: '18/12/2024',
+ },
+ {
+ establishment: 'DIAMOND MEAT PROCESSING L.L.C',
+ emirate: 'Abu Dhabi',
+ productCount: '8',
+ year: '2025',
+ quarter: 'Q2',
+ lastSubmission: '08/12/2024',
+ deadline: '18/12/2024',
+ },
+ {
+ establishment: 'ALLIANCE FOODS CO L.L.C',
+ emirate: 'Abu Dhabi',
+ productCount: '8',
+ year: '2025',
+ quarter: 'Q2',
+ lastSubmission: '08/12/2024',
+ deadline: '18/12/2024',
+ },
+ {
+ establishment: 'KRUSTASIA FOODS L.L.C',
+ emirate: 'Dubai',
+ productCount: '4',
+ year: '2025',
+ quarter: 'Q1',
+ lastSubmission: '14/12/2024',
+ deadline: '21/12/2024',
+ },
+];
+
+const SubmissionTable = () => {
+ const [search, setSearch] = React.useState('');
+
+ const filtered = React.useMemo(() => {
+ if (!search.trim()) return submissions;
+ const normalized = search.trim().toLowerCase();
+ return submissions.filter((item) =>
+ [
+ item.establishment,
+ item.emirate,
+ item.productCount,
+ item.year,
+ item.quarter,
+ item.lastSubmission,
+ item.deadline,
+ ]
+ .join(' ')
+ .toLowerCase()
+ .includes(normalized)
+ );
+ }, [search]);
+
+ const headers = ['Establishment', 'Emirates', 'Product Count', 'Year', 'Quarter', 'Last Submission', 'Deadline'];
+ const tableRows = filtered.map((item) => [
+ item.establishment,
+ item.emirate,
+ item.productCount,
+ item.year,
+ item.quarter,
+ item.lastSubmission,
+ item.deadline,
+ ]);
+
+ const columnWidths = React.useMemo(
+ () => [
+ 317,
+ 161,
+ 140,
+ 119,
+ 98,
+ 158,
+ 150,
+ ],
+ []
+ );
+
+ const toolbar = (
+
+
+
+ Recent Submissions ({submissions.length})
+
+
+
+
setSearch(event.target.value)}
+ placeholder="Search Establishment"
+ className="h-10 w-full rounded-md border border-[#C3C6CB] pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-[#92722A]"
+ />
+
+
+
+ );
+
+ return (
+
+
+
+
+ View All Submissions
+
+
+
+ );
+};
+
+export default SubmissionTable;
diff --git a/ipi-survey-platform/src/components/common/Button.jsx b/ipi-survey-platform/src/components/common/Button.jsx
new file mode 100644
index 0000000..b9435fa
--- /dev/null
+++ b/ipi-survey-platform/src/components/common/Button.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+const Button = ({ children, className, onClick, type = 'button', disabled = false }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default Button;
\ No newline at end of file
diff --git a/ipi-survey-platform/src/components/common/Card.css b/ipi-survey-platform/src/components/common/Card.css
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/ipi-survey-platform/src/components/common/Card.css
@@ -0,0 +1 @@
+
diff --git a/ipi-survey-platform/src/components/common/Card.jsx b/ipi-survey-platform/src/components/common/Card.jsx
new file mode 100644
index 0000000..416b11b
--- /dev/null
+++ b/ipi-survey-platform/src/components/common/Card.jsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+const Card = ({ children, className = '' }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default Card;
\ No newline at end of file
diff --git a/ipi-survey-platform/src/components/common/FormControls.jsx b/ipi-survey-platform/src/components/common/FormControls.jsx
new file mode 100644
index 0000000..31649a0
--- /dev/null
+++ b/ipi-survey-platform/src/components/common/FormControls.jsx
@@ -0,0 +1,325 @@
+import React from 'react';
+import CaretDownActive from '../../assets/images/CaretDown - active.svg';
+import CalendarIcon from '../../assets/images/uil_calender.svg';
+
+const baseInputStyles = {
+ default: {
+ height: '40px',
+ border: '2px solid #CBA344',
+ borderRadius: '4px',
+ paddingLeft: '16px',
+ paddingRight: '16px',
+ transition: 'border-color 0.2s ease, color 0.2s ease',
+ backgroundColor: '#FFFFFF',
+ },
+ toolbar: {
+ height: '40px',
+ border: '1px solid #C3C6CB',
+ borderRadius: '8px',
+ paddingLeft: '16px',
+ paddingRight: '16px',
+ transition: 'border-color 0.2s ease, box-shadow 0.2s ease',
+ backgroundColor: '#FFFFFF',
+ color: '#5F646D',
+ },
+};
+
+const focusBorderColors = {
+ default: '#92722A',
+ toolbar: '#92722A',
+};
+
+const idleBorderColors = {
+ default: '#CBA344',
+ toolbar: '#C3C6CB',
+};
+
+export const TextField = ({
+ label,
+ placeholder,
+ value,
+ onChange,
+ type = 'text',
+ width = 'auto', // 'auto' | number | string
+ className = '',
+ style = {},
+ name,
+ id,
+ leftIcon,
+ onFocus,
+ onBlur,
+ variant = 'default',
+}) => {
+ const wrapperStyle = {};
+ if (width !== 'auto') {
+ wrapperStyle.width = typeof width === 'number' ? `${width}px` : width;
+ }
+ const [focused, setFocused] = React.useState(false);
+ const handleFocus = (event) => {
+ setFocused(true);
+ onFocus?.(event);
+ };
+ const handleBlur = (event) => {
+ setFocused(false);
+ onBlur?.(event);
+ };
+ const baseStyle = baseInputStyles[variant] || baseInputStyles.default;
+ const inputStyle = { ...baseStyle, ...style };
+ if (leftIcon) {
+ inputStyle.paddingLeft = '40px';
+ }
+ inputStyle.borderColor = focused ? focusBorderColors[variant] : idleBorderColors[variant] || idleBorderColors.default;
+ if (variant !== 'toolbar') {
+ inputStyle.color = '#232528';
+ }
+ return (
+
+ {label && (
+
+ {label}
+
+ )}
+
+
+ {leftIcon && (
+
+ )}
+
+
+ );
+};
+
+export const SelectField = ({
+ label,
+ value,
+ onChange,
+ options = [],
+ width = 'auto',
+ className = '',
+ style = {},
+ name,
+ id,
+ rightIcon = CaretDownActive,
+ placeholder,
+ onFocus,
+ onBlur,
+ variant = 'default',
+ allowClear = false,
+ onClear,
+ clearValue = '',
+}) => {
+ const wrapperStyle = {};
+ if (width !== 'auto') {
+ wrapperStyle.width = typeof width === 'number' ? `${width}px` : width;
+ }
+ const baseStyle = baseInputStyles[variant] || baseInputStyles.default;
+ const inputStyle = { ...baseStyle, ...style };
+ const [focused, setFocused] = React.useState(false);
+ const normalizedClear = clearValue ?? '';
+ const hasValue = value !== undefined && value !== null && value !== '' && value !== normalizedClear;
+ const showClear = allowClear && hasValue;
+ if (rightIcon) {
+ inputStyle.paddingRight = '40px';
+ }
+ const handleFocus = (event) => {
+ setFocused(true);
+ onFocus?.(event);
+ };
+ const handleBlur = (event) => {
+ setFocused(false);
+ onBlur?.(event);
+ };
+ inputStyle.borderColor = focused ? focusBorderColors[variant] : idleBorderColors[variant] || idleBorderColors.default;
+ if (variant === 'toolbar') {
+ inputStyle.color = hasValue && value !== 'All' && value !== 'Status' && value !== 'Year' && value !== 'Quarter' && value !== 'Emirates' ? '#232528' : '#5F646D';
+ } else {
+ inputStyle.color = hasValue ? '#232528' : '#232528';
+ }
+ const handleClear = (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ if (onClear) {
+ onClear();
+ } else if (onChange) {
+ onChange({ target: { value: normalizedClear } });
+ }
+ };
+ return (
+
+ {label && (
+
+ {label}
+
+ )}
+
+
+ {placeholder && (
+
+ {placeholder}
+
+ )}
+ {options.map((opt) => (
+
+ {opt.label}
+
+ ))}
+
+ {showClear ? (
+
+ ×
+
+ ) : (
+ rightIcon &&
+ )}
+
+
+ );
+};
+
+export const DateField = ({
+ label,
+ value,
+ onChange,
+ placeholder,
+ width = 'auto',
+ className = '',
+ style = {},
+ name,
+ id,
+ rightIcon = CalendarIcon,
+ onBlur,
+ onFocus,
+ ...rest
+}) => {
+ const inputRef = React.useRef(null);
+ const [isDateMode, setIsDateMode] = React.useState(Boolean(value));
+ const [focused, setFocused] = React.useState(false);
+
+ React.useEffect(() => {
+ if (typeof document === 'undefined') return;
+ if (document.getElementById('date-field-style')) return;
+ const styleElement = document.createElement('style');
+ styleElement.id = 'date-field-style';
+ styleElement.innerHTML = `
+ input.date-field::-webkit-calendar-picker-indicator {
+ opacity: 0;
+ position: absolute;
+ right: 0;
+ width: 40px;
+ height: 100%;
+ cursor: pointer;
+ }
+ input.date-field::-webkit-clear-button { display: none; }
+ input.date-field::-webkit-inner-spin-button { display: none; }
+ input.date-field { -webkit-appearance: none; }
+ input.date-field::placeholder { color: #9CA3AF; }
+ `;
+ document.head.appendChild(styleElement);
+ }, []);
+
+ const wrapperStyle = {};
+ if (width !== 'auto') {
+ wrapperStyle.width = typeof width === 'number' ? `${width}px` : width;
+ }
+ const inputStyle = {
+ ...baseInputStyle,
+ ...style,
+ color: value ? '#232528' : '#232528',
+ borderColor: focused ? '#92722A' : '#CBA344',
+ };
+ if (rightIcon) {
+ inputStyle.paddingRight = '40px';
+ }
+
+ React.useEffect(() => {
+ setIsDateMode(Boolean(value));
+ }, [value]);
+
+ const triggerPicker = () => {
+ setIsDateMode(true);
+ requestAnimationFrame(() => {
+ if (inputRef.current) {
+ inputRef.current.showPicker?.();
+ inputRef.current.focus?.();
+ }
+ });
+ };
+
+ const handleFocus = (event) => {
+ onFocus?.(event);
+ setFocused(true);
+ if (!isDateMode) {
+ triggerPicker();
+ }
+ };
+
+ const handleBlur = (event) => {
+ onBlur?.(event);
+ setFocused(false);
+ if (!value) {
+ setIsDateMode(false);
+ }
+ };
+
+ return (
+
+ {label && (
+
+ {label}
+
+ )}
+
+
+ {rightIcon && (
+
+
+
+ )}
+
+
+ );
+};
diff --git a/ipi-survey-platform/src/components/common/Loader.jsx b/ipi-survey-platform/src/components/common/Loader.jsx
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/ipi-survey-platform/src/components/common/Loader.jsx
@@ -0,0 +1 @@
+
diff --git a/ipi-survey-platform/src/components/common/Modal.css b/ipi-survey-platform/src/components/common/Modal.css
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/ipi-survey-platform/src/components/common/Modal.css
@@ -0,0 +1 @@
+
diff --git a/ipi-survey-platform/src/components/common/Modal.jsx b/ipi-survey-platform/src/components/common/Modal.jsx
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/ipi-survey-platform/src/components/common/Modal.jsx
@@ -0,0 +1 @@
+
diff --git a/ipi-survey-platform/src/components/common/StatusBadge.jsx b/ipi-survey-platform/src/components/common/StatusBadge.jsx
new file mode 100644
index 0000000..3a95ba1
--- /dev/null
+++ b/ipi-survey-platform/src/components/common/StatusBadge.jsx
@@ -0,0 +1,27 @@
+import React from 'react';
+
+const toneClasses = {
+ green: 'bg-[#F3FAF4] text-[#2F663C] ring-1 ring-[#C4E5CC]',
+ gray: 'bg-[#E9EDF3] text-[#232528] ring-1 ring-[#D6DBE4]',
+};
+
+const normalizeTone = (tone, status) => {
+ if (tone) return tone;
+ const normalizedStatus = String(status || '').toLowerCase();
+ if (normalizedStatus === 'active' || normalizedStatus === 'open') return 'green';
+ return 'gray';
+};
+
+const StatusBadge = ({ tone, status, children, className = '' }) => {
+ const resolvedTone = normalizeTone(tone, status);
+ const classes = toneClasses[resolvedTone] || toneClasses.green;
+ const label = children ?? status ?? '';
+
+ return (
+
+ {label}
+
+ );
+};
+
+export default StatusBadge;
diff --git a/ipi-survey-platform/src/components/common/Table.jsx b/ipi-survey-platform/src/components/common/Table.jsx
new file mode 100644
index 0000000..51487d9
--- /dev/null
+++ b/ipi-survey-platform/src/components/common/Table.jsx
@@ -0,0 +1,293 @@
+import React from 'react';
+import CaretLeftIcon from '../../assets/images/CaretLeft.svg';
+import CaretRightIcon from '../../assets/images/CaretRight.svg';
+import CaretDoubleLeftIcon from '../../assets/images/CaretDoubleLeft.svg';
+import CaretDoubleRightIcon from '../../assets/images/CaretDoubleRight.svg';
+
+// Generic, reusable table
+// Props:
+// - headers: string[]
+// - rows: any[][] (array of arrays matching header order)
+// - beforeHeader: ReactNode (optional toolbar row above table header)
+// - className: string (optional)
+// - headerClassName: string (optional)
+// - separated: boolean (optional) -> adds vertical gaps between rows
+// - rowGapClass: string (optional) -> tailwind border-spacing classes, default: 'border-spacing-y-2'
+// - getRowClass: (rowIndex) => string (optional)
+// - getCellClass: (rowIndex, colIndex) => string (optional)
+// - renderCell: (value, rowIndex, colIndex) => ReactNode (optional)
+// - pagination: {
+// currentPage?: number,
+// onPageChange?: (page: number) => void,
+// pageSize?: number,
+// totalItems?: number,
+// pageSizeOptions?: number[], // reserved for future use
+// }
+const Table = ({
+ headers = [],
+ rows = [],
+ beforeHeader = null,
+ className = '',
+ headerClassName = '',
+ separated = false,
+ rowGapClass = 'border-spacing-y-2',
+ getRowClass,
+ getCellClass,
+ renderCell,
+ pagination,
+ columnWidths = [],
+ disableHorizontalScroll = false,
+}) => {
+ const enablePagination = Boolean(pagination);
+ const pageSize = enablePagination ? Math.max(1, pagination?.pageSize || 10) : rows.length || 1;
+ const totalItems = enablePagination ? pagination?.totalItems ?? rows.length : rows.length;
+ const totalPages = enablePagination ? Math.max(1, Math.ceil(totalItems / pageSize)) : 1;
+ const isControlled = enablePagination && typeof pagination?.currentPage === 'number' && typeof pagination?.onPageChange === 'function';
+ const [internalPage, setInternalPage] = React.useState(1);
+
+ React.useEffect(() => {
+ if (!enablePagination || isControlled) return;
+ const maxPage = Math.max(1, Math.ceil(rows.length / pageSize));
+ setInternalPage((prev) => {
+ if (prev > maxPage) return maxPage;
+ if (prev < 1) return 1;
+ return prev;
+ });
+ }, [enablePagination, isControlled, rows.length, pageSize]);
+
+ const currentPage = enablePagination
+ ? isControlled
+ ? pagination.currentPage || 1
+ : internalPage
+ : 1;
+
+ const safeCurrentPage = enablePagination ? Math.min(Math.max(currentPage, 1), totalPages) : 1;
+ const startIndex = enablePagination ? (safeCurrentPage - 1) * pageSize : 0;
+ const endIndex = enablePagination ? startIndex + pageSize : rows.length;
+ const rangeStart = totalItems === 0 ? 0 : startIndex + 1;
+ const rangeEnd = enablePagination ? Math.min(endIndex, totalItems) : rows.length;
+
+ const displayedRows = React.useMemo(() => {
+ if (!enablePagination) return rows;
+ return rows.slice(startIndex, endIndex);
+ }, [enablePagination, rows, startIndex, endIndex]);
+
+ const goToPage = React.useCallback(
+ (pageNumber) => {
+ if (!enablePagination) return;
+ const next = Math.min(Math.max(pageNumber, 1), totalPages);
+ if (isControlled) {
+ pagination.onPageChange?.(next);
+ } else {
+ setInternalPage(next);
+ }
+ },
+ [enablePagination, isControlled, pagination, totalPages]
+ );
+
+ const hasPrevious = enablePagination ? safeCurrentPage > 1 : false;
+ const hasNext = enablePagination ? safeCurrentPage < totalPages : false;
+
+ const pageItems = React.useMemo(() => {
+ if (!enablePagination) return [];
+ if (totalPages <= 7) {
+ return Array.from({ length: totalPages }, (_, index) => index + 1);
+ }
+
+ const items = [1];
+ const left = safeCurrentPage - 1;
+ const right = safeCurrentPage + 1;
+
+ if (left > 2) {
+ items.push('ellipsis-left');
+ }
+
+ const start = Math.max(2, left);
+ const end = Math.min(totalPages - 1, right);
+ for (let page = start; page <= end; page += 1) {
+ items.push(page);
+ }
+
+ if (right < totalPages - 1) {
+ items.push('ellipsis-right');
+ }
+
+ items.push(totalPages);
+ return items;
+ }, [enablePagination, safeCurrentPage, totalPages]);
+
+ const arrowButtonClass =
+ 'h-8 w-8 flex items-center justify-center text-[#92722A] disabled:text-[#C3C6CB] disabled:cursor-not-allowed transition-colors hover:text-[#B68A35]';
+ const navButtonClass =
+ 'h-9 px-2 text-sm text-[#232528] disabled:text-[#C3C6CB] disabled:cursor-not-allowed transition-colors flex items-center gap-2 hover:text-[#92722A]';
+
+ return (
+
+ {beforeHeader}
+
+
+
+
+ {headers.map((h, i) => {
+ const widthValue = columnWidths[i];
+ const widthStyle = widthValue
+ ? {
+ width: typeof widthValue === 'number' ? `${widthValue}px` : widthValue,
+ minWidth: typeof widthValue === 'number' ? `${widthValue}px` : widthValue,
+ }
+ : undefined;
+ return (
+
+ {h}
+
+ );
+ })}
+
+
+
+ {displayedRows.map((row, ri) => (
+
+ {row.map((cell, ci) => {
+ const widthValue = columnWidths[ci];
+ const widthStyle = widthValue
+ ? {
+ width: typeof widthValue === 'number' ? `${widthValue}px` : widthValue,
+ minWidth: typeof widthValue === 'number' ? `${widthValue}px` : widthValue,
+ }
+ : undefined;
+ return (
+
+ {renderCell ? renderCell(cell, ri, ci) : cell}
+
+ );
+ })}
+
+ ))}
+
+
+
+
+ {enablePagination && (
+
+
+
+
goToPage(safeCurrentPage - 1)}
+ disabled={!hasPrevious}
+ aria-label="Previous page"
+ >
+
+
+
+ {rangeStart}
+ –
+ {rangeEnd}
+ of
+ {totalItems}
+
+
goToPage(safeCurrentPage + 1)}
+ disabled={!hasNext}
+ aria-label="Next page"
+ >
+
+
+
+
+
+
goToPage(1)}
+ disabled={!hasPrevious}
+ >
+
+ First
+
+
goToPage(safeCurrentPage - 1)}
+ disabled={!hasPrevious}
+ >
+
+ Previous
+
+
+ {pageItems.map((item, index) => {
+ if (typeof item === 'string') {
+ return (
+
+ …
+
+ );
+ }
+ const isActive = item === safeCurrentPage;
+ return (
+
goToPage(item)}
+ >
+ {item}
+
+ );
+ })}
+
+
goToPage(safeCurrentPage + 1)}
+ disabled={!hasNext}
+ >
+ Next
+
+
+
goToPage(totalPages)}
+ disabled={!hasNext}
+ >
+ Last
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default Table;
+
diff --git a/ipi-survey-platform/src/components/dashboard/SubmissionHistory.jsx b/ipi-survey-platform/src/components/dashboard/SubmissionHistory.jsx
new file mode 100644
index 0000000..6f22162
--- /dev/null
+++ b/ipi-survey-platform/src/components/dashboard/SubmissionHistory.jsx
@@ -0,0 +1,191 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import Table from '../common/Table';
+import SearchIcon from '../../assets/images/material-symbols_search-rounded.svg';
+import CaretDown from '../../assets/images/CaretDown.svg';
+
+const getStatusClass = (status) => {
+ const normalized = String(status || '').toLowerCase();
+ if (normalized === 'approved') {
+ return 'inline-flex items-center rounded-md bg-green-100 px-2 py-1 text-xs font-medium text-green-800';
+ }
+ if (normalized === 'pending' || normalized === 'under review' || normalized === 'in-progress') {
+ return 'inline-flex items-center rounded-md bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-800';
+ }
+ if (normalized === 'rejected' || normalized === 'returned') {
+ return 'inline-flex items-center rounded-md bg-red-100 px-2 py-1 text-xs font-medium text-red-800';
+ }
+ return 'inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-800';
+};
+
+const formatQuarter = (quarter, year) => {
+ if (!quarter && !year) return '—';
+ return [quarter || '', year || ''].filter(Boolean).join(' ');
+};
+
+const formatDate = (value) => {
+ if (!value) return '—';
+ const date = new Date(value);
+ if (Number.isNaN(date.getTime())) return '—';
+ return date.toLocaleDateString('en-GB');
+};
+
+const statusOptions = [
+ { label: 'All Status', value: 'all' },
+ { label: 'Approved', value: 'approved' },
+ { label: 'Under Review', value: 'under review' },
+ { label: 'Pending', value: 'pending' },
+ { label: 'Returned', value: 'returned' },
+ { label: 'Rejected', value: 'rejected' },
+];
+
+const normalizeStatus = (status) => String(status || '').toLowerCase();
+
+const downloadCsv = (rows) => {
+ if (!rows.length) return;
+ const headers = ['Quarter', 'Status', 'Submission Date', 'Products'];
+ const csvRows = [headers.join(',')];
+ rows.forEach((row) => {
+ csvRows.push(row.slice(0, 4).map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(','));
+ });
+ const blob = new Blob([csvRows.join('\n')], { type: 'text/csv;charset=utf-8;' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', 'submission-history.csv');
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+};
+
+export const SubmissionHistory = ({ history = [], loading = false, error = '' }) => {
+ const [searchTerm, setSearchTerm] = React.useState('');
+ const [statusFilter, setStatusFilter] = React.useState('all');
+ const navigate = useNavigate();
+
+ const normalizedHistory = React.useMemo(() => {
+ if (!Array.isArray(history)) return [];
+ return history.map((entry, index) => ({
+ id: entry?.id ?? index,
+ quarter: formatQuarter(entry?.quarter, entry?.year),
+ status: entry?.status || 'Pending',
+ date: formatDate(entry?.created_at || entry?.submitted_on),
+ products: entry?.product_count,
+ }));
+ }, [history]);
+
+ const filteredRows = normalizedHistory.filter((entry) => {
+ const term = searchTerm.trim().toLowerCase();
+ const matchesSearch =
+ !term ||
+ entry.quarter.toLowerCase().includes(term) ||
+ entry.status.toLowerCase().includes(term) ||
+ entry.date.toLowerCase().includes(term);
+ const matchesStatus =
+ statusFilter === 'all' || normalizeStatus(entry.status) === statusFilter;
+ return matchesSearch && matchesStatus;
+ });
+ const rowsData = filteredRows.map((s) => {
+ const count = Number.isFinite(Number(s.products)) ? Number(s.products) : null;
+ const display = count === null ? '—' : `${count} ${count === 1 ? 'product' : 'products'}`;
+ return [s.quarter, s.status, s.date, display, 'View'];
+ });
+
+ const handleViewSubmission = React.useCallback(() => {
+ navigate('/history');
+ }, [navigate]);
+
+ const toolbar = (
+
+
+
Submission History
+
+ {loading && Loading… }
+ {error && {error} }
+
+
+
+
+
setSearchTerm(e.target.value)}
+ placeholder="Search submissions"
+ className="w-full h-10 rounded-md border border-[#E5E7EB] pl-10 pr-3 text-sm focus:outline-none"
+ />
+
+
+
+
setStatusFilter(e.target.value)}
+ className="h-10 w-full appearance-none rounded-md border border-[#E5E7EB] bg-white pl-3 pr-9 text-sm text-[#232528] focus:outline-none cursor-pointer"
+ >
+ {statusOptions.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+
+
downloadCsv(rowsData)}
+ disabled={!rowsData.length}
+ className={`inline-flex h-10 items-center gap-2 rounded-md border px-4 text-sm font-medium ${rowsData.length ? 'border-[#92722A] text-[#92722A] hover:bg-[#F2ECCF]' : 'border-gray-200 text-gray-400 cursor-not-allowed'}`}
+ >
+
+
+
+ Export CSV
+
+
+
+ );
+
+ return (
+
+ {
+ if (ci === 1) {
+ return {value} ;
+ }
+ if (ci === 4) {
+ const disabled = !normalizedHistory[ri];
+ return (
+
+ View
+
+ );
+ }
+ return value;
+ }}
+ />
+
+ );
+};
diff --git a/ipi-survey-platform/src/components/dashboard/SurveyStatus.jsx b/ipi-survey-platform/src/components/dashboard/SurveyStatus.jsx
new file mode 100644
index 0000000..89e65ae
--- /dev/null
+++ b/ipi-survey-platform/src/components/dashboard/SurveyStatus.jsx
@@ -0,0 +1,121 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+
+const formatQuarterYear = (quarter, year) => {
+ if (!quarter || !year) return '—';
+ return `${quarter} ${year}`;
+};
+
+const formatDate = (value) => {
+ if (!value) return '—';
+ const date = new Date(value);
+ if (Number.isNaN(date.getTime())) return '—';
+ return date.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' });
+};
+
+const formatRelativeDays = (value) => {
+ if (!value) return '—';
+ const deadline = new Date(value);
+ if (Number.isNaN(deadline.getTime())) return '—';
+ const now = new Date();
+ const diffMs = deadline.getTime() - now.getTime();
+ const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
+ if (!Number.isFinite(diffDays)) return '—';
+ if (diffDays < 0) return `${Math.abs(diffDays)} days overdue`;
+ if (diffDays === 0) return 'Due today';
+ return `${diffDays} day${diffDays === 1 ? '' : 's'}`;
+};
+
+export const SurveyStatus = ({ data = null, loading = false, error = '' }) => {
+ const navigate = useNavigate();
+ const handleStartSurvey = () => {
+ navigate('/survey');
+ };
+
+ const submissionStatus = data?.submission_status || '—';
+ const currentQuarter = formatQuarterYear(data?.current_quarter, data?.current_quarter_year);
+ const nextDeadlineDate = formatDate(data?.next_deadline);
+ const nextDeadlineRelative = formatRelativeDays(data?.next_deadline);
+ const lastSubmissionQuarter = formatQuarterYear(data?.last_submission?.quarter, data?.last_submission?.year);
+ const lastSubmissionDate = formatDate(data?.last_submission?.submitted_on);
+
+ const heroTitle = loading
+ ? 'Loading survey status…'
+ : error
+ ? 'Unable to load survey status'
+ : currentQuarter !== '—'
+ ? `${currentQuarter} Survey`
+ : 'Survey status unavailable';
+
+ const heroSubtitle = loading
+ ? 'Fetching the latest reporting information.'
+ : error
+ ? error
+ : 'Complete your quarterly industrial production data submission';
+
+ return (
+
+
+
+
+
+
{heroTitle}
+
{heroSubtitle}
+
+ Due: {nextDeadlineDate}
+ {nextDeadlineRelative}
+
+
+
+ Start Survey Now
+
+
+
+
+
+
+
+
+
Submission Status
+
{loading ? 'Loading…' : submissionStatus}
+
{currentQuarter !== '—' ? `${currentQuarter} submission required` : 'Awaiting reporting period data.'}
+
+
+
+
Next Deadline
+
{loading ? 'Loading…' : nextDeadlineRelative}
+
{`Due on ${nextDeadlineDate}`}
+
+
+
+
Last Submission
+
{loading ? 'Loading…' : lastSubmissionQuarter}
+
{`Submitted ${lastSubmissionDate}`}
+
+
+
+
+
+
Submission Status
+
{loading ? 'Loading…' : submissionStatus}
+
{currentQuarter !== '—' ? `${currentQuarter} submission required` : 'Awaiting reporting period data.'}
+
+
+
Next Deadline
+
{loading ? 'Loading…' : nextDeadlineRelative}
+
{`Due on ${nextDeadlineDate}`}
+
+
+
Last Submission
+
{loading ? 'Loading…' : lastSubmissionQuarter}
+
{`Submitted ${lastSubmissionDate}`}
+
+
+
+
+ );
+};
diff --git a/ipi-survey-platform/src/components/dashboard/WelcomeSection.jsx b/ipi-survey-platform/src/components/dashboard/WelcomeSection.jsx
new file mode 100644
index 0000000..00c0234
--- /dev/null
+++ b/ipi-survey-platform/src/components/dashboard/WelcomeSection.jsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import HeaderBar from '../../components/layout/HeaderBar';
+
+const formatDate = (value) => {
+ if (!value) return '—';
+ const date = new Date(value);
+ if (Number.isNaN(date.getTime())) return '—';
+ return date.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' });
+};
+
+export const WelcomeSection = ({ data = null, loading = false, error = '' }) => {
+ const currentQuarter = data?.current_quarter;
+ const currentYear = data?.current_quarter_year;
+ const deadline = data?.next_deadline;
+
+ let description = 'Current reporting information unavailable.';
+ if (loading) {
+ description = 'Loading dashboard information…';
+ } else if (error) {
+ description = error;
+ } else if (currentQuarter && currentYear) {
+ const formattedDeadline = formatDate(deadline);
+ description = `Current reporting period: ${currentQuarter} ${currentYear}${formattedDeadline !== '—' ? ` (Due: ${formattedDeadline})` : ''}`;
+ }
+
+ return (
+
+
+
+
+
+
Welcome Back
+
{description}
+
+
+
+ );
+};
diff --git a/ipi-survey-platform/src/components/forms/Checkbox.jsx b/ipi-survey-platform/src/components/forms/Checkbox.jsx
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/ipi-survey-platform/src/components/forms/Checkbox.jsx
@@ -0,0 +1 @@
+
diff --git a/ipi-survey-platform/src/components/forms/FormGroup.css b/ipi-survey-platform/src/components/forms/FormGroup.css
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/ipi-survey-platform/src/components/forms/FormGroup.css
@@ -0,0 +1 @@
+
diff --git a/ipi-survey-platform/src/components/forms/FormGroup.jsx b/ipi-survey-platform/src/components/forms/FormGroup.jsx
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/ipi-survey-platform/src/components/forms/FormGroup.jsx
@@ -0,0 +1 @@
+
diff --git a/ipi-survey-platform/src/components/forms/Input.jsx b/ipi-survey-platform/src/components/forms/Input.jsx
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/ipi-survey-platform/src/components/forms/Input.jsx
@@ -0,0 +1 @@
+
diff --git a/ipi-survey-platform/src/components/forms/Select.jsx b/ipi-survey-platform/src/components/forms/Select.jsx
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/ipi-survey-platform/src/components/forms/Select.jsx
@@ -0,0 +1 @@
+
diff --git a/ipi-survey-platform/src/components/forms/Textarea.jsx b/ipi-survey-platform/src/components/forms/Textarea.jsx
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/ipi-survey-platform/src/components/forms/Textarea.jsx
@@ -0,0 +1 @@
+
diff --git a/ipi-survey-platform/src/components/layout/HeaderBar.jsx b/ipi-survey-platform/src/components/layout/HeaderBar.jsx
new file mode 100644
index 0000000..a8b9a58
--- /dev/null
+++ b/ipi-survey-platform/src/components/layout/HeaderBar.jsx
@@ -0,0 +1,186 @@
+import React from 'react';
+import { NavLink, useNavigate } from 'react-router-dom';
+import Logo from '../../assets/images/FCSCLogo.svg';
+import SurveyIconActive from '../../assets/images/Vector.svg';
+import SurveyIconInactive from '../../assets/images/vectorblack.svg';
+import DashboardIconActive from '../../assets/images/cuida_dashboard-outline.svg';
+import DashboardIconInactive from '../../assets/images/cuida_dashboard-outline-inactive.svg';
+import HistoryIconActive from '../../assets/images/mdi_history.svg';
+import HistoryIconInactive from '../../assets/images/history.svg';
+
+const HeaderBar = () => {
+ const [open, setOpen] = React.useState(false);
+ const [userOpen, setUserOpen] = React.useState(false);
+ const [userName, setUserName] = React.useState('User');
+ const [userEmail, setUserEmail] = React.useState('');
+ const navigate = useNavigate();
+
+ React.useEffect(() => {
+ let parsed;
+ try {
+ const stored = localStorage.getItem('user_profile');
+ if (stored) {
+ parsed = JSON.parse(stored);
+ }
+ } catch (error) {
+ parsed = undefined;
+ }
+ const name = parsed?.name || parsed?.username || parsed?.email || '';
+ const email = parsed?.email || '';
+ if (name) {
+ setUserName(name);
+ }
+ if (email) {
+ setUserEmail(email);
+ }
+ }, []);
+
+ const onLogout = () => {
+ try {
+ localStorage.removeItem('auth_token');
+ localStorage.removeItem('user_profile');
+ } catch {}
+ setUserOpen(false);
+ navigate('/login');
+ };
+
+ const onChangePassword = () => {
+ setUserOpen(false);
+ navigate('/change-password');
+ };
+
+ const initials = React.useMemo(() => {
+ const source = userName || userEmail;
+ if (!source) return 'U';
+ const parts = source.trim().split(' ');
+ const letters = parts.slice(0, 2).map((part) => part.charAt(0)).join('');
+ return letters ? letters.toUpperCase() : 'U';
+ }, [userEmail, userName]);
+ return (
+
+ );
+};
+
+export default HeaderBar;
diff --git a/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.css b/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.css
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.css
@@ -0,0 +1 @@
+
diff --git a/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx b/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx
new file mode 100644
index 0000000..9362384
--- /dev/null
+++ b/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx
@@ -0,0 +1,269 @@
+import React from 'react';
+import CaretDown from '../../../assets/images/caretdown.svg';
+import PrimaryButton from '../../../assets/images/primarybutton.svg';
+import BackVendor from '../../../assets/images/backvector.svg';
+
+const Field = ({ label, placeholder = '', type = 'text', value = '', onChange = () => {}, disabled = false }) => (
+
+ {label}
+
+
+);
+
+const Select = ({ label, options = [], value = '', placeholder = 'Select option', onChange = () => {}, disabled = false }) => (
+
+
{label}
+
+
+ {placeholder}
+ {options.map((opt) => {
+ if (typeof opt === 'object' && opt !== null) {
+ return (
+
+ {opt.label ?? opt.value ?? ''}
+
+ );
+ }
+ return (
+ {opt}
+ );
+ })}
+
+
+
+
+);
+
+const Card = ({ children }) => (
+
+);
+
+const defaultEmployeeInfo = {
+ emiratiMale: '',
+ nonEmiratiMale: '',
+ emiratiFemale: '',
+ nonEmiratiFemale: '',
+ totalEmployees: '',
+ totalEmirati: '',
+};
+
+const EstablishmentInfo = ({
+ data = {},
+ onChange = () => {},
+ onNext = () => {},
+ onBack = () => {},
+ loading = false,
+ error = '',
+}) => {
+ const info = React.useMemo(
+ () => ({
+ quarter: '',
+ year: '',
+ establishmentName: '',
+ permanentFactoryCode: '',
+ industryCode: '',
+ licenseNumber: '',
+ isicCode: '',
+ emirate: '',
+ employeeInfo: {
+ ...defaultEmployeeInfo,
+ ...(data.employeeInfo || {}),
+ },
+ ...data,
+ }),
+ [data]
+ );
+
+ const handleFieldChange = (field) => (event) => {
+ onChange({
+ ...info,
+ [field]: event.target.value,
+ });
+ };
+
+ const handleEmployeeChange = (field) => (event) => {
+ onChange({
+ ...info,
+ employeeInfo: {
+ ...info.employeeInfo,
+ [field]: event.target.value,
+ },
+ });
+ };
+
+ return (
+
+ {/* Reporting Period */}
+
+ Reporting Period
+
+
+
+
+
+
+ {/* Establishment Information */}
+
+ Establishment Information
+
+
+
+
+
+
+
+
+
+
+ {/* Employee Information */}
+
+ Employee Information
+
+
+
+
+
+
+
+
+
+
+ {/* Footer actions */}
+
+
+
+ Back
+
+
+ Next
+
+
+
+
+
+ {(loading || error) && (
+
+ {loading &&
Loading establishment information…
}
+ {!loading && error &&
{error}
}
+
+ )}
+
+ );
+};
+
+export default EstablishmentInfo;
diff --git a/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx b/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx
new file mode 100644
index 0000000..0aef6a5
--- /dev/null
+++ b/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx
@@ -0,0 +1,454 @@
+import React from 'react';
+import {
+ fetchVariationReasons,
+ fetchZeroTargetReasons,
+ fetchProducts,
+ fetchUnits,
+} from '../../../services/masters/masterService.js';
+import CaretDown from '../../../assets/images/caretdown.svg';
+
+const Input = ({ placeholder = '', type = 'text', value = '', onChange = () => {} }) => (
+
+);
+
+const Select = ({ placeholder = '', options = [], value = '', onChange }) => (
+
+
+ {placeholder}
+ {options.map((opt) => {
+ if (opt && typeof opt === 'object') {
+ const optionValue = opt.value ?? '';
+ const optionLabel = opt.label ?? optionValue;
+ if (!optionValue && !optionLabel) return null;
+ return (
+
+ {optionLabel}
+
+ );
+ }
+ return (
+ {opt}
+ );
+ })}
+
+
+
+);
+
+const Textarea = ({ placeholder = '', value = '', onChange = () => {} }) => (
+
+);
+
+const Card = ({ children }) => (
+
+);
+
+const SectionBox = ({ title, children, badgeBg = '#F3F4F6', badgeText = '#374151' }) => (
+
+
+
+
+ {title}
+
+
+ {children}
+
+
+);
+
+const ProductData = ({
+ products = [],
+ onProductsChange = () => {},
+ onNext = () => {},
+ onBack = () => {},
+}) => {
+ const MAX_PRODUCTS = 10;
+ const nextId = React.useRef(products.length + 1);
+ const [variationReasons, setVariationReasons] = React.useState([]);
+ const [isLoadingReasons, setIsLoadingReasons] = React.useState(false);
+ const [reasonsError, setReasonsError] = React.useState('');
+ const [zeroTargetReasons, setZeroTargetReasons] = React.useState([]);
+ const [isLoadingZeroReasons, setIsLoadingZeroReasons] = React.useState(false);
+ const [zeroReasonsError, setZeroReasonsError] = React.useState('');
+ const [productOptions, setProductOptions] = React.useState([]);
+ const [isLoadingProducts, setIsLoadingProducts] = React.useState(false);
+ const [productsError, setProductsError] = React.useState('');
+ const [unitOptions, setUnitOptions] = React.useState([]);
+ const [isLoadingUnits, setIsLoadingUnits] = React.useState(false);
+ const [unitsError, setUnitsError] = React.useState('');
+
+ React.useEffect(() => {
+ const variationController = new AbortController();
+ const zeroController = new AbortController();
+ const productsController = new AbortController();
+ const unitsController = new AbortController();
+ const loadReasons = async () => {
+ console.log('Fetching variation reasons...');
+ setIsLoadingReasons(true);
+ setReasonsError('');
+ try {
+ const reasons = await fetchVariationReasons({ signal: variationController.signal });
+ console.log('Variation reasons fetched:', reasons);
+ setVariationReasons(reasons);
+ setReasonsError('');
+ } catch (error) {
+ if (error.name !== 'AbortError') {
+ console.error('Failed to fetch variation reasons', error);
+ setReasonsError('Failed to load variation reasons.');
+ }
+ } finally {
+ setIsLoadingReasons(false);
+ }
+ };
+
+ const loadZeroReasons = async () => {
+ setIsLoadingZeroReasons(true);
+ setZeroReasonsError('');
+ try {
+ const reasons = await fetchZeroTargetReasons({ signal: zeroController.signal });
+ setZeroTargetReasons(reasons);
+ setZeroReasonsError('');
+ } catch (error) {
+ if (error.name !== 'AbortError') {
+ console.error('Failed to fetch zero target reasons', error);
+ setZeroReasonsError('Failed to load zero target reasons.');
+ }
+ } finally {
+ setIsLoadingZeroReasons(false);
+ }
+ };
+
+ const loadProducts = async () => {
+ setIsLoadingProducts(true);
+ setProductsError('');
+ try {
+ const productsList = await fetchProducts({ signal: productsController.signal });
+ setProductOptions(productsList);
+ setProductsError('');
+ } catch (error) {
+ if (error.name !== 'AbortError') {
+ console.error('Failed to fetch products', error);
+ setProductsError('Failed to load products.');
+ }
+ } finally {
+ setIsLoadingProducts(false);
+ }
+ };
+
+ const loadUnits = async () => {
+ setIsLoadingUnits(true);
+ setUnitsError('');
+ try {
+ const unitsList = await fetchUnits({ signal: unitsController.signal });
+ setUnitOptions(unitsList);
+ setUnitsError('');
+ } catch (error) {
+ if (error.name !== 'AbortError') {
+ console.error('Failed to fetch units', error);
+ setUnitsError('Failed to load units.');
+ }
+ } finally {
+ setIsLoadingUnits(false);
+ }
+ };
+
+ loadReasons();
+ loadZeroReasons();
+ loadProducts();
+ loadUnits();
+ return () => {
+ variationController.abort();
+ zeroController.abort();
+ productsController.abort();
+ unitsController.abort();
+ };
+ }, []);
+
+ React.useEffect(() => {
+ const maxId = products.reduce((max, item) => (item && typeof item.id === 'number' ? Math.max(max, item.id) : max), 0);
+ if (maxId >= nextId.current) {
+ nextId.current = maxId + 1;
+ }
+ }, [products]);
+
+ const addProduct = () => {
+ if (products.length >= MAX_PRODUCTS) return;
+ const newProduct = {
+ id: nextId.current++,
+ product: '',
+ unit: '',
+ capacity: '',
+ previousQuantity: '',
+ previousCost: '',
+ currentQuantity: '',
+ currentCost: '',
+ variationReason: '',
+ otherVariationReason: '',
+ remarks: '',
+ forecastQuantity: '',
+ forecastCost: '',
+ zeroTargetReason: '',
+ otherZeroTargetReason: '',
+ };
+ onProductsChange([...products, newProduct]);
+ };
+
+ const removeProduct = (id) => {
+ if (products.length === 1) return;
+ onProductsChange(products.filter((p) => p.id !== id));
+ };
+
+ const updateProductField = (id, field, value) => {
+ onProductsChange(
+ products.map((product) =>
+ product.id === id ? { ...product, [field]: value } : product
+ )
+ );
+ };
+
+ const canAddMoreProducts = products.length < MAX_PRODUCTS;
+
+ return (
+
+
+
+
Product Data
+ Current Quarter: Q1 2025
+
+
+
+ + Add Product
+
+ {!canAddMoreProducts && (
+ Maximum of {MAX_PRODUCTS} products reached.
+ )}
+
+
+
+ {products.map((p, idx) => (
+
+
+
+
Product #{idx + 1}
+ {products.length > 1 && (
+
removeProduct(p.id)}
+ className="text-[#92722A] text-sm px-3 py-1 rounded hover:bg-[#92722A]/10 cursor-pointer"
+ >
+ Remove
+
+ )}
+
+
+
+
Product (HS Code - Name)
+
updateProductField(p.id, 'product', e.target.value)}
+ />
+ {productsError && productOptions.length === 0 && (
+ {productsError}
+ )}
+
+
+
Unit
+
updateProductField(p.id, 'unit', e.target.value)}
+ />
+ {unitsError && unitOptions.length === 0 && (
+ {unitsError}
+ )}
+
+
+ Annual installed capacity (in unit)
+ updateProductField(p.id, 'capacity', e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ Quantity (liter)
+ updateProductField(p.id, 'currentQuantity', e.target.value)}
+ />
+
+
+ Cost (AED)
+ updateProductField(p.id, 'currentCost', e.target.value)}
+ />
+
+
+
Reason for Variation
+
updateProductField(p.id, 'variationReason', e.target.value)}
+ />
+ {reasonsError && variationReasons.length === 0 && (
+ {reasonsError}
+ )}
+
+
+ Other reason for variation
+ updateProductField(p.id, 'otherVariationReason', e.target.value)}
+ />
+
+
+ Remarks (optional)
+
+
+
+
+
+
+
+
+
Reason for 0 Target
+
updateProductField(p.id, 'zeroTargetReason', e.target.value)}
+ />
+ {zeroReasonsError && zeroTargetReasons.length === 0 && (
+ {zeroReasonsError}
+ )}
+
+
+ Other reason for 0 target
+ updateProductField(p.id, 'otherZeroTargetReason', e.target.value)}
+ />
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+ Back
+
+
+ Next
+
+
+
+
+
+
+ );
+};
+
+export default ProductData;
+
diff --git a/ipi-survey-platform/src/components/survey/ReviewSubmit/ReviewSubmit.jsx b/ipi-survey-platform/src/components/survey/ReviewSubmit/ReviewSubmit.jsx
new file mode 100644
index 0000000..a75258a
--- /dev/null
+++ b/ipi-survey-platform/src/components/survey/ReviewSubmit/ReviewSubmit.jsx
@@ -0,0 +1,223 @@
+import React from 'react';
+import Table from '../../common/Table';
+import BackVendor from '../../../assets/images/backvector.svg';
+
+const Card = ({ title, children }) => (
+
+ {title && (
+
+
{title}
+
+ )}
+
+ {children}
+
+
+);
+
+const KeyValueTable = ({ rows = [] }) => (
+
+
+
+
+ {rows.map(([label, value], idx) => (
+
+
+ {label}
+
+ {value}
+
+ ))}
+
+
+
+
+);
+
+// Reuse common Table for uniform styling across app
+
+const formatNumber = (value) => {
+ if (value === null || value === undefined || value === '') return '';
+ const number = Number(value);
+ if (Number.isNaN(number)) return value;
+ return new Intl.NumberFormat('en-US').format(number);
+};
+
+const buildEstablishmentRows = (establishment) => {
+ if (!establishment) return [];
+ return [
+ ['Establishment Name', establishment.establishmentName || '—'],
+ ['Establishment ID', establishment.permanentFactoryCode || '—'],
+ ['Industry Code', establishment.industryCode || '—'],
+ ['License Number', establishment.licenseNumber || '—'],
+ ['ISIC Code', establishment.isicCode || '—'],
+ ['Emirate', establishment.emirate || '—'],
+ ['Quarter', establishment.quarter || '—'],
+ ['Year', establishment.year || '—'],
+ ];
+};
+
+const buildEmploymentRows = (establishment) => {
+ const info = establishment?.employeeInfo || {};
+ return [
+ ['Total Employees', formatNumber(info.totalEmployees) || '—'],
+ ['Emirati Male', formatNumber(info.emiratiMale) || '—'],
+ ['Emirati Female', formatNumber(info.emiratiFemale) || '—'],
+ ['Non-Emirati Male', formatNumber(info.nonEmiratiMale) || '—'],
+ ['Non-Emirati Female', formatNumber(info.nonEmiratiFemale) || '—'],
+ ['Total Emirati', formatNumber(info.totalEmirati) || '—'],
+ ];
+};
+
+const buildProductRows = (products = []) => {
+ if (!Array.isArray(products)) return [];
+ return products.map((product) => [
+ product.product || '—',
+ product.unit || '—',
+ formatNumber(product.currentQuantity) || '—',
+ formatNumber(product.currentCost) || '—',
+ formatNumber(product.forecastQuantity) || '—',
+ formatNumber(product.forecastCost) || '—',
+ ]);
+};
+
+const ReviewSubmit = ({
+ establishment,
+ products = [],
+ onBack = () => {},
+ onSubmit = () => {},
+ toast = null,
+ onToastClose = () => {},
+ isSubmitting = false,
+ hasSubmitted = false,
+}) => {
+ const [confirm, setConfirm] = React.useState(false);
+ const establishmentRows = React.useMemo(() => buildEstablishmentRows(establishment), [establishment]);
+ const employmentRows = React.useMemo(() => buildEmploymentRows(establishment), [establishment]);
+ const productRows = React.useMemo(() => buildProductRows(products), [products]);
+
+ const submitLabel = React.useMemo(() => {
+ if (hasSubmitted) return 'Submitted';
+ if (isSubmitting) return 'Submitting...';
+ return 'Submit';
+ }, [hasSubmitted, isSubmitting]);
+
+ return (
+
+ {toast && (
+
+
+ {toast.type === 'success' ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
{toast.message || toast.text || ''}
+
+
+
+
+
+
+
+ )}
+
+
Review & Submit
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Info note */}
+ {!hasSubmitted && (
+
+ Review your data carefully before submission. You can go back to previous steps to edit any details. Once submitted, changes will require admin approval.
+
+ )}
+
+ {/* Confirmation */}
+
+
+ setConfirm(e.target.checked)}
+ />
+ I hereby confirm that all data entered is accurate and complete to the best of my knowledge.
+
+
+
+ {/* Footer actions */}
+
+
+
+ Back
+
+
+ {submitLabel}
+
+
+
+ );
+};
+
+export default ReviewSubmit;
+
diff --git a/ipi-survey-platform/src/components/survey/SurveyProgress.jsx b/ipi-survey-platform/src/components/survey/SurveyProgress.jsx
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/ipi-survey-platform/src/components/survey/SurveyProgress.jsx
@@ -0,0 +1 @@
+
diff --git a/ipi-survey-platform/src/index.css b/ipi-survey-platform/src/index.css
new file mode 100644
index 0000000..1751ea3
--- /dev/null
+++ b/ipi-survey-platform/src/index.css
@@ -0,0 +1,16 @@
+@import "tailwindcss";
+
+@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');
+
+@layer base {
+ html,
+ body {
+ font-family: 'Roboto', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
+ }
+ * {
+ font-family: inherit;
+ }
+ button {
+ cursor: pointer;
+ }
+}
diff --git a/ipi-survey-platform/src/main.jsx b/ipi-survey-platform/src/main.jsx
new file mode 100644
index 0000000..0b295bd
--- /dev/null
+++ b/ipi-survey-platform/src/main.jsx
@@ -0,0 +1,13 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.jsx'
+import './index.css'
+import { BrowserRouter } from 'react-router-dom'
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+
+
+ ,
+)
\ No newline at end of file
diff --git a/ipi-survey-platform/src/pages/Admin/AdminDashboard.jsx b/ipi-survey-platform/src/pages/Admin/AdminDashboard.jsx
new file mode 100644
index 0000000..2f27055
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/AdminDashboard.jsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import AdminHeader from '../../components/admin/AdminHeader';
+import StatCard from '../../components/admin/StatCard';
+import WpfSurveyIcon from '../../assets/images/wpf_survey.svg';
+import SubmittedIcon from '../../assets/images/check-circle-fill 1.svg';
+import ApprovedIcon from '../../assets/images/check-circle-fill 1-approved.svg';
+import RejectedIcon from '../../assets/images/warning-circle-fill 1.svg';
+import OverdueIcon from '../../assets/images/warning-fill 1.svg';
+import PendingIcon from '../../assets/images/info-fill 1.svg';
+import SubmissionTable from '../../components/admin/SubmissionTable';
+
+const AdminDashboard = () => {
+ return (
+
+
+
+
+
+ } />
+ } iconBg="#E5F0FF" iconBorder="#D0E2FF" />
+ } iconBg="#E6F4EA" iconBorder="#CBE9D5" />
+ } iconBg="#FEEDEA" iconBorder="#FFD9D2" />
+ } iconBg="#FFF3E6" iconBorder="#FFE3C9" />
+ } iconBg="#FFF7E6" iconBorder="#FFE9B8" />
+
+
+
+
+
+
+ );
+};
+
+export default AdminDashboard;
diff --git a/ipi-survey-platform/src/pages/Admin/AdminUsers.jsx b/ipi-survey-platform/src/pages/Admin/AdminUsers.jsx
new file mode 100644
index 0000000..44ce0a5
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/AdminUsers.jsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import AdminHeader from '../../components/admin/AdminHeader';
+import AdminUsersContent from './configuration/AdminUsers';
+import CaretUp from '../../assets/images/caret-up.svg';
+
+const AdminUsersPage = () => {
+ return (
+
+
+
+
+
+ Dashboard
+
+
+
+ Users
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default AdminUsersPage;
diff --git a/ipi-survey-platform/src/pages/Admin/Configuration.jsx b/ipi-survey-platform/src/pages/Admin/Configuration.jsx
new file mode 100644
index 0000000..53c6df8
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/Configuration.jsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import AdminHeader from '../../components/admin/AdminHeader';
+import QuarterlyWindows from './configuration/QuarterlyWindows';
+import IsicHsCodes from './configuration/IsicHsCodes';
+import UnitMaster from './configuration/UnitMaster';
+import AdminUsers from './configuration/AdminUsers';
+import CompanyProfile from './configuration/CompanyProfile';
+import CaretUp from '../../assets/images/caret-up.svg';
+
+const Configuration = () => {
+ const tabs = ['Quarterly Windows', 'HS Codes', 'Unit Master', 'Admin Users', 'Company Profile'];
+ const [active, setActive] = React.useState(tabs[0]);
+
+ const Breadcrumbs = (
+
+
+ Dashboard
+
+
+
+ Configuration
+
+
+
+ );
+
+ const PageHeader = (
+
+
+
+ {tabs.map((t) => (
+ setActive(t)}
+ className={`pb-3 text-sm whitespace-nowrap border-b-2 cursor-pointer transition-colors ${
+ active === t
+ ? 'text-[#92722A] border-[#92722A] font-medium'
+ : 'text-[#232528] border-transparent hover:text-[#92722A]'
+ }`}
+ >
+ {t}
+
+ ))}
+
+
+
+ );
+
+ const isQuarterTab = active === 'Quarterly Windows';
+ const isIsicTab = active === 'HS Codes';
+ const isUnitMasterTab = active === 'Unit Master';
+ const isAdminTab = active === 'Admin Users';
+ const isCompanyProfileTab = active === 'Company Profile';
+
+ return (
+
+
+
+ {Breadcrumbs}
+ {PageHeader}
+
+ {isQuarterTab &&
}
+
+ {isIsicTab &&
}
+
+ {isUnitMasterTab &&
}
+
+ {isAdminTab &&
}
+
+ {isCompanyProfileTab &&
}
+
+ {!isQuarterTab && !isIsicTab && !isUnitMasterTab && !isAdminTab && !isCompanyProfileTab && (
+
+
{active} content coming soon…
+
+ )}
+
+
+ );
+};
+
+export default Configuration;
diff --git a/ipi-survey-platform/src/pages/Admin/ValidationReview.jsx b/ipi-survey-platform/src/pages/Admin/ValidationReview.jsx
new file mode 100644
index 0000000..d03bd15
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/ValidationReview.jsx
@@ -0,0 +1,361 @@
+import React from 'react';
+import { Link, useNavigate, useParams } from 'react-router-dom';
+import AdminHeader from '../../components/admin/AdminHeader';
+import CaretUp from '../../assets/images/caret-up.svg';
+import EstablishmentIcon from '../../assets/images/Establishment.svg';
+import EstablishmentIdIcon from '../../assets/images/Establishmentid.svg';
+import EmailIcon from '../../assets/images/emailid.svg';
+import EmiratesIcon from '../../assets/images/emirates.svg';
+import TotalEmployeesIcon from '../../assets/images/totalemployess.svg';
+import MaleEmployeesIcon from '../../assets/images/maleemployes.svg';
+import FemaleEmployeesIcon from '../../assets/images/femaleemployess.svg';
+import UaeIcon from '../../assets/images/uae.svg';
+import NonUaeIcon from '../../assets/images/nonuae.svg';
+
+const establishmentDetails = [
+ { label: 'Establishment Name', value: 'ABC Manufacturing LLC' },
+ { label: 'Establishment ID', value: 'MSCensus-23-05-002829' },
+ { label: 'Email', value: 'info@abcmg.com' },
+ { label: 'Emirate', value: 'Abu Dhabi' },
+];
+
+const employmentDetails = [
+ { label: 'Total Employees', value: '120' },
+ { label: 'Male Employees', value: '80' },
+ { label: 'Female Employees', value: '40' },
+ { label: 'UAE Nationals', value: '30' },
+ { label: 'Non-UAE Nationals', value: '90' },
+];
+
+const employmentIcons = {
+ 'Total Employees': TotalEmployeesIcon,
+ 'Male Employees': MaleEmployeesIcon,
+ 'Female Employees': FemaleEmployeesIcon,
+ 'UAE Nationals': UaeIcon,
+ 'Non-UAE Nationals': NonUaeIcon,
+};
+
+const quarterLegend = [
+ { label: 'Previous Quarter', color: '#1D4ED8' },
+ { label: 'Current Quarter', color: '#1E7C34' },
+ { label: 'Next Quarter', color: '#D97706' },
+];
+
+const productDetails = [
+ {
+ code: '04012000',
+ name: 'Milk',
+ submittedOn: '28/03/2025',
+ unit: 'Liters',
+ capacity: '20000',
+ quarters: [
+ { label: 'Q1-2025', color: '#0C64F6', cellColor: '#F4F8FF' },
+ { label: 'Q2-2025', color: '#1E7C34', cellColor: '#F3FAF4' },
+ { label: 'Q3-2025', color: '#D97706', cellColor: '#FFF6EC' },
+ ],
+ rows: [
+ { label: 'Quantity (liters)', values: ['500', '200', '50'] },
+ { label: 'Cost (AED)', values: ['40,000', '20,000', '20,000'] },
+ { label: 'Reason', values: ['--', 'Seasonal production surge', 'Higher demand expected'] },
+ ],
+ remarks: 'NA',
+ },
+ {
+ code: '07028000',
+ name: 'Iron',
+ submittedOn: '18/08/2025',
+ unit: 'Kilogram',
+ capacity: '50000',
+ quarters: [
+ { label: 'Q1-2025', color: '#0C64F6', cellColor: '#F4F8FF' },
+ { label: 'Q2-2025', color: '#1E7C34', cellColor: '#F3FAF4' },
+ { label: 'Q3-2025', color: '#D97706', cellColor: '#FFF6EC' },
+ ],
+ rows: [
+ { label: 'Quantity (kilograms)', values: ['800', '400', '80'] },
+ { label: 'Cost (AED)', values: ['70,000', '40,000', '30,000'] },
+ { label: 'Reason', values: ['--', 'Seasonal procurement', 'Higher demand expected'] },
+ ],
+ remarks: 'NA',
+ },
+];
+
+const infoBadgeClass = 'inline-flex items-center rounded-[10px] border border-[#E5D7AA] bg-[#FDF9EB] px-3 py-1 text-sm font-medium text-[#92722A]';
+
+const ValidationReview = () => {
+ const navigate = useNavigate();
+ const { id } = useParams();
+ const [toast, setToast] = React.useState(null);
+ const [isRejectOpen, setIsRejectOpen] = React.useState(false);
+ const [rejectReason, setRejectReason] = React.useState('');
+
+ const showApproveToast = () => {
+ setToast({ type: 'success', text: 'ABC Manufacturing LLC has been approved successfully.' });
+ window.clearTimeout((showApproveToast._t));
+ // Auto-hide after 4 seconds
+ showApproveToast._t = window.setTimeout(() => setToast(null), 4000);
+ };
+
+ const handleRejectSubmit = () => {
+ setToast({ type: 'info', text: rejectReason ? `Submission rejected. Reason: ${rejectReason}` : 'Submission rejected.' });
+ window.clearTimeout((showApproveToast._t));
+ showApproveToast._t = window.setTimeout(() => setToast(null), 4000);
+ setIsRejectOpen(false);
+ setRejectReason('');
+ };
+
+ return (
+
+
+
+ {toast && (
+
+
+
setToast(null)}>
+
+
+
+
+
+ )}
+
+
+
+ Dashboard
+
+
+
+ Manage Submissions
+
+
+
+ Submission Details
+
+
+
+ {/*
+
+
Submission Details
+
Submission #{id || '—'}
+
+
navigate('/admin/validations')}
+ className="inline-flex h-10 items-center justify-center rounded-[10px] border border-[#C3C6CB] bg-white px-4 text-sm font-medium text-[#5F646D] transition-colors hover:bg-[#F7F7F7]"
+ >
+ Back to Submissions
+
+
*/}
+
+
+
+
+
+
Establishment Information
+
+
+ {establishmentDetails.map((item) => (
+
+ {item.label === 'Establishment Name' && (
+
+ )}
+ {item.label === 'Establishment ID' && (
+
+ )}
+ {item.label === 'Email' && (
+
+ )}
+ {item.label === 'Emirate' && (
+
+ )}
+ {!['Establishment Name', 'Establishment ID', 'Email', 'Emirate'].includes(item.label) && (
+
+ )}
+
+ {item.label}
+
+ {item.value}
+
+
+ ))}
+
+
+
+
+
Employment Details
+
+
+ {employmentDetails.map((item) => (
+
+ {employmentIcons[item.label] ? (
+
+ ) : (
+
+ )}
+
+ {item.label}
+
+ {item.value}
+
+
+ ))}
+
+
+
+
+
+
+ {productDetails.map((product) => (
+
+
+
Product: {product.code} - {product.name}
+
+ Submitted By: {product.submittedOn}
+ Unit: {product.unit}
+ Capacity: {product.capacity}
+
+
+
+
+ {quarterLegend.map((item) => (
+
+
+ {item.label}
+
+ ))}
+
+
+
+
+
+
+ Description
+ {product.quarters.map((quarter) => (
+
+ {quarter.label}
+
+ ))}
+
+
+
+ {product.rows.map((row) => (
+
+ {row.label}
+ {row.values.map((value, index) => (
+
+ {value}
+
+ ))}
+
+ ))}
+
+
+
+
+
+
+ Remarks: {product.remarks}
+
+
+ Admin Remarks
+
+
+
+
+ ))}
+
+
+
+
+
Note: Please review the details carefully before taking action.
+
+ setIsRejectOpen(true)}
+ >
+ Reject
+
+
+ Approve
+
+
+
+
+
+
+ {isRejectOpen && (
+
+
+
+
Reason for Rejection
+ setIsRejectOpen(false)}
+ aria-label="Close rejection dialog"
+ >
+ ×
+
+
+
+
+ Reason
+
+
+
+ {
+ setIsRejectOpen(false);
+ setRejectReason('');
+ }}
+ >
+ Cancel
+
+
+ Reject
+
+
+
+
+ )}
+
+ );
+};
+
+export default ValidationReview;
diff --git a/ipi-survey-platform/src/pages/Admin/Validations.jsx b/ipi-survey-platform/src/pages/Admin/Validations.jsx
new file mode 100644
index 0000000..dadbfb6
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/Validations.jsx
@@ -0,0 +1,482 @@
+import React from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import AdminHeader from '../../components/admin/AdminHeader';
+import Table from '../../components/common/Table';
+import SearchIcon from '../../assets/images/material-symbols_search-rounded.svg';
+import DownloadIcon from '../../assets/images/DownloadSimple.svg';
+import ActionIcon from '../../assets/images/qlementine-icons_preview-16.svg';
+import CaretUp from '../../assets/images/caret-up.svg';
+import { TextField, SelectField } from '../../components/common/FormControls';
+
+const submissions = [
+ {
+ id: 1,
+ establishment: 'CASCADE MARINE FOODS LLC',
+ emirate: 'Sharjah',
+ year: '2025',
+ quarter: 'Q4',
+ submittedAt: '18/10/2025, 10:40 AM',
+ products: 10,
+ submittedBy: 'Ahmed (Owner)',
+ status: 'Pending',
+ reviewer: 'Omar',
+ },
+ {
+ id: 2,
+ establishment: 'Khazan meat factory',
+ emirate: 'Sharjah',
+ year: '2025',
+ quarter: 'Q3',
+ submittedAt: '18/10/2025, 10:40 AM',
+ products: 6,
+ submittedBy: 'Sara (Admin)',
+ status: 'Approved',
+ reviewer: 'Abdul',
+ },
+ {
+ id: 3,
+ establishment: 'SPINNEYS FRESH FOOD INDUSTRIES L.L.C',
+ emirate: 'Dubai',
+ year: '2025',
+ quarter: 'Q2',
+ submittedAt: '18/10/2025, 10:40 AM',
+ products: 8,
+ submittedBy: 'Shaik (Admin)',
+ status: 'Approved',
+ reviewer: 'Abdul',
+ },
+ {
+ id: 4,
+ establishment: 'FRESHLY FROZEN FOODS FACTORY L.L.C',
+ emirate: 'Dubai',
+ year: '2025',
+ quarter: 'Q1',
+ submittedAt: '18/10/2025, 10:40 AM',
+ products: 4,
+ submittedBy: 'Abdul (Manager)',
+ status: 'Rejected',
+ reviewer: 'Abdul',
+ },
+ {
+ id: 5,
+ establishment: 'SAHAR FOOD INDUSTRY (L.L.C)',
+ emirate: 'Dubai',
+ year: '2025',
+ quarter: 'Q4',
+ submittedAt: '18/10/2025, 10:40 AM',
+ products: 8,
+ submittedBy: 'Ahmed (Owner)',
+ status: 'Approved',
+ reviewer: 'Abdul',
+ },
+ {
+ id: 6,
+ establishment: 'CONTINENTAL FOOD PROCESSING LLC',
+ emirate: 'Ajman',
+ year: '2025',
+ quarter: 'Q3',
+ submittedAt: '18/10/2025, 10:40 AM',
+ products: 5,
+ submittedBy: 'Sara (Admin)',
+ status: 'Rejected',
+ reviewer: 'Abdul',
+ },
+ {
+ id: 7,
+ establishment: 'AL KHAZNA POULTRY FARM',
+ emirate: 'Abu Dhabi',
+ year: '2025',
+ quarter: 'Q2',
+ submittedAt: '18/10/2025, 10:40 AM',
+ products: 7,
+ submittedBy: 'Sara (Admin)',
+ status: 'Approved',
+ reviewer: 'Abdul',
+ },
+ {
+ id: 8,
+ establishment: 'DIAMOND MEAT PROCESSING L.L.C',
+ emirate: 'Abu Dhabi',
+ year: '2025',
+ quarter: 'Q2',
+ submittedAt: '18/10/2025, 10:40 AM',
+ products: 7,
+ submittedBy: 'Ahmed (Owner)',
+ status: 'Approved',
+ reviewer: 'Abdul',
+ },
+ {
+ id: 9,
+ establishment: 'ALLIANCE FOODS CO L.L.C',
+ emirate: 'Abu Dhabi',
+ year: '2025',
+ quarter: 'Q2',
+ submittedAt: '18/10/2025, 10:40 AM',
+ products: 7,
+ submittedBy: 'Ahmed (Owner)',
+ status: 'Approved',
+ reviewer: 'Abdul',
+ },
+ {
+ id: 10,
+ establishment: 'KRUSTASIA FOODS L.L.C',
+ emirate: 'Dubai',
+ year: '2025',
+ quarter: 'Q1',
+ submittedAt: '18/10/2025, 10:40 AM',
+ products: 8,
+ submittedBy: 'Sara (Admin)',
+ status: 'Pending',
+ reviewer: 'Fathima',
+ },
+ {
+ id: 11,
+ establishment: 'AL TAMEER FOOD INDUSTRIES',
+ emirate: 'Abu Dhabi',
+ year: '2025',
+ quarter: 'Q3',
+ submittedAt: '19/10/2025, 8:15 AM',
+ products: 11,
+ submittedBy: 'Ahmed (Owner)',
+ status: 'Pending',
+ reviewer: 'Omar',
+ },
+ {
+ id: 12,
+ establishment: 'AL RAWABI DAIRY COMPANY',
+ emirate: 'Dubai',
+ year: '2024',
+ quarter: 'Q4',
+ submittedAt: '12/09/2024, 4:10 PM',
+ products: 9,
+ submittedBy: 'Mariam (Admin)',
+ status: 'Approved',
+ reviewer: 'Abdul',
+ },
+ {
+ id: 13,
+ establishment: 'EMIRATES FRESH POULTRY',
+ emirate: 'Ajman',
+ year: '2024',
+ quarter: 'Q4',
+ submittedAt: '05/09/2024, 11:05 AM',
+ products: 6,
+ submittedBy: 'Haroon (Manager)',
+ status: 'Rejected',
+ reviewer: 'Omar',
+ },
+ {
+ id: 14,
+ establishment: 'GULF FISHERIES LLC',
+ emirate: 'Ras Al Khaimah',
+ year: '2024',
+ quarter: 'Q3',
+ submittedAt: '21/08/2024, 9:15 AM',
+ products: 12,
+ submittedBy: 'Owais (Owner)',
+ status: 'Approved',
+ reviewer: 'Fathima',
+ },
+];
+
+const STATUS_VARIANTS = {
+ Approved: { background: '#F3FAF4', text: '#2F663C', border: '#F3FAF4' },
+ Pending: { background: '#E7F5FF', text: '#286CFF', border: '#E7F5FF' },
+ Rejected: { background: '#FEF2F2', text: '#B52520', border: '#FEF2F2' },
+};
+
+const SUMMARY_VARIANTS = {
+ total: { background: '#F9F7ED', label: '#7C5E24', value: '#232528' },
+ approved: { background: '#F3FAF4', label: '#2F663C', value: '#232528' },
+ pending: { background: '#E7F5FF', label: '#003CFF', value: '#232528' },
+ rejected: { background: '#FEF2F2', label: '#B52520', value: '#232528' },
+};
+
+const Badge = ({ children, status }) => {
+ const variant = STATUS_VARIANTS[status] || { background: '#F2F4F7', text: '#475467', border: '#EAECF0' };
+ return (
+
+ {children}
+
+ );
+};
+
+const ManageSubmissions = () => {
+ const navigate = useNavigate();
+ const [search, setSearch] = React.useState('');
+ const [year, setYear] = React.useState('');
+ const [quarter, setQuarter] = React.useState('');
+ const [emirate, setEmirate] = React.useState('');
+ const [status, setStatus] = React.useState('');
+ const [currentPage, setCurrentPage] = React.useState(1);
+ const pageSize = 10;
+
+ const yearOptions = React.useMemo(() => ['All', ...new Set(submissions.map((item) => item.year))], []);
+ const quarterOptions = React.useMemo(() => ['All', ...new Set(submissions.map((item) => item.quarter))], []);
+ const emirateOptions = React.useMemo(() => ['All', ...new Set(submissions.map((item) => item.emirate))], []);
+ const statusOptions = React.useMemo(() => ['All', ...new Set(submissions.map((item) => item.status))], []);
+
+ const filtered = React.useMemo(() => {
+ return submissions.filter((item) => {
+ const matchesSearch = search
+ ? item.establishment.toLowerCase().includes(search.toLowerCase()) ||
+ item.submittedBy.toLowerCase().includes(search.toLowerCase())
+ : true;
+ const matchesYear = !year || year === 'All' || item.year === year;
+ const matchesQuarter = !quarter || quarter === 'All' || item.quarter === quarter;
+ const matchesEmirate = !emirate || emirate === 'All' || item.emirate === emirate;
+ const matchesStatus = !status || status === 'All' || item.status === status;
+ return matchesSearch && matchesYear && matchesQuarter && matchesEmirate && matchesStatus;
+ });
+ }, [search, year, quarter, emirate, status]);
+
+ React.useEffect(() => {
+ setCurrentPage(1);
+ }, [search, year, quarter, emirate, status]);
+
+ const summaryCounts = React.useMemo(() => {
+ return {
+ total: submissions.length,
+ approved: submissions.filter((item) => item.status === 'Approved').length,
+ pending: submissions.filter((item) => item.status === 'Pending').length,
+ rejected: submissions.filter((item) => item.status === 'Rejected').length,
+ };
+ }, []);
+
+ const headers = [
+ 'Establishment',
+ 'Emirates',
+ 'Year',
+ 'Quarter',
+ 'Products',
+ 'Submitted By',
+ 'Submission Date & Time',
+ 'Status',
+ 'Reviewer',
+ 'Actions',
+ ];
+
+ const tableRows = React.useMemo(
+ () =>
+ filtered.map((item) => [
+ item.establishment,
+ item.emirate,
+ item.year,
+ item.quarter,
+ item.products,
+ item.submittedBy,
+ item.submittedAt,
+ item.status,
+ item.reviewer,
+ item.id,
+ ]),
+ [filtered]
+ );
+
+ const columnWidths = React.useMemo(
+ () => [
+ 302,
+ 105,
+ 83,
+ 91,
+ 100,
+ 152,
+ 208,
+ 120,
+ 104,
+ 119,
+ ],
+ []
+ );
+
+ const toolbar = (
+
+
+
+ Manage Submissions ({filtered.length})
+
+
+
+
+
+
setSearch(event.target.value)}
+ placeholder="Search Establishment"
+ className="h-full w-full rounded-md border border-[#C3C6CB] pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-[#92722A]"
+ />
+
+
+ setQuarter(event.target.value)}
+ options={quarterOptions.map((value) => ({ label: value === 'All' ? 'All' : value, value }))}
+ placeholder="Quarter"
+ width="100%"
+ variant="toolbar"
+ allowClear
+ onClear={() => setQuarter('All')}
+ clearValue="All"
+ />
+
+
+
+ setYear(event.target.value)}
+ options={yearOptions.map((value) => ({ label: value === 'All' ? 'All' : value, value }))}
+ placeholder="Year"
+ width="100%"
+ variant="toolbar"
+ allowClear
+ onClear={() => setYear('All')}
+ clearValue="All"
+ />
+
+
+ setStatus(event.target.value)}
+ options={statusOptions.map((value) => ({ label: value === 'All' ? 'All' : value, value }))}
+ placeholder="Status"
+ width="100%"
+ variant="toolbar"
+ allowClear
+ onClear={() => setStatus('All')}
+ clearValue="All"
+ />
+
+
+ setEmirate(event.target.value)}
+ options={emirateOptions.map((value) => ({ label: value === 'All' ? 'All' : value, value }))}
+ placeholder="Emirates"
+ width="100%"
+ variant="toolbar"
+ allowClear
+ onClear={() => setEmirate('All')}
+ clearValue="All"
+ />
+
+
+
+
+
+
+ );
+
+ return (
+
+
+
+
+
+ Dashboard
+
+
+
+ Manage Submissions
+ {/* */}
+
+
+
+
+
+ {(
+ [
+ { key: 'total', label: 'Total', value: summaryCounts.total },
+ { key: 'approved', label: 'Approved', value: summaryCounts.approved },
+ { key: 'pending', label: 'Pending', value: summaryCounts.pending },
+ { key: 'rejected', label: 'Rejected', value: summaryCounts.rejected },
+ ]
+ ).map((item) => {
+ const variant = SUMMARY_VARIANTS[item.key];
+ return (
+
+
+ {item.label}:
+
+
+ {item.value}
+
+
+ );
+ })}
+
+
+
+ Export CSV
+
+
+
+
+
{
+ if (columnIndex === 7) {
+ return (
+
+ {value}
+
+ );
+ }
+
+ if (columnIndex === 9) {
+ return (
+
+ navigate(`/admin/validations/${value}`)}
+ >
+ View Details
+
+
+ );
+ }
+
+ if (columnIndex === 5) {
+ return {value} ;
+ }
+
+ if (columnIndex === 8) {
+ return {value} ;
+ }
+
+ return value;
+ }}
+ />
+
+
+
+ );
+};
+
+export default ManageSubmissions;
\ No newline at end of file
diff --git a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers.jsx b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers.jsx
new file mode 100644
index 0000000..9fbb3f3
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers.jsx
@@ -0,0 +1,217 @@
+import React from 'react';
+import Table from '../../../components/common/Table';
+import SearchIcon from '../../../assets/images/material-symbols_search-rounded.svg';
+import ExportIcon from '../../../assets/images/DownloadSimple.svg';
+import AddIcon from '../../../assets/images/ic_baseline-plus.svg';
+import PencilActive from '../../../assets/images/PencilSimple.svg';
+import PencilInactive from '../../../assets/images/PencilSimple - inactive.svg';
+import TrashActive from '../../../assets/images/Trash - active.svg';
+import TrashInactive from '../../../assets/images/Trash - Inactive.svg';
+import AdminUserIcon from '../../../assets/images/UserGear.svg';
+import ActiveUserIcon from '../../../assets/images/Active - User.svg';
+import InactiveUserIcon from '../../../assets/images/Inactive - user.svg';
+import ResetPasswordIcon from '../../../assets/images/hugeicons_reset-password.svg';
+import ResetPasswordInactiveIcon from '../../../assets/images/hugeicons_reset-password-inactive.svg';
+
+const SummaryCard = ({ label, count, icon, iconBg = '#FDF7EB' }) => (
+
+
+
+ {icon && }
+
+
{label}
+
+
{count}
+
+);
+
+const StatusBadge = ({ status }) => {
+ const map = {
+ Active: 'bg-green-50 text-green-700 ring-1 ring-green-200',
+ Closed: 'bg-gray-100 text-gray-700 ring-1 ring-gray-200',
+ };
+ return (
+
+ {status}
+
+ );
+};
+
+const AdminUsers = () => {
+ const [users, setUsers] = React.useState([
+ {
+ name: 'Ahmed Al-Mansouri',
+ email: 'ahmed.mansouri@fcsc.gov.ae',
+ lastLogin: '11th Mar, 2025 14:30',
+ status: 'Active',
+ },
+ {
+ name: 'Sarah Johnson',
+ email: 'sarah.johnson@fcsc.gov.ae',
+ lastLogin: '13th Mar, 2025 13:00',
+ status: 'Active',
+ },
+ {
+ name: 'Mohammed Hassan',
+ email: 'mohammed.hassan@fcsc.gov.ae',
+ lastLogin: '18th Mar, 2025 22:40',
+ status: 'Active',
+ },
+ {
+ name: 'Emily Davis',
+ email: 'emily.davis@fcsc.gov.ae',
+ lastLogin: '20th Mar, 2025 18:40',
+ status: 'Closed',
+ },
+ ]);
+ const [editingRow, setEditingRow] = React.useState(null);
+ const [deletingRow, setDeletingRow] = React.useState(null);
+
+ const totalUsers = users.length;
+ const activeUsers = users.filter((user) => user.status === 'Active').length;
+ const inactiveUsers = totalUsers - activeUsers;
+
+ const summaryItems = [
+ {
+ label: 'Total Users',
+ count: totalUsers,
+ icon: AdminUserIcon,
+ iconBg: '#FDF7EB',
+ },
+ {
+ label: 'Active User',
+ count: activeUsers,
+ icon: ActiveUserIcon,
+ iconBg: '#E7F2FF',
+ },
+ {
+ label: 'Inactive User',
+ count: inactiveUsers,
+ icon: InactiveUserIcon,
+ iconBg: '#F5F5F7',
+ },
+ ];
+
+ const headers = ['Name', 'Email', 'Last Login', 'Status', 'Actions'];
+ const rows = users.map((user) => [
+ user.name,
+ user.email,
+ user.lastLogin,
+ ,
+ 'actions',
+ ]);
+
+ const handleEdit = (index) => {
+ setEditingRow(index);
+ // Future: open edit modal
+ };
+
+ const handleDelete = (index) => {
+ setDeletingRow(index);
+ // Future: open delete confirmation
+ };
+
+ const tableToolbar = (
+
+
Admin Users
+
+
+
+
+
+
+
+
+ Export CSV
+
+
+
+ Add User
+
+
+
+
+ );
+
+ return (
+
+
+ {summaryItems.map((item) => (
+
+ ))}
+
+
+
{
+ if (colIndex === 4) {
+ const user = users[rowIndex];
+ const isActive = user.status === 'Active';
+ return (
+
+
handleEdit(rowIndex)}
+ >
+
+
+
handleDelete(rowIndex)}
+ >
+
+
+
+
+
+
+ );
+ }
+ return value;
+ }}
+ />
+
+ );
+}
+;
+
+export default AdminUsers;
diff --git a/ipi-survey-platform/src/pages/Admin/configuration/CompanyProfile.jsx b/ipi-survey-platform/src/pages/Admin/configuration/CompanyProfile.jsx
new file mode 100644
index 0000000..a9b27c7
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/configuration/CompanyProfile.jsx
@@ -0,0 +1,1013 @@
+import React from 'react';
+import Table from '../../../components/common/Table';
+import { TextField, SelectField } from '../../../components/common/FormControls';
+import SearchIcon from '../../../assets/images/material-symbols_search-rounded.svg';
+import DownloadIcon from '../../../assets/images/DownloadSimple.svg';
+import AddIcon from '../../../assets/images/ic_baseline-plus.svg';
+import PencilActive from '../../../assets/images/PencilSimple.svg';
+import PencilInactive from '../../../assets/images/PencilSimple - inactive.svg';
+import TrashActive from '../../../assets/images/Trash - active.svg';
+import TrashInactive from '../../../assets/images/Trash - Inactive.svg';
+import DeleteIcon from '../../../assets/images/delete.svg';
+
+const emirateOptions = [
+ { label: 'Abu Dhabi', value: 'Abu Dhabi' },
+ { label: 'Dubai', value: 'Dubai' },
+ { label: 'Sharjah', value: 'Sharjah' },
+ { label: 'Ajman', value: 'Ajman' },
+ { label: 'Umm Al Quwain', value: 'Umm Al Quwain' },
+ { label: 'Ras Al Khaimah', value: 'Ras Al Khaimah' },
+ { label: 'Fujairah', value: 'Fujairah' },
+];
+
+const createEmptyProfile = () => ({
+ establishmentName: '',
+ emirate: '',
+ isicCode: '',
+ establishmentId: '',
+ products: '',
+ totalEmployees: '',
+ createdBy: '',
+ createdOn: '',
+ lastUpdated: '',
+ // Identification particulars
+ permanentFactoryCode: '',
+ uniqueLicenseNumber: '',
+ industryCodeBusiness: '',
+ industryCodeCurrent: '',
+ industryDescription: '',
+ // Establishment contact details
+ contactName: '',
+ contactAddress: '',
+ contactCityTown: '',
+ contactEmirate: '',
+ contactPostalCode: '',
+ contactPoBox: '',
+ contactMakaniNumber: '',
+ contactPersonName: '',
+ contactPersonDesignation: '',
+ contactMobileNumber: '',
+ contactEmail: '',
+ contactWebsite: '',
+ // Corporate / head office contact details
+ corporateSameAs: false,
+ corporateName: '',
+ corporateAddress: '',
+ corporateCityTown: '',
+ corporateEmirate: '',
+ corporatePostalCode: '',
+ corporatePoBox: '',
+ corporateMakaniNumber: '',
+ corporateContactPersonName: '',
+ corporateContactPersonDesignation: '',
+ corporateMobileNumber: '',
+ corporateEmail: '',
+ corporateWebsite: '',
+ // Employment in establishment
+ employmentEmiratiMale: '',
+ employmentNonEmiratiMale: '',
+ employmentEmiratiFemale: '',
+ employmentNonEmiratiFemale: '',
+ employmentTotalEmployees: '',
+ employmentTotalEmirati: '',
+});
+
+const companyProfilesSeed = [
+ {
+ ...createEmptyProfile(),
+ establishmentName: 'CASCADE MARINE FOODS LLC',
+ emirate: 'Sharjah',
+ isicCode: '2098',
+ establishmentId: 'MSCensus-23-04-001276',
+ products: '10',
+ totalEmployees: '300',
+ createdBy: 'Amend (Owner)',
+ createdOn: '18/10/2025',
+ lastUpdated: 'Omar',
+ contactName: 'CASCADE MARINE FOODS LLC',
+ contactEmirate: 'Sharjah',
+ corporateName: 'CASCADE MARINE FOODS LLC',
+ },
+ {
+ ...createEmptyProfile(),
+ establishmentName: 'Khazan Meat Factory',
+ emirate: 'Sharjah',
+ isicCode: '1098',
+ establishmentId: 'MSCensus-23-05-002829',
+ products: '6',
+ totalEmployees: '100',
+ createdBy: 'Sara (Admin)',
+ createdOn: '18/10/2025',
+ lastUpdated: 'Abdul',
+ contactName: 'Khazan Meat Factory',
+ contactEmirate: 'Sharjah',
+ corporateName: 'Khazan Meat Factory',
+ },
+ {
+ ...createEmptyProfile(),
+ establishmentName: 'Spinneys Fresh Food Industries L.L.C',
+ emirate: 'Dubai',
+ isicCode: '3198',
+ establishmentId: 'MSCensus-Dubai-1407',
+ products: '8',
+ totalEmployees: '40',
+ createdBy: 'Shaik (Admin)',
+ createdOn: '18/10/2025',
+ lastUpdated: 'Abdul',
+ contactName: 'Spinneys Fresh Food Industries L.L.C',
+ contactEmirate: 'Dubai',
+ corporateName: 'Spinneys Fresh Food Industries L.L.C',
+ },
+ {
+ ...createEmptyProfile(),
+ establishmentName: 'Freshly Frozen Foods Factory L.L.C',
+ emirate: 'Dubai',
+ isicCode: '2098',
+ establishmentId: 'MSCensus-Dubai-906',
+ products: '4',
+ totalEmployees: '50',
+ createdBy: 'Abdul (Manager)',
+ createdOn: '18/10/2025',
+ lastUpdated: 'Abdul',
+ contactName: 'Freshly Frozen Foods Factory L.L.C',
+ contactEmirate: 'Dubai',
+ corporateName: 'Freshly Frozen Foods Factory L.L.C',
+ },
+ {
+ ...createEmptyProfile(),
+ establishmentName: 'Sahar Food Industry (L.L.C)',
+ emirate: 'Dubai',
+ isicCode: '1095',
+ establishmentId: 'MSCensus-Dubai-472',
+ products: '8',
+ totalEmployees: '80',
+ createdBy: 'Amend (Owner)',
+ createdOn: '18/10/2025',
+ lastUpdated: 'Abdul',
+ contactName: 'Sahar Food Industry (L.L.C)',
+ contactEmirate: 'Dubai',
+ corporateName: 'Sahar Food Industry (L.L.C)',
+ },
+ {
+ ...createEmptyProfile(),
+ establishmentName: 'Continental Food Processing LLC',
+ emirate: 'Ajman',
+ isicCode: '0035',
+ establishmentId: 'MSCensus-23-06-005496',
+ products: '5',
+ totalEmployees: '400',
+ createdBy: 'Sara (Admin)',
+ createdOn: '18/10/2025',
+ lastUpdated: 'Abdul',
+ contactName: 'Continental Food Processing LLC',
+ contactEmirate: 'Ajman',
+ corporateName: 'Continental Food Processing LLC',
+ },
+ {
+ ...createEmptyProfile(),
+ establishmentName: 'Al Khazna Poultry Farm',
+ emirate: 'Abu Dhabi',
+ isicCode: '0986',
+ establishmentId: 'MSCensus-23-05-002320',
+ products: '7',
+ totalEmployees: '800',
+ createdBy: 'Sara (Admin)',
+ createdOn: '18/10/2025',
+ lastUpdated: 'Fathima',
+ contactName: 'Al Khazna Poultry Farm',
+ contactEmirate: 'Abu Dhabi',
+ corporateName: 'Al Khazna Poultry Farm',
+ },
+ {
+ ...createEmptyProfile(),
+ establishmentName: 'Diamond Meat Processing L.L.C',
+ emirate: 'Abu Dhabi',
+ isicCode: '8766',
+ establishmentId: 'MSCensus-23-05-002899',
+ products: '7',
+ totalEmployees: '300',
+ createdBy: 'Sara (Admin)',
+ createdOn: '18/10/2025',
+ lastUpdated: 'Abdul',
+ contactName: 'Diamond Meat Processing L.L.C',
+ contactEmirate: 'Abu Dhabi',
+ corporateName: 'Diamond Meat Processing L.L.C',
+ },
+ {
+ ...createEmptyProfile(),
+ establishmentName: 'Alliance Foods Co L.L.C',
+ emirate: 'Abu Dhabi',
+ isicCode: '9883',
+ establishmentId: 'MSCensus-Dubai-128',
+ products: '7',
+ totalEmployees: '200',
+ createdBy: 'Amend (Owner)',
+ createdOn: '18/10/2025',
+ lastUpdated: 'Abdul',
+ contactName: 'Alliance Foods Co L.L.C',
+ contactEmirate: 'Abu Dhabi',
+ corporateName: 'Alliance Foods Co L.L.C',
+ },
+ {
+ ...createEmptyProfile(),
+ establishmentName: 'Krustasia Foods L.L.C',
+ emirate: 'Dubai',
+ isicCode: '9986',
+ establishmentId: 'MSCensus-23-05-002320',
+ products: '8',
+ totalEmployees: '500',
+ createdBy: 'Sara (Admin)',
+ createdOn: '18/10/2025',
+ lastUpdated: 'Fathima',
+ contactName: 'Krustasia Foods L.L.C',
+ contactEmirate: 'Dubai',
+ corporateName: 'Krustasia Foods L.L.C',
+ },
+];
+
+const CompanyProfile = () => {
+ const [profiles, setProfiles] = React.useState(companyProfilesSeed);
+ const [form, setForm] = React.useState(createEmptyProfile());
+ const [modalMode, setModalMode] = React.useState(null);
+ const [editingRow, setEditingRow] = React.useState(null);
+ const [deletingRow, setDeletingRow] = React.useState(null);
+ const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
+ const [currentPage, setCurrentPage] = React.useState(1);
+ const pageSize = 10;
+ const [searchTerm, setSearchTerm] = React.useState('');
+
+ const headers = [
+ 'Establishment Name',
+ 'Emirates',
+ 'ISIC Code',
+ 'Establishment ID',
+ 'Products',
+ 'Total Employees',
+ 'Created By',
+ 'Created on',
+ 'Last Updated',
+ 'Actions',
+ ];
+
+ const columnWidths = React.useMemo(() => {
+ const base = Array(headers.length).fill(undefined);
+ base[0] = 326; // Establishment Name
+ base[1] = 118; // Emirates
+ base[2] = 132; // ISIC Code
+ base[3] = 208; // Establishment ID
+ base[4] = 100; // Products
+ base[5] = 152; // Total Employees
+ base[6] = 152; // Created By
+ base[7] = 148; // Created on
+ base[8] = 136; // Last Updated
+ base[9] = 119; // Actions
+ return base;
+ }, [headers.length]);
+
+ const contactToCorporateMap = React.useMemo(
+ () => ({
+ contactName: 'corporateName',
+ contactAddress: 'corporateAddress',
+ contactCityTown: 'corporateCityTown',
+ contactEmirate: 'corporateEmirate',
+ contactPostalCode: 'corporatePostalCode',
+ contactPoBox: 'corporatePoBox',
+ contactMakaniNumber: 'corporateMakaniNumber',
+ contactPersonName: 'corporateContactPersonName',
+ contactPersonDesignation: 'corporateContactPersonDesignation',
+ contactMobileNumber: 'corporateMobileNumber',
+ contactEmail: 'corporateEmail',
+ contactWebsite: 'corporateWebsite',
+ }),
+ []
+ );
+
+ const filteredProfiles = React.useMemo(() => {
+ if (!searchTerm.trim()) return profiles;
+ const normalized = searchTerm.toLowerCase();
+ return profiles.filter((item) =>
+ [
+ item.establishmentName,
+ item.emirate,
+ item.isicCode,
+ item.establishmentId,
+ item.createdBy,
+ item.lastUpdated,
+ ]
+ .join(' ')
+ .toLowerCase()
+ .includes(normalized)
+ );
+ }, [profiles, searchTerm]);
+
+ const displayedRows = filteredProfiles.map((item, index) => {
+ const isEditing =
+ editingRow !== null && profiles[editingRow]?.establishmentId === item.establishmentId;
+ const isDeleting =
+ deletingRow !== null && profiles[deletingRow]?.establishmentId === item.establishmentId;
+
+ return [
+ item.establishmentName,
+ item.emirate,
+ item.isicCode,
+ item.establishmentId,
+ item.products,
+ item.totalEmployees,
+ item.createdBy,
+ item.createdOn,
+ item.lastUpdated,
+ (
+
+
handleEdit(index)}
+ >
+
+
+
handleDelete(index)}
+ >
+
+
+
+ ),
+ ];
+ });
+
+ React.useEffect(() => {
+ setCurrentPage(1);
+ }, [searchTerm]);
+
+ const handleEdit = (indexInFiltered) => {
+ const selected = filteredProfiles[indexInFiltered];
+ if (!selected) return;
+ const originalIndex = profiles.findIndex((item) => item.establishmentId === selected.establishmentId);
+ if (originalIndex === -1) return;
+ setEditingRow(originalIndex);
+ const base = createEmptyProfile();
+ const nextForm = {
+ ...base,
+ ...profiles[originalIndex],
+ corporateSameAs: Boolean(profiles[originalIndex]?.corporateSameAs),
+ };
+ if (nextForm.corporateSameAs) {
+ Object.entries(contactToCorporateMap).forEach(([source, target]) => {
+ nextForm[target] = nextForm[source];
+ });
+ }
+ setForm(nextForm);
+ setModalMode('edit');
+ };
+
+ const handleDelete = (indexInFiltered) => {
+ const selected = filteredProfiles[indexInFiltered];
+ if (!selected) return;
+ const originalIndex = profiles.findIndex((item) => item.establishmentId === selected.establishmentId);
+ setDeletingRow(originalIndex);
+ setShowDeleteConfirm(true);
+ };
+
+ const openAddModal = () => {
+ setForm(createEmptyProfile());
+ setEditingRow(null);
+ setModalMode('add');
+ };
+
+ const closeModal = () => {
+ setModalMode(null);
+ setForm(createEmptyProfile());
+ setEditingRow(null);
+ };
+
+ const closeDeleteConfirm = () => {
+ setShowDeleteConfirm(false);
+ setDeletingRow(null);
+ };
+
+ const handleDeleteConfirm = () => {
+ if (deletingRow === null) return;
+ setProfiles((prev) => prev.filter((_, index) => index !== deletingRow));
+ closeDeleteConfirm();
+ };
+
+ const handleFormChange = (field) => (event) => {
+ const isCheckbox = event?.target?.type === 'checkbox';
+ const value = isCheckbox ? event.target.checked : event?.target?.value ?? event;
+ setForm((prev) => {
+ const next = { ...prev, [field]: value };
+
+ if (field === 'corporateSameAs') {
+ if (value) {
+ Object.entries(contactToCorporateMap).forEach(([source, target]) => {
+ next[target] = next[source];
+ });
+ }
+ return next;
+ }
+
+ if (next.corporateSameAs && contactToCorporateMap[field]) {
+ next[contactToCorporateMap[field]] = value;
+ }
+
+ return next;
+ });
+ };
+
+ const getToday = () => {
+ const now = new Date();
+ const day = String(now.getDate()).padStart(2, '0');
+ const month = String(now.getMonth() + 1).padStart(2, '0');
+ const year = now.getFullYear();
+ return `${day}/${month}/${year}`;
+ };
+
+ const handleSaveForm = () => {
+ if (!form.establishmentName || !form.emirate || !form.isicCode || !form.establishmentId) {
+ return;
+ }
+
+ const employmentTotals = ['employmentEmiratiMale', 'employmentNonEmiratiMale', 'employmentEmiratiFemale', 'employmentNonEmiratiFemale'].reduce(
+ (acc, key) => acc + (Number(form[key]) || 0),
+ 0
+ );
+ const totalEmirati = ['employmentEmiratiMale', 'employmentEmiratiFemale'].reduce(
+ (acc, key) => acc + (Number(form[key]) || 0),
+ 0
+ );
+
+ const computedTotalEmployees =
+ form.employmentTotalEmployees || (employmentTotals > 0 ? String(employmentTotals) : form.totalEmployees || '0');
+ const computedTotalEmirati =
+ form.employmentTotalEmirati || (totalEmirati > 0 ? String(totalEmirati) : form.employmentTotalEmirati || '');
+
+ const payload = {
+ ...form,
+ products: form.products || '0',
+ totalEmployees: computedTotalEmployees,
+ employmentTotalEmployees: computedTotalEmployees,
+ employmentTotalEmirati: computedTotalEmirati,
+ createdBy: form.createdBy || 'Admin',
+ createdOn: form.createdOn || getToday(),
+ lastUpdated: form.lastUpdated || 'Admin',
+ corporateSameAs: Boolean(form.corporateSameAs),
+ };
+
+ if (payload.corporateSameAs) {
+ Object.entries(contactToCorporateMap).forEach(([source, target]) => {
+ payload[target] = payload[source];
+ });
+ }
+
+ if (modalMode === 'edit' && editingRow !== null) {
+ setProfiles((prev) =>
+ prev.map((item, index) => (index === editingRow ? { ...item, ...payload, lastUpdated: form.lastUpdated || 'Admin' } : item))
+ );
+ } else {
+ setProfiles((prev) => [{ ...payload }, ...prev]);
+ }
+
+ closeModal();
+ };
+
+ const deletingProfile = React.useMemo(() => {
+ if (deletingRow === null || deletingRow < 0 || deletingRow >= profiles.length) return null;
+ return profiles[deletingRow];
+ }, [deletingRow, profiles]);
+
+ return (
+
+
+
Company Profile
+
+
+
setSearchTerm(event.target.value)}
+ className="w-full h-10 rounded-md border border-[#E5E7EB] pl-10 pr-4 text-sm focus:outline-none"
+ />
+
+
+
+
+ Export CSV
+
+
+
+ Add Profile
+
+
+
+
+
+
+ {modalMode && (
+
+
+
+
+
+ {modalMode === 'edit' ? 'Edit Company Profile' : 'Add Profile'}
+
+
+
+
+
+
+
+
+
+
+
+
Identification Particulars
+
+
+
+
+
+
+
+
+
+
+
+
Establishment Contact Details
+
Provide the primary contact information for the establishment.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Corporate/Head Office Contact Details
+
Provide corporate office details or copy from establishment contact details.
+
+
+
+ Same as Establishment Contact Details
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Employment in Establishment
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Administrative Details
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ {modalMode === 'edit' ? 'Update' : 'Add'}
+
+
+
+
+ )}
+
+ {showDeleteConfirm && deletingProfile && (
+
+
+
+
+
+
+
+
+
+
Delete Profile
+
+ Are you sure you want to delete{' '}
+ {deletingProfile.establishmentName} ?
+
+
+ This action will permanently delete the data and cannot be undone.
+
+
+
+
+
+
+ Cancel
+
+
+ Delete
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default CompanyProfile;
diff --git a/ipi-survey-platform/src/pages/Admin/configuration/EmailTemplates.jsx b/ipi-survey-platform/src/pages/Admin/configuration/EmailTemplates.jsx
new file mode 100644
index 0000000..5fe227c
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/configuration/EmailTemplates.jsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import { SelectField, TextField } from '../../../components/common/FormControls';
+
+const initialTemplate = {
+ subject: 'IPI Survey Submission Reminder - {{quarter}}',
+ body: `Dear {{establishment_name}},
+
+This is a reminder that your IPI survey submission for {{quarter}}
+ is due on {{deadline}}.
+
+Please complete your submission at: {{survey_link}}
+
+Variables available:
+{{establishment_name}},
+{{quarter}}, {{deadline}},
+{{survey_link}}`,
+};
+
+const EmailTemplates = () => {
+ const [selectedTemplate, setSelectedTemplate] = React.useState('reminder');
+ const [form, setForm] = React.useState(initialTemplate);
+
+ const handleSelectChange = (event) => {
+ setSelectedTemplate(event.target.value);
+ setForm(initialTemplate);
+ };
+
+ const handleChange = (key) => (event) => {
+ setForm((prev) => ({ ...prev, [key]: event.target.value }));
+ };
+
+ const handleCancel = () => {
+ setForm(initialTemplate);
+ };
+
+ const handleSave = () => {
+ // Replace with API call
+ console.log('Saving template', selectedTemplate, form);
+ };
+
+ return (
+
+
+
Email Templates
+
+
+
+
+
+
+
+
+
+
+ Permanent Factory Code
+
+
+
+
+
+
+ Cancel
+
+
+ Save
+
+
+
+ );
+};
+
+export default EmailTemplates;
diff --git a/ipi-survey-platform/src/pages/Admin/configuration/Establishment.jsx b/ipi-survey-platform/src/pages/Admin/configuration/Establishment.jsx
new file mode 100644
index 0000000..d7efdab
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/configuration/Establishment.jsx
@@ -0,0 +1,331 @@
+import React from 'react';
+import Table from '../../../components/common/Table';
+import { TextField, SelectField } from '../../../components/common/FormControls';
+import SearchIcon from '../../../assets/images/material-symbols_search-rounded.svg';
+import ExportIcon from '../../../assets/images/DownloadSimple.svg';
+import AddIcon from '../../../assets/images/ic_baseline-plus.svg';
+import PencilActive from '../../../assets/images/PencilSimple.svg';
+import PencilInactive from '../../../assets/images/PencilSimple - inactive.svg';
+import TrashActive from '../../../assets/images/Trash - active.svg';
+import TrashInactive from '../../../assets/images/Trash - Inactive.svg';
+
+const establishmentsSeed = [
+ {
+ name: 'ABC Manufacturing LLC',
+ id: 'MSCensus-23-05-002899',
+ email: 'info@abcmg.com',
+ emirate: 'Abu Dhabi',
+ license: 'CN-1234588',
+ },
+ {
+ name: 'BAC Manufacturing LLC',
+ id: 'MSCensus-23-05-002899',
+ email: 'info@bacmg.com',
+ emirate: 'Dubai',
+ license: 'CN-1234599',
+ },
+ {
+ name: 'AAC Manufacturing LLC',
+ id: 'MSCensus-23-05-002879',
+ email: 'info@aacmg.com',
+ emirate: 'Sharjah',
+ license: 'CN-1234567',
+ },
+ {
+ name: 'CBC Manufacturing LLC',
+ id: 'MSCensus-23-05-002849',
+ email: 'info@cbcmg.com',
+ emirate: 'Ajman',
+ license: 'CN-1234554',
+ },
+];
+
+const createEmptyForm = () => ({
+ name: '',
+ id: '',
+ email: '',
+ emirate: '',
+ license: '',
+ factoryCode: '',
+ industryCode: '',
+ emirateMales: '',
+ emirateFemales: '',
+ totalEmployees: '',
+});
+
+const emirateOptions = [
+ { label: 'Abu Dhabi', value: 'Abu Dhabi' },
+ { label: 'Dubai', value: 'Dubai' },
+ { label: 'Sharjah', value: 'Sharjah' },
+ { label: 'Ajman', value: 'Ajman' },
+ { label: 'Umm Al Quwain', value: 'Umm Al Quwain' },
+ { label: 'Ras Al Khaimah', value: 'Ras Al Khaimah' },
+ { label: 'Fujairah', value: 'Fujairah' },
+];
+
+const Establishment = () => {
+ const [establishments, setEstablishments] = React.useState(establishmentsSeed);
+ const [editingRow, setEditingRow] = React.useState(null);
+ const [deletingRow, setDeletingRow] = React.useState(null);
+ const [showModal, setShowModal] = React.useState(false);
+ const [form, setForm] = React.useState(createEmptyForm());
+
+ const headers = [
+ 'Establishment Name',
+ 'Establishment ID',
+ 'Email',
+ 'Emirate',
+ 'License Number',
+ 'Actions',
+ ];
+
+ const rows = establishments.map((item) => [
+ item.name,
+ item.id,
+ item.email,
+ item.emirate,
+ item.license,
+ 'actions',
+ ]);
+
+ const handleEdit = (index) => {
+ setEditingRow(index);
+ // Future: open edit modal
+ };
+
+ const handleDelete = (index) => {
+ setDeletingRow(index);
+ // Future: open delete confirmation
+ };
+
+ const openModal = () => {
+ setEditingRow(null);
+ setDeletingRow(null);
+ setForm(createEmptyForm());
+ setShowModal(true);
+ };
+
+ const closeModal = () => {
+ setShowModal(false);
+ };
+
+ const handleChange = (field) => (event) => {
+ const { value } = event.target;
+ setForm((prev) => ({ ...prev, [field]: value }));
+ };
+
+ const handleSave = () => {
+ setEstablishments((prev) => [
+ ...prev,
+ {
+ name: form.name,
+ id: form.id,
+ email: form.email,
+ emirate: form.emirate,
+ license: form.license,
+ factoryCode: form.factoryCode,
+ industryCode: form.industryCode,
+ emirateMales: form.emirateMales,
+ emirateFemales: form.emirateFemales,
+ totalEmployees: form.totalEmployees,
+ },
+ ]);
+ closeModal();
+ };
+
+ const tableToolbar = (
+
+
Establishment
+
+
+
+
+
+
+
+
+ Export CSV
+
+
+
+ Add Establishment
+
+
+
+
+ );
+
+ return (
+ <>
+ {
+ if (colIndex === 5) {
+ return (
+
+
handleEdit(rowIndex)}
+ >
+
+
+
handleDelete(rowIndex)}
+ >
+
+
+
+ );
+ }
+ if (colIndex === 2) {
+ return (
+
+ {value}
+
+ );
+ }
+ return value;
+ }}
+ />
+
+ {showModal && (
+
+
+
event.stopPropagation()}
+ >
+
+
Add Establishment
+
+ ×
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ Add Establishment
+
+
+
+
+ )}
+ >
+ );
+};
+
+export default Establishment;
diff --git a/ipi-survey-platform/src/pages/Admin/configuration/IsicHsCodes.jsx b/ipi-survey-platform/src/pages/Admin/configuration/IsicHsCodes.jsx
new file mode 100644
index 0000000..fdef260
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/configuration/IsicHsCodes.jsx
@@ -0,0 +1,392 @@
+import React from 'react';
+import Table from '../../../components/common/Table';
+import DownloadIcon from '../../../assets/images/DownloadSimple.svg';
+import UploadImportIcon from '../../../assets/images/Upload-import.svg';
+import AddIcon from '../../../assets/images/ic_baseline-plus.svg';
+import PencilActive from '../../../assets/images/PencilSimple.svg';
+import PencilInactive from '../../../assets/images/PencilSimple - inactive.svg';
+import TrashActive from '../../../assets/images/Trash - active.svg';
+import TrashInactive from '../../../assets/images/Trash - Inactive.svg';
+import DeleteIcon from '../../../assets/images/delete.svg';
+import StatusBadge from '../../../components/common/StatusBadge';
+import { TextField, SelectField } from '../../../components/common/FormControls';
+
+const createEmptyCodeForm = () => ({
+ code: '',
+ product: '',
+ unit: '',
+ status: '',
+});
+
+const IsicHsCodes = () => {
+ const [rowsData, setRowsData] = React.useState([
+ { code: '0111', product: 'Wheat', unit: 'Kg', estimatedMapped: 18, updated: '31/03/2025', status: 'Active' },
+ { code: '0112', product: 'Barley', unit: 'Kg', estimatedMapped: 22, updated: '15/05/2025', status: 'Active' },
+ { code: '0113', product: 'Oats', unit: 'Kg', estimatedMapped: 20, updated: '10/06/2025', status: 'Active' },
+ { code: '0114', product: 'Rice', unit: 'Kg', estimatedMapped: 25, updated: '01/07/2025', status: 'Active' },
+ { code: '0115', product: 'Corn', unit: 'Kg', estimatedMapped: 30, updated: '20/08/2025', status: 'Active' },
+ { code: '0116', product: 'Soybeans', unit: 'Kg', estimatedMapped: 28, updated: '15/09/2025', status: 'Active' },
+ { code: '0117', product: 'Sorghum', unit: 'Kg', estimatedMapped: 19, updated: '12/10/2025', status: 'Active' },
+ { code: '0118', product: 'Millet', unit: 'Kg', estimatedMapped: 21, updated: '18/11/2025', status: 'Active' },
+ { code: '0119', product: 'Quinoa', unit: 'Kg', estimatedMapped: 27, updated: '25/12/2025', status: 'Active' },
+ { code: '0120', product: 'Rye', unit: 'Kg', estimatedMapped: 16, updated: '04/01/2026', status: 'Active' },
+ { code: '0121', product: 'Buckwheat', unit: 'Kg', estimatedMapped: 12, updated: '18/01/2026', status: 'Active' },
+ { code: '0122', product: 'Barley Malt', unit: 'Tones', estimatedMapped: 14, updated: '05/02/2026', status: 'Inactive' },
+ { code: '0123', product: 'Chickpeas', unit: 'Kg', estimatedMapped: 23, updated: '20/02/2026', status: 'Active' },
+ ]);
+ const [editingRow, setEditingRow] = React.useState(null);
+ const [deletingRow, setDeletingRow] = React.useState(null);
+ const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
+ const [currentPage, setCurrentPage] = React.useState(1);
+ const pageSize = 10;
+ const [modalMode, setModalMode] = React.useState(null);
+ const [form, setForm] = React.useState(createEmptyCodeForm());
+
+ const headers = ['Code', 'Product Name', 'Unit', 'Est mapped', 'Last Updated', 'Status', 'Actions'];
+ const rows = rowsData.map((item) => [
+ item.code,
+ item.product,
+ item.unit,
+ item.estimatedMapped,
+ item.updated,
+ ,
+ 'actions',
+ ]);
+
+ const handleEdit = (index) => {
+ const selected = rowsData[index];
+ if (!selected) return;
+ setEditingRow(index);
+ setForm({
+ code: selected.code,
+ product: selected.product,
+ unit: selected.unit,
+ status: selected.status,
+ });
+ setModalMode('edit');
+ };
+
+ const handleDelete = (index) => {
+ setDeletingRow(index);
+ setShowDeleteConfirm(true);
+ };
+
+ const deletingCode = React.useMemo(() => {
+ if (deletingRow === null || deletingRow < 0 || deletingRow >= rowsData.length) return null;
+ return rowsData[deletingRow];
+ }, [deletingRow, rowsData]);
+
+ const closeDeleteConfirm = () => {
+ setShowDeleteConfirm(false);
+ setDeletingRow(null);
+ };
+
+ const handleDeleteConfirm = () => {
+ if (deletingRow !== null) {
+ setRowsData((prev) => prev.filter((_, index) => index !== deletingRow));
+ closeDeleteConfirm();
+ }
+ };
+
+ const filteredRows = rowsData;
+
+ const unitOptions = React.useMemo(
+ () => [
+ { label: 'Kilograms (Kg)', value: 'Kg' },
+ { label: 'Tones', value: 'Tones' },
+ { label: 'Liters', value: 'Liters' },
+ { label: 'Pounds (Lb)', value: 'Lb' },
+ ],
+ []
+ );
+
+ const statusOptions = React.useMemo(
+ () => [
+ { label: 'Active', value: 'Active' },
+ { label: 'Inactive', value: 'Inactive' },
+ ],
+ []
+ );
+
+ const openAddModal = () => {
+ setEditingRow(null);
+ setForm(createEmptyCodeForm());
+ setModalMode('add');
+ };
+
+ const closeModal = () => {
+ setModalMode(null);
+ setEditingRow(null);
+ setForm(createEmptyCodeForm());
+ };
+
+ const handleFormChange = (field) => (event) => {
+ const value = event?.target?.value ?? event;
+ setForm((prev) => ({ ...prev, [field]: value }));
+ };
+
+ const getToday = () => {
+ const now = new Date();
+ const day = String(now.getDate()).padStart(2, '0');
+ const month = String(now.getMonth() + 1).padStart(2, '0');
+ const year = now.getFullYear();
+ return `${day}/${month}/${year}`;
+ };
+
+ const handleSaveForm = () => {
+ if (!form.code || !form.product || !form.unit || !form.status) {
+ return;
+ }
+ if (modalMode === 'edit' && editingRow !== null) {
+ setRowsData((prev) =>
+ prev.map((item, index) =>
+ index === editingRow
+ ? {
+ ...item,
+ code: form.code,
+ product: form.product,
+ unit: form.unit,
+ status: form.status,
+ }
+ : item
+ )
+ );
+ } else {
+ setRowsData((prev) => [
+ {
+ code: form.code,
+ product: form.product,
+ unit: form.unit,
+ estimatedMapped: 0,
+ updated: getToday(),
+ status: form.status,
+ },
+ ...prev,
+ ]);
+ }
+ closeModal();
+ };
+
+ const handleResetSelection = () => {
+ setEditingRow(null);
+ setDeletingRow(null);
+ };
+
+ React.useEffect(() => handleResetSelection, []);
+
+ return (
+
+
+
HS Codes
+
+
+
+ Import CSV
+
+
+
+ Export CSV
+
+
+
+ Add Code
+
+
+
+
+
{
+ if (colIndex === 6) {
+ return (
+
+
handleEdit(rowIndex)}
+ >
+
+
+
handleDelete(rowIndex)}
+ >
+
+
+
+ );
+ }
+
+ return value;
+ }}
+ pagination={{
+ currentPage,
+ onPageChange: setCurrentPage,
+ pageSize,
+ totalItems: filteredRows.length,
+ }}
+ />
+
+ {modalMode && (
+
+
+
+
+
+ {modalMode === 'edit' ? 'Edit HS Codes' : 'Add HS Codes'}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ {modalMode === 'edit' ? 'Update' : 'Add'}
+
+
+
+
+
+ )}
+
+ {showDeleteConfirm && (
+
+
+
+
+
+
+
+
+
+
Delete HS Codes
+
+ Are you sure you want to delete{' '}
+ {deletingCode && (
+
+ {`${deletingCode.code} - ${deletingCode.product}`}
+
+ )}
+ ?
+
+
+ This action will permanently delete the data and cannot be undone.
+
+
+
+
+
+
+ Cancel
+
+
+ Delete
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default IsicHsCodes;
diff --git a/ipi-survey-platform/src/pages/Admin/configuration/QuarterlyWindows.jsx b/ipi-survey-platform/src/pages/Admin/configuration/QuarterlyWindows.jsx
new file mode 100644
index 0000000..9ab5737
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/configuration/QuarterlyWindows.jsx
@@ -0,0 +1,632 @@
+import React from 'react';
+import Table from '../../../components/common/Table';
+import DownloadIcon from '../../../assets/images/DownloadSimple.svg';
+import AddIcon from '../../../assets/images/ic_baseline-plus.svg';
+import PencilActive from '../../../assets/images/PencilSimple.svg';
+import PencilInactive from '../../../assets/images/PencilSimple - inactive.svg';
+import TrashActive from '../../../assets/images/Trash - active.svg';
+import TrashInactive from '../../../assets/images/Trash - Inactive.svg';
+import DeleteIcon from '../../../assets/images/delete.svg';
+import CaretDownActive from '../../../assets/images/CaretDown - active.svg';
+import SearchIcon from '../../../assets/images/material-symbols_search-rounded.svg';
+import CaretDown from '../../../assets/images/CaretDown-black.svg';
+import { TextField, SelectField, DateField } from '../../../components/common/FormControls';
+import StatusBadge from '../../../components/common/StatusBadge';
+
+const createEmptyQuarterForm = () => ({
+ establishment: '',
+ year: '',
+ quarter: '',
+ startDate: '',
+ endDate: '',
+ gracePeriod: '',
+ submissionCount: '0',
+ status: '',
+});
+
+const QuarterlyWindows = () => {
+ const [quarterData, setQuarterData] = React.useState([
+ {
+ establishment: 'Cascade Marine Foods LLC',
+ year: '2025',
+ quarter: 'Q1',
+ startDate: '11/12/2025',
+ endDate: '31/03/2025',
+ gracePeriod: '15 days',
+ submissionCount: '0',
+ status: 'Active',
+ },
+ {
+ establishment: 'Khazan Meat Factory',
+ year: '2025',
+ quarter: 'Q1',
+ startDate: '11/12/2025',
+ endDate: '31/03/2025',
+ gracePeriod: '15 days',
+ submissionCount: '40',
+ status: 'Active',
+ },
+ {
+ establishment: 'Spinneys Fresh Food Industries L.L.C',
+ year: '2025',
+ quarter: 'Q1',
+ startDate: '11/12/2025',
+ endDate: '31/03/2025',
+ gracePeriod: '15 days',
+ submissionCount: '30',
+ status: 'Active',
+ },
+ {
+ establishment: 'Freshly Frozen Foods Factory L.L.C',
+ year: '2025',
+ quarter: 'Q1',
+ startDate: '11/12/2025',
+ endDate: '31/03/2025',
+ gracePeriod: '15 days',
+ submissionCount: '15',
+ status: 'Active',
+ },
+ {
+ establishment: 'Sahar Food Industry (L.L.C)',
+ year: '2025',
+ quarter: 'Q1',
+ startDate: '11/12/2025',
+ endDate: '31/03/2025',
+ gracePeriod: '15 days',
+ submissionCount: '25',
+ status: 'Active',
+ },
+ {
+ establishment: 'Continental Food Processing LLC',
+ year: '2025',
+ quarter: 'Q1',
+ startDate: '11/12/2025',
+ endDate: '31/03/2025',
+ gracePeriod: '15 days',
+ submissionCount: '60',
+ status: 'Inactive',
+ },
+ {
+ establishment: 'Al Khazna Poultry Farm',
+ year: '2025',
+ quarter: 'Q1',
+ startDate: '11/12/2025',
+ endDate: '31/03/2025',
+ gracePeriod: '15 days',
+ submissionCount: '15',
+ status: 'Active',
+ },
+ {
+ establishment: 'Diamond Meat Processing L.L.C',
+ year: '2025',
+ quarter: 'Q1',
+ startDate: '11/12/2025',
+ endDate: '31/03/2025',
+ gracePeriod: '15 days',
+ submissionCount: '10',
+ status: 'Active',
+ },
+ {
+ establishment: 'Alliance Foods Co L.L.C',
+ year: '2025',
+ quarter: 'Q1',
+ startDate: '11/12/2025',
+ endDate: '31/03/2025',
+ gracePeriod: '15 days',
+ submissionCount: '44',
+ status: 'Active',
+ },
+ {
+ establishment: 'Krustasia Foods L.L.C',
+ year: '2024',
+ quarter: 'Q4',
+ startDate: '14/10/2024',
+ endDate: '31/12/2024',
+ gracePeriod: '15 days',
+ submissionCount: '25',
+ status: 'Inactive',
+ },
+ {
+ establishment: 'Gulf Coast Seafood Exports',
+ year: '2025',
+ quarter: 'Q2',
+ startDate: '01/04/2025',
+ endDate: '30/06/2025',
+ gracePeriod: '15 days',
+ submissionCount: '32',
+ status: 'Active',
+ },
+ {
+ establishment: 'Emirates Food Logistics',
+ year: '2025',
+ quarter: 'Q2',
+ startDate: '01/04/2025',
+ endDate: '30/06/2025',
+ gracePeriod: '15 days',
+ submissionCount: '18',
+ status: 'Active',
+ },
+ {
+ establishment: 'Global Seafood Traders',
+ year: '2025',
+ quarter: 'Q2',
+ startDate: '01/04/2025',
+ endDate: '30/06/2025',
+ gracePeriod: '15 days',
+ submissionCount: '22',
+ status: 'Active',
+ },{
+ establishment: 'Global Seafood Traders',
+ year: '2025',
+ quarter: 'Q2',
+ startDate: '01/04/2025',
+ endDate: '30/06/2025',
+ gracePeriod: '15 days',
+ status: 'Active',
+ },
+ {
+ establishment: 'Global Seafood Traders',
+ year: '2025',
+ quarter: 'Q2',
+ startDate: '01/04/2025',
+ endDate: '30/06/2025',
+ gracePeriod: '15 days',
+ status: 'Active',
+ },
+ {
+ establishment: 'Global Seafood Traders',
+ year: '2025',
+ quarter: 'Q2',
+ startDate: '01/04/2025',
+ endDate: '30/06/2025',
+ gracePeriod: '15 days',
+ status: 'Active',
+ },
+ {
+ establishment: 'Global Seafood Traders',
+ year: '2025',
+ quarter: 'Q2',
+ startDate: '01/04/2025',
+ endDate: '30/06/2025',
+ gracePeriod: '15 days',
+ status: 'Active',
+ },
+ ]);
+ const [editingRow, setEditingRow] = React.useState(null);
+ const [deletingRow, setDeletingRow] = React.useState(null);
+ const [modalMode, setModalMode] = React.useState(null);
+ const [form, setForm] = React.useState(createEmptyQuarterForm());
+ const [originalForm, setOriginalForm] = React.useState(null);
+ const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
+ const [searchTerm, setSearchTerm] = React.useState('');
+ const [statusFilter, setStatusFilter] = React.useState('all');
+ const [hoveredEdit, setHoveredEdit] = React.useState(null);
+ const [hoveredDelete, setHoveredDelete] = React.useState(null);
+ const [currentPage, setCurrentPage] = React.useState(1);
+ const pageSize = 10;
+
+ const deletingQuarter = React.useMemo(() => {
+ if (deletingRow === null || deletingRow < 0 || deletingRow >= quarterData.length) {
+ return null;
+ }
+ return quarterData[deletingRow];
+ }, [deletingRow, quarterData]);
+
+ const headers = ['Year', 'Quarter', 'Start Date', 'End Date', 'Grace Period', 'Submission Count', 'Status', 'Actions'];
+
+ const filteredRows = React.useMemo(() => {
+ const term = searchTerm.trim().toLowerCase();
+ const statusValue = statusFilter.toLowerCase();
+ return quarterData.filter((item) => {
+ const matchesStatus =
+ statusValue === 'all' || String(item.status).toLowerCase() === statusValue;
+ if (!matchesStatus) return false;
+ if (!term) return true;
+ const haystack = [
+ item.establishment,
+ item.year,
+ item.quarter,
+ item.startDate,
+ item.endDate,
+ item.gracePeriod,
+ item.submissionCount,
+ item.status,
+ ]
+ .map((val) => String(val || '').toLowerCase())
+ .join(' ');
+ return haystack.includes(term);
+ });
+ }, [quarterData, searchTerm, statusFilter]);
+
+ const rows = filteredRows.map((item) => [
+ item.year,
+ item.quarter,
+ item.startDate,
+ item.endDate,
+ item.gracePeriod,
+ item.submissionCount,
+ ,
+ 'actions',
+ ]);
+
+ const statusOptions = React.useMemo(
+ () => [
+ { label: 'All', value: 'all' },
+ { label: 'Active', value: 'Active' },
+ { label: 'Inactive', value: 'Inactive' },
+ ],
+ []
+ );
+
+ const openAddModal = () => {
+ setForm(createEmptyQuarterForm());
+ setOriginalForm(null);
+ setEditingRow(null);
+ setModalMode('add');
+ };
+
+ React.useEffect(() => {
+ const maxPage = Math.max(1, Math.ceil(filteredRows.length / pageSize));
+ setCurrentPage((prev) => Math.min(prev, maxPage));
+ }, [filteredRows.length, pageSize]);
+
+ const openEditModal = (index) => {
+ const selected = quarterData[index];
+ setEditingRow(index);
+ setForm({ ...selected });
+ setOriginalForm({ ...selected });
+ setModalMode('edit');
+ };
+
+ const handleCloseModal = () => {
+ setModalMode(null);
+ setEditingRow(null);
+ };
+
+ const handleReset = () => {
+ if (modalMode === 'edit' && editingRow !== null) {
+ if (originalForm) {
+ setForm({ ...originalForm });
+ } else {
+ const fallback = quarterData[editingRow];
+ if (fallback) {
+ setForm({ ...fallback });
+ }
+ }
+ } else {
+ setForm(createEmptyQuarterForm());
+ }
+ };
+
+ const openDeleteConfirm = (index) => {
+ setDeletingRow(index);
+ setShowDeleteConfirm(true);
+ };
+
+ const closeDeleteConfirm = () => {
+ setShowDeleteConfirm(false);
+ setDeletingRow(null);
+ };
+
+ const handleSave = () => {
+ if (modalMode === 'add') {
+ setQuarterData((prev) => [...prev, form]);
+ } else if (modalMode === 'edit' && editingRow !== null) {
+ setQuarterData((prev) => prev.map((item, idx) => (idx === editingRow ? form : item)));
+ }
+ handleCloseModal();
+ };
+
+ const handleDelete = () => {
+ if (deletingRow !== null) {
+ setQuarterData((prev) => prev.filter((_, index) => index !== deletingRow));
+ closeDeleteConfirm();
+ }
+ };
+
+ return (
+
+
+
+
Quarterly Windows
+
+
+
setSearchTerm(e.target.value)}
+ placeholder="Search Establishment"
+ className="h-10 w-full rounded-[4px] border border-[#C3C6CB] pl-10 pr-3 text-sm text-[#232528] focus:outline-none"
+ />
+
+
+
+
setStatusFilter(e.target.value)}
+ className="w-full appearance-none rounded-[4px] border border-[#C3C6CB] bg-white text-sm text-[#232528] focus:outline-none cursor-pointer h-10 pl-[12px] pr-[36px] py-[10px]"
+ >
+ {statusOptions.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+
+
{
+ if (!filteredRows.length) return;
+ const csvHeader = headers.slice(0, headers.length - 1).join(',');
+ const csvRows = filteredRows.map((item) =>
+ [item.year, item.quarter, item.startDate, item.endDate, item.gracePeriod, item.submissionCount, item.status]
+ .map((value) => `"${String(value ?? '').replace(/"/g, '""')}"`)
+ .join(',')
+ );
+ const blob = new Blob([csvHeader + '\n' + csvRows.join('\n')], {
+ type: 'text/csv;charset=utf-8;',
+ });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', 'quarterly-windows.csv');
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ }}
+ className={`h-10 px-5 rounded-[6px] border text-sm inline-flex items-center gap-3 ${
+ filteredRows.length
+ ? 'bg-[#F7F7F7] border-[#C3C6CB] text-[#232528]'
+ : 'bg-[#F3F4F6] border-[#E5E7EB] text-[#9CA3AF] cursor-not-allowed'
+ }`}
+ disabled={!filteredRows.length}
+ style={{ minWidth: '143px' }}
+ >
+
+ Export CSV
+
+
+
+ Add Quarter
+
+
+
+
+
+
{
+ if (colIndex === headers.length - 1) {
+ return (
+
+
openEditModal(rowIndex)}
+ onMouseEnter={() => setHoveredEdit(rowIndex)}
+ onMouseLeave={() => setHoveredEdit(null)}
+ >
+
+
+
openDeleteConfirm(rowIndex)}
+ onMouseEnter={() => setHoveredDelete(rowIndex)}
+ onMouseLeave={() => setHoveredDelete(null)}
+ >
+
+
+
+ );
+ }
+ return value;
+ }}
+ pagination={{
+ currentPage,
+ onPageChange: setCurrentPage,
+ pageSize,
+ totalItems: filteredRows.length,
+ }}
+ />
+
+ {modalMode && (
+
+
+
+
+
+ {modalMode === 'edit' ? 'Edit Quarter' : 'Add Quarter'}
+
+
+ {modalMode === 'edit' && (
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ setForm({ ...form, year: e.target.value })}
+ options={Array.from({ length: 6 }).map((_, idx) => {
+ const yr = 2023 + idx;
+ return { label: String(yr), value: String(yr) };
+ })}
+ placeholder="Select Year"
+ />
+ setForm({ ...form, quarter: e.target.value })}
+ options={['Q1', 'Q2', 'Q3', 'Q4'].map((q) => ({ label: q, value: q }))}
+ placeholder="Select Quarter"
+ />
+ setForm({ ...form, startDate: e.target.value })}
+ placeholder="Select Start Date"
+ />
+ setForm({ ...form, endDate: e.target.value })}
+ placeholder="Select End Date"
+ />
+ setForm({ ...form, gracePeriod: e.target.value })}
+ options={['5 days', '10 days', '15 days', '20 days', '30 days'].map((item) => ({
+ label: item,
+ value: item,
+ }))}
+ placeholder="Select Grace Period"
+ />
+ setForm({ ...form, status: e.target.value })}
+ options={[
+ { label: 'Active', value: 'Active' },
+ { label: 'Inactive', value: 'Inactive' },
+ ]}
+ placeholder="Select Status"
+ />
+
+
+
+
+ Cancel
+
+
+ {modalMode === 'edit' ? 'Update Quarter' : 'Add'}
+
+
+
+
+
+ )}
+
+ {showDeleteConfirm && (
+
+
+
+
+
+
+
+
+
+
Delete Quarter
+
+ Are you sure you want to delete{' '}
+ {deletingQuarter && (
+
+ {`${deletingQuarter.year}-${deletingQuarter.quarter}`}
+
+ )}
+ ?
+
+
+ This action will permanently delete the data and cannot be undone.
+
+
+
+
+
+
+ Cancel
+
+
+ Delete
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default QuarterlyWindows;
diff --git a/ipi-survey-platform/src/pages/Admin/configuration/SubmissionDeadlines.jsx b/ipi-survey-platform/src/pages/Admin/configuration/SubmissionDeadlines.jsx
new file mode 100644
index 0000000..3c3b2a0
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/configuration/SubmissionDeadlines.jsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import { TextField } from '../../../components/common/FormControls';
+
+const SubmissionDeadlines = () => {
+ const [form, setForm] = React.useState({
+ standardDeadline: '30',
+ gracePeriod: '15',
+ firstReminder: '14',
+ finalReminder: '3',
+ });
+
+ const handleChange = (key) => (event) => {
+ setForm((prev) => ({ ...prev, [key]: event.target.value }));
+ };
+
+ const handleCancel = () => {
+ setForm({
+ standardDeadline: '30',
+ gracePeriod: '15',
+ firstReminder: '14',
+ finalReminder: '3',
+ });
+ };
+
+ const handleSave = () => {
+ // Future: Replace with save logic
+ console.log('Saving submission deadlines', form);
+ };
+
+ return (
+
+
+
Submission Deadlines
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ Save
+
+
+
+ );
+};
+
+export default SubmissionDeadlines;
diff --git a/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx b/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx
new file mode 100644
index 0000000..886eb38
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx
@@ -0,0 +1,547 @@
+import React from 'react';
+import Table from '../../../components/common/Table';
+import DownloadIcon from '../../../assets/images/DownloadSimple.svg';
+import AddIcon from '../../../assets/images/ic_baseline-plus.svg';
+import PencilActive from '../../../assets/images/PencilSimple.svg';
+import PencilInactive from '../../../assets/images/PencilSimple - inactive.svg';
+import TrashActive from '../../../assets/images/Trash - active.svg';
+import TrashInactive from '../../../assets/images/Trash - Inactive.svg';
+import DeleteIcon from '../../../assets/images/delete.svg';
+import StatusBadge from '../../../components/common/StatusBadge';
+import { TextField, SelectField } from '../../../components/common/FormControls';
+import CaretDownIcon from '../../../assets/images/CaretDown - active.svg';
+import RectangleIcon from '../../../assets/images/Rectangle.svg';
+import CheckboxIcon from '../../../assets/images/checkbox.svg';
+
+const createEmptyUnitForm = () => ({
+ unitName: '',
+ description: '',
+ productsMapped: [],
+ status: '',
+});
+
+const UnitMaster = () => {
+ const [units, setUnits] = React.useState([
+ {
+ unitName: 'Kg',
+ description: 'Kilogram',
+ productsMapped: ['Wheat', 'Barley', 'Oats', 'Corn', 'Soybeans'],
+ createdBy: 'Akbar',
+ createdOn: '25/03/2025',
+ lastUpdated: '31/03/2025',
+ status: 'Active',
+ },
+ {
+ unitName: 'Lt',
+ description: 'Liters',
+ productsMapped: ['Rice', 'Sorghum', 'Millet', 'Quinoa', 'Buckwheat', 'Corn'],
+ createdBy: 'Zara',
+ createdOn: '01/04/2025',
+ lastUpdated: '10/04/2025',
+ status: 'Active',
+ },
+ {
+ unitName: 'Kg',
+ description: 'Kilogram',
+ productsMapped: ['Rice', 'Oats'],
+ createdBy: 'Ravi',
+ createdOn: '15/05/2025',
+ lastUpdated: '25/05/2025',
+ status: 'Inactive',
+ },
+ {
+ unitName: 'Lt',
+ description: 'Liters',
+ productsMapped: ['Wheat', 'Barley', 'Corn', 'Soybeans', 'Millet', 'Quinoa', 'Sorghum'],
+ createdBy: 'Maya',
+ createdOn: '10/06/2025',
+ lastUpdated: '20/06/2025',
+ status: 'Active',
+ },
+ {
+ unitName: 'Kg',
+ description: 'Kilogram',
+ productsMapped: ['Buckwheat'],
+ createdBy: 'Jin',
+ createdOn: '30/07/2025',
+ lastUpdated: '05/08/2025',
+ status: 'Active',
+ },
+ {
+ unitName: 'Lt',
+ description: 'Liters',
+ productsMapped: ['Rice', 'Quinoa', 'Millet', 'Corn'],
+ createdBy: 'Leo',
+ createdOn: '12/09/2025',
+ lastUpdated: '20/09/2025',
+ status: 'Inactive',
+ },
+ {
+ unitName: 'Kg',
+ description: 'Kilogram',
+ productsMapped: ['Wheat', 'Soybeans', 'Corn', 'Sorghum'],
+ createdBy: 'Lila',
+ createdOn: '05/10/2025',
+ lastUpdated: '15/10/2025',
+ status: 'Active',
+ },
+ {
+ unitName: 'Lt',
+ description: 'Liters',
+ productsMapped: ['Barley', 'Oats', 'Millet'],
+ createdBy: 'Nina',
+ createdOn: '30/07/2025',
+ lastUpdated: '05/08/2025',
+ status: 'Inactive',
+ },
+ {
+ unitName: 'Kg',
+ description: 'Kilogram',
+ productsMapped: ['Rice', 'Corn', 'Soybeans', 'Quinoa', 'Millet', 'Sorghum'],
+ createdBy: 'Owen',
+ createdOn: '10/06/2025',
+ lastUpdated: '20/06/2025',
+ status: 'Inactive',
+ },
+ {
+ unitName: 'Lt',
+ description: 'Liters',
+ productsMapped: ['Wheat', 'Barley'],
+ createdBy: 'Riya',
+ createdOn: '28/03/2025',
+ lastUpdated: '31/03/2025',
+ status: 'Inactive',
+ },
+ ]);
+ const [editingRow, setEditingRow] = React.useState(null);
+ const [deletingRow, setDeletingRow] = React.useState(null);
+ const [modalMode, setModalMode] = React.useState(null);
+ const [currentPage, setCurrentPage] = React.useState(1);
+ const pageSize = 10;
+ const [form, setForm] = React.useState(createEmptyUnitForm());
+ const [isProductDropdownOpen, setIsProductDropdownOpen] = React.useState(false);
+ const productDropdownRef = React.useRef(null);
+
+ const headers = [
+ 'Unit Name',
+ 'Description',
+ 'Mapped Products',
+ 'Created By',
+ 'Created On',
+ 'Last Updated',
+ 'Status',
+ 'Actions',
+ ];
+
+ const rows = units.map((item) => [
+ item.unitName,
+ item.description,
+ Array.isArray(item.productsMapped) ? item.productsMapped.length : item.productsMapped,
+ item.createdBy,
+ item.createdOn,
+ item.lastUpdated,
+ ,
+ 'actions',
+ ]);
+
+ const statusOptions = React.useMemo(
+ () => [
+ { label: 'Active', value: 'Active' },
+ { label: 'Inactive', value: 'Inactive' },
+ ],
+ []
+ );
+
+ const productOptions = React.useMemo(
+ () => [
+ { label: 'Wheat', value: 'Wheat' },
+ { label: 'Barley', value: 'Barley' },
+ { label: 'Oats', value: 'Oats' },
+ { label: 'Rice', value: 'Rice' },
+ { label: 'Corn', value: 'Corn' },
+ { label: 'Soybeans', value: 'Soybeans' },
+ { label: 'Sorghum', value: 'Sorghum' },
+ { label: 'Millet', value: 'Millet' },
+ { label: 'Quinoa', value: 'Quinoa' },
+ { label: 'Buckwheat', value: 'Buckwheat' },
+ ],
+ []
+ );
+
+ const filteredRows = units;
+
+ const openAddModal = () => {
+ setEditingRow(null);
+ setForm(createEmptyUnitForm());
+ setModalMode('add');
+ setIsProductDropdownOpen(false);
+ };
+
+ const handleEdit = (index) => {
+ const selected = units[index];
+ if (!selected) return;
+ setEditingRow(index);
+ setForm({
+ unitName: selected.unitName,
+ description: selected.description,
+ productsMapped: Array.isArray(selected.productsMapped) ? [...selected.productsMapped] : [],
+ status: selected.status,
+ });
+ setModalMode('edit');
+ setIsProductDropdownOpen(false);
+ };
+
+ const handleDelete = (index) => {
+ setDeletingRow(index);
+ };
+
+ const closeModal = () => {
+ setModalMode(null);
+ setForm(createEmptyUnitForm());
+ setEditingRow(null);
+ setIsProductDropdownOpen(false);
+ };
+
+ const handleFormChange = (field) => (event) => {
+ const value = event?.target?.value ?? event;
+ setForm((prev) => ({ ...prev, [field]: value }));
+ };
+
+ const handleProductToggle = (value) => {
+ setForm((prev) => {
+ const current = Array.isArray(prev.productsMapped) ? prev.productsMapped : [];
+ const exists = current.includes(value);
+ return {
+ ...prev,
+ productsMapped: exists ? current.filter((item) => item !== value) : [...current, value],
+ };
+ });
+ };
+
+ React.useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (!productDropdownRef.current) return;
+ if (!productDropdownRef.current.contains(event.target)) {
+ setIsProductDropdownOpen(false);
+ }
+ };
+
+ if (isProductDropdownOpen) {
+ document.addEventListener('mousedown', handleClickOutside);
+ }
+
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [isProductDropdownOpen]);
+
+ const getToday = () => {
+ const now = new Date();
+ const day = String(now.getDate()).padStart(2, '0');
+ const month = String(now.getMonth() + 1).padStart(2, '0');
+ const year = now.getFullYear();
+ return `${day}/${month}/${year}`;
+ };
+
+ const handleSaveForm = () => {
+ const selectedProducts = Array.isArray(form.productsMapped) ? form.productsMapped : [];
+ if (!form.unitName || !form.description || selectedProducts.length === 0 || !form.status) {
+ return;
+ }
+ if (modalMode === 'edit' && editingRow !== null) {
+ setUnits((prev) =>
+ prev.map((item, index) =>
+ index === editingRow
+ ? {
+ ...item,
+ unitName: form.unitName,
+ description: form.description,
+ productsMapped: [...selectedProducts],
+ status: form.status,
+ lastUpdated: getToday(),
+ }
+ : item
+ )
+ );
+ } else {
+ setUnits((prev) => [
+ {
+ unitName: form.unitName,
+ description: form.description,
+ productsMapped: [...selectedProducts],
+ createdBy: 'Admin',
+ createdOn: getToday(),
+ lastUpdated: getToday(),
+ status: form.status,
+ },
+ ...prev,
+ ]);
+ }
+ closeModal();
+ };
+
+ const deletingUnit = React.useMemo(() => {
+ if (deletingRow === null || deletingRow < 0 || deletingRow >= units.length) {
+ return null;
+ }
+ return units[deletingRow];
+ }, [deletingRow, units]);
+
+ const closeDeleteConfirm = () => {
+ setDeletingRow(null);
+ };
+
+ const handleDeleteConfirm = () => {
+ if (deletingRow === null) return;
+ setUnits((prev) => prev.filter((_, index) => index !== deletingRow));
+ closeDeleteConfirm();
+ };
+
+ return (
+
+
+
Unit Master
+
+
+
+ Export CSV
+
+
+
+ Add Unit
+
+
+
+
+
{
+ if (colIndex === headers.length - 1) {
+ return (
+
+
handleEdit(rowIndex)}
+ >
+
+
+
handleDelete(rowIndex)}
+ >
+
+
+
+ );
+ }
+
+ return value;
+ }}
+ pagination={{
+ currentPage,
+ onPageChange: setCurrentPage,
+ pageSize,
+ totalItems: filteredRows.length,
+ }}
+ />
+
+ {modalMode && (
+
+
+
+
+
+ {modalMode === 'edit' ? 'Edit Unit Master' : 'Add Unit Master'}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Mapped Products
+
setIsProductDropdownOpen((prev) => !prev)}
+ >
+
+ {form.productsMapped.length
+ ? `${form.productsMapped.length} product${form.productsMapped.length > 1 ? 's' : ''} selected`
+ : 'Select Products'}
+
+
+
+ {isProductDropdownOpen && (
+
+
+ {productOptions.map((option) => {
+ const checked = form.productsMapped.includes(option.value);
+ return (
+
+ handleProductToggle(option.value)}
+ >
+
+
+
+ {option.label}
+
+
+ );
+ })}
+
+
+ )}
+
+
+
+
+
+
+ Cancel
+
+
+ {modalMode === 'edit' ? 'Update' : 'Add'}
+
+
+
+
+
+ )}
+
+ {deletingUnit && (
+
+
+
+
+
+
+
+
+
+
Delete Unit
+
+ Are you sure you want to delete{' '}
+
+ {`${deletingUnit.unitName} - ${deletingUnit.description}`}
+
+ ?
+
+
+ This action will permanently delete the data and cannot be undone.
+
+
+
+
+
+
+ Cancel
+
+
+ Delete
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default UnitMaster;
diff --git a/ipi-survey-platform/src/pages/ChangePassword/ChangePassword.jsx b/ipi-survey-platform/src/pages/ChangePassword/ChangePassword.jsx
new file mode 100644
index 0000000..6a0188d
--- /dev/null
+++ b/ipi-survey-platform/src/pages/ChangePassword/ChangePassword.jsx
@@ -0,0 +1,221 @@
+import React from 'react';
+import Logo from '../../assets/images/FCSCLogo.svg';
+import { useNavigate } from 'react-router-dom';
+import { changePassword } from '../../services/auth/authService.js';
+
+const ChangePassword = () => {
+ const navigate = useNavigate();
+ const [currentPassword, setCurrentPassword] = React.useState('');
+ const [newPassword, setNewPassword] = React.useState('');
+ const [confirmPassword, setConfirmPassword] = React.useState('');
+ const [showCurrentPassword, setShowCurrentPassword] = React.useState(false);
+ const [showNewPassword, setShowNewPassword] = React.useState(false);
+ const [showConfirmPassword, setShowConfirmPassword] = React.useState(false);
+ const [loading, setLoading] = React.useState(false);
+ const [error, setError] = React.useState('');
+ const [success, setSuccess] = React.useState('');
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+ setError('');
+ setSuccess('');
+
+ if (!currentPassword || !newPassword) {
+ setError('Please fill in all required fields.');
+ return;
+ }
+
+ if (newPassword !== confirmPassword) {
+ setError('New password and confirmation do not match.');
+ return;
+ }
+
+ setLoading(true);
+ try {
+ const response = await changePassword({ currentPassword, newPassword });
+ const status = response?.status ?? 'success';
+ const message = response?.message || 'Password updated successfully. Please sign in again with your new password.';
+ if (status !== 'success') {
+ throw new Error(message);
+ }
+ try {
+ localStorage.removeItem('auth_token');
+ localStorage.removeItem('user_profile');
+ localStorage.removeItem('user_role');
+ } catch (storageError) {
+ console.warn('Unable to clear stored session', storageError);
+ }
+ setSuccess(message);
+ setTimeout(() => {
+ navigate('/login');
+ }, 1500);
+ } catch (err) {
+ const message = err?.response?.data?.message || err?.message || 'Unable to change password.';
+ setError(message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+
Change Password
+
Keep your account secure by updating your password.
+
+
+
+
+
+ );
+};
+
+export default ChangePassword;
diff --git a/ipi-survey-platform/src/pages/Dashboard/Dashboard.jsx b/ipi-survey-platform/src/pages/Dashboard/Dashboard.jsx
new file mode 100644
index 0000000..8336d49
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Dashboard/Dashboard.jsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import { WelcomeSection } from '../../components/dashboard/WelcomeSection';
+import { SurveyStatus } from '../../components/dashboard/SurveyStatus';
+import { SubmissionHistory } from '../../components/dashboard/SubmissionHistory';
+import { fetchEstablishmentDashboard } from '../../services/establishments/establishmentService.js';
+
+const Dashboard = () => {
+ const [dashboardData, setDashboardData] = React.useState(null);
+ const [loading, setLoading] = React.useState(false);
+ const [error, setError] = React.useState('');
+
+ React.useEffect(() => {
+ let cancelled = false;
+ const loadDashboard = async () => {
+ setLoading(true);
+ setError('');
+ try {
+ const response = await fetchEstablishmentDashboard();
+ if (cancelled) return;
+ const payload = response?.data ?? response ?? null;
+ setDashboardData(payload);
+ } catch (err) {
+ if (cancelled) return;
+ if (err?.code === 'ERR_CANCELED') return;
+ const message = err?.response?.data?.message || err?.message || 'Failed to load dashboard data.';
+ setError(message);
+ setDashboardData(null);
+ } finally {
+ if (!cancelled) {
+ setLoading(false);
+ }
+ }
+ };
+
+ loadDashboard();
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ return (
+
+ );
+};
+
+export default Dashboard;
\ No newline at end of file
diff --git a/ipi-survey-platform/src/pages/ForgotPassword/ForgotPassword.jsx b/ipi-survey-platform/src/pages/ForgotPassword/ForgotPassword.jsx
new file mode 100644
index 0000000..99a912b
--- /dev/null
+++ b/ipi-survey-platform/src/pages/ForgotPassword/ForgotPassword.jsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import Logo from '../../assets/images/FCSCLogo.svg';
+import { useNavigate } from 'react-router-dom';
+import { requestPasswordReset } from '../../services/auth/authService.js';
+
+const ForgotPassword = () => {
+ const navigate = useNavigate();
+ const [email, setEmail] = React.useState('');
+ const [loading, setLoading] = React.useState(false);
+ const [error, setError] = React.useState('');
+ const [success, setSuccess] = React.useState('');
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+ setError('');
+ setSuccess('');
+ if (!email.trim()) {
+ setError('Please enter the email associated with your account.');
+ return;
+ }
+
+ setLoading(true);
+ try {
+ const response = await requestPasswordReset({ email });
+ const message = response?.message || 'If an account exists for this email, reset instructions have been sent.';
+ const status = response?.status ?? 'success';
+ if (status !== 'success') {
+ throw new Error(message);
+ }
+ setSuccess(message);
+ } catch (err) {
+ const message = err?.response?.data?.message || err?.message || 'Unable to process password reset request.';
+ setError(message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+
Reset Password
+
Enter your email and we will send you instructions to reset your password.
+
+
+
+
+
+ );
+};
+
+export default ForgotPassword;
diff --git a/ipi-survey-platform/src/pages/History/History.jsx b/ipi-survey-platform/src/pages/History/History.jsx
new file mode 100644
index 0000000..7c1b854
--- /dev/null
+++ b/ipi-survey-platform/src/pages/History/History.jsx
@@ -0,0 +1,246 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import Table from '../../components/common/Table';
+import HeaderBar from '../../components/layout/HeaderBar';
+import { fetchSubmissionHistory } from '../../services/submissions/submissionService.js';
+import SearchIcon from '../../assets/images/material-symbols_search-rounded.svg';
+import CaretDown from '../../assets/images/CaretDown.svg';
+
+const Badge = ({ variant = 'default', children }) => {
+ const styles = {
+ approved: 'bg-green-50 text-green-700 ring-1 ring-green-200',
+ submitted: 'bg-blue-50 text-blue-700 ring-1 ring-blue-200',
+ rejected: 'bg-red-50 text-red-600 ring-1 ring-red-200',
+ pending: 'bg-yellow-50 text-yellow-700 ring-1 ring-yellow-200',
+ default: 'bg-gray-50 text-gray-700 ring-1 ring-gray-200',
+ };
+ return (
+
+ {children}
+
+ );
+};
+
+const History = () => {
+ const [rows, setRows] = React.useState([]);
+ const [loading, setLoading] = React.useState(false);
+ const [error, setError] = React.useState('');
+ const [searchTerm, setSearchTerm] = React.useState('');
+ const [statusFilter, setStatusFilter] = React.useState('all');
+ const navigate = useNavigate();
+
+ const toVariant = React.useCallback((status) => {
+ const text = String(status || '').toLowerCase();
+ if (text === 'approved') return 'approved';
+ if (text === 'submitted') return 'submitted';
+ if (text === 'rejected') return 'rejected';
+ if (text === 'pending' || text === 'in-progress' || text === 'under review') return 'pending';
+ return 'default';
+ }, []);
+
+ const formatStatus = React.useCallback((status) => {
+ if (!status) return 'Pending';
+ const text = String(status).trim();
+ if (!text) return 'Pending';
+ return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
+ }, []);
+
+ const formatQuarter = React.useCallback((quarter, year) => {
+ if (!quarter && !year) return '—';
+ return [quarter || '', year || ''].filter(Boolean).join(' ');
+ }, []);
+
+ const formatDate = React.useCallback((value) => {
+ if (!value) return '—';
+ const date = new Date(value);
+ if (Number.isNaN(date.getTime())) return '—';
+ return date.toLocaleDateString('en-GB');
+ }, []);
+
+ React.useEffect(() => {
+ let cancelled = false;
+ const loadHistory = async () => {
+ setLoading(true);
+ setError('');
+ try {
+ const data = await fetchSubmissionHistory();
+ if (cancelled) return;
+ const list = Array.isArray(data?.data) ? data.data : Array.isArray(data) ? data : [];
+ setRows(list);
+ } catch (err) {
+ if (cancelled) return;
+ if (err?.code === 'ERR_CANCELED') return;
+ setError(err?.response?.data?.message || err?.message || 'Failed to load submission history.');
+ setRows([]);
+ } finally {
+ if (!cancelled) {
+ setLoading(false);
+ }
+ }
+ };
+ loadHistory();
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ const headers = ['Year', 'Quarter', 'Submission Window', 'Total Products', 'Products Submitted', 'Total Cost (AED)', 'Status', 'Date Submitted', 'Actions'];
+
+ const statusOptions = React.useMemo(
+ () => [
+ { label: 'All Status', value: 'all' },
+ { label: 'Approved', value: 'approved' },
+ { label: 'Submitted', value: 'submitted' },
+ { label: 'Pending', value: 'pending' },
+ { label: 'Rejected', value: 'rejected' },
+ ],
+ []
+ );
+ const handleViewDetails = React.useCallback((submission) => {
+ if (!submission) return;
+ navigate('/survey', { state: { submission, initialStep: 3 } });
+ }, [navigate]);
+
+ const filteredRows = React.useMemo(() => {
+ const term = searchTerm.trim().toLowerCase();
+ const statusValue = statusFilter.toLowerCase();
+ return rows.filter((entry) => {
+ const matchesStatus =
+ statusValue === 'all' || toVariant(entry.status) === statusValue || formatStatus(entry.status).toLowerCase() === statusValue;
+ if (!term) return matchesStatus;
+ const haystack = [
+ entry.year,
+ entry.quarter,
+ entry.submission_window,
+ entry.total_products,
+ entry.products_submitted,
+ entry.total_cost,
+ formatStatus(entry.status),
+ formatDate(entry.created_at),
+ ]
+ .map((val) => String(val || '').toLowerCase())
+ .join(' ');
+ return matchesStatus && haystack.includes(term);
+ });
+ }, [rows, searchTerm, statusFilter, toVariant, formatStatus, formatDate]);
+
+ const tableRows = filteredRows.map((entry) => [
+ entry.year || '—',
+ entry.quarter || '—',
+ entry.submission_window || '—',
+ Number.isFinite(Number(entry.total_products)) ? entry.total_products : '—',
+ Number.isFinite(Number(entry.products_submitted)) ? entry.products_submitted : '—',
+ entry.total_cost ? Intl.NumberFormat('en-US').format(entry.total_cost) : '—',
+ formatStatus(entry.status),
+ formatDate(entry.created_at),
+ 'View Details',
+ ]);
+
+ const toolbar = (
+
+
+
Quarter Overview
+
+ {loading && Loading… }
+ {error && {error} }
+
+
+
+
+
setSearchTerm(e.target.value)}
+ placeholder="Search submissions"
+ className="w-full h-10 rounded-md border border-[#E5E7EB] pl-10 pr-3 text-sm focus:outline-none"
+ />
+
+
+
+
setStatusFilter(e.target.value)}
+ className="h-10 w-full appearance-none rounded-md border border-[#E5E7EB] bg-white pl-3 pr-9 text-sm text-[#232528] focus:outline-none cursor-pointer"
+ >
+ {statusOptions.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+
+
{
+ if (!tableRows.length) return;
+ const csvRows = [headers.slice(0, headers.length - 1).join(',')];
+ tableRows.forEach((row) => {
+ csvRows.push(
+ row
+ .slice(0, row.length - 1)
+ .map((cell) => `"${String(cell ?? '').replace(/"/g, '""')}"`)
+ .join(',')
+ );
+ });
+ const blob = new Blob([csvRows.join('\n')], { type: 'text/csv;charset=utf-8;' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', 'submission-history.csv');
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ }}
+ disabled={!tableRows.length}
+ className={`inline-flex h-10 items-center gap-2 rounded-md border px-4 text-sm font-medium ${
+ tableRows.length ? 'border-[#92722A] text-[#92722A] hover:bg-[#F2ECCF]' : 'border-gray-200 text-gray-400 cursor-not-allowed'
+ }`}
+ >
+
+
+
+ Export CSV
+
+
+
+ );
+
+ return (
+
+
+
+
+
{
+ if (ci === 6) {
+ return {value} ;
+ }
+ if (ci === headers.length - 1) {
+ const submission = filteredRows[ri];
+ const disabled = !submission;
+ return (
+ handleViewDetails(submission)}
+ >
+ {String(value)}
+
+ );
+ }
+ return value;
+ }}
+ />
+
+
+
+ );
+};
+
+export default History;
diff --git a/ipi-survey-platform/src/pages/Login/Login.jsx b/ipi-survey-platform/src/pages/Login/Login.jsx
new file mode 100644
index 0000000..ea10b77
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Login/Login.jsx
@@ -0,0 +1,247 @@
+import React from 'react';
+import Logo from '../../assets/images/FCSCLogo.svg';
+import { useNavigate } from 'react-router-dom';
+import { login } from '../../services/auth/authService.js';
+
+const Login = () => {
+ const navigate = useNavigate();
+ const [email, setEmail] = React.useState('');
+ const [password, setPassword] = React.useState('');
+ const [showPassword, setShowPassword] = React.useState(false);
+ const [rememberMe, setRememberMe] = React.useState(false);
+ const [loading, setLoading] = React.useState(false);
+ const [error, setError] = React.useState('');
+
+ React.useEffect(() => {
+ try {
+ const remembered = localStorage.getItem('remember_me');
+ if (remembered === 'true') {
+ setRememberMe(true);
+ const storedEmail = localStorage.getItem('remembered_email');
+ const storedPassword = localStorage.getItem('remembered_password');
+ if (storedEmail) {
+ setEmail(storedEmail);
+ }
+ if (storedPassword) {
+ setPassword(storedPassword);
+ }
+ }
+ } catch (storageError) {
+ console.warn('Unable to read remembered credentials', storageError);
+ }
+ }, []);
+
+ const submit = async (e) => {
+ e.preventDefault();
+ setError('');
+ setLoading(true);
+ try {
+ const response = await login({ email, password });
+ if (response?.status !== 'success' || !response?.data) {
+ throw new Error(response?.message || 'Invalid response');
+ }
+
+ const token = response.data;
+ try {
+ localStorage.setItem('auth_token', token);
+ } catch (storageError) {
+ console.error('Failed to persist auth token', storageError);
+ }
+
+ let payload;
+ try {
+ const [, base64Payload] = token.split('.');
+ const json = atob(base64Payload);
+ payload = JSON.parse(json);
+ } catch (decodeError) {
+ console.error('Failed to decode token', decodeError);
+ throw new Error('Received invalid token.');
+ }
+
+ const role = payload?.role ?? (payload?.is_admin ? 'Admin' : undefined);
+ if (role) {
+ try {
+ localStorage.setItem('user_role', role);
+ } catch (storageError) {
+ console.warn('Unable to persist user role', storageError);
+ }
+ }
+
+ try {
+ const profile = {
+ name: payload?.name || payload?.username || '',
+ email: payload?.email || '',
+ };
+ localStorage.setItem('user_profile', JSON.stringify(profile));
+ } catch (storageError) {
+ console.warn('Unable to persist user profile', storageError);
+ }
+
+ const establishmentId = payload?.establishment_id ?? payload?.establishmentId;
+ const isAdmin = String(role || '').toLowerCase() === 'admin' || payload?.is_admin === true;
+
+ if (isAdmin) {
+ try {
+ localStorage.removeItem('establishment_id');
+ } catch (storageError) {
+ console.warn('Unable to clear establishment ID', storageError);
+ }
+ navigate('/admin');
+ return;
+ }
+
+ if (rememberMe) {
+ try {
+ localStorage.setItem('remember_me', 'true');
+ localStorage.setItem('remembered_email', email);
+ localStorage.setItem('remembered_password', password);
+ } catch (storageError) {
+ console.warn('Unable to persist remember-me preference', storageError);
+ }
+ } else {
+ try {
+ localStorage.removeItem('remember_me');
+ localStorage.removeItem('remembered_email');
+ localStorage.removeItem('remembered_password');
+ } catch (storageError) {
+ console.warn('Unable to clear remembered credentials', storageError);
+ }
+ }
+
+ if (establishmentId) {
+ try {
+ localStorage.setItem('establishment_id', String(establishmentId));
+ } catch (storageError) {
+ console.warn('Unable to persist establishment ID', storageError);
+ }
+ navigate('/dashboard');
+ } else {
+ setError('No establishment associated with this account.');
+ }
+ } catch (err) {
+ console.error('Login failed', err);
+ setError(err.message || 'Unable to sign in. Please try again.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
IPI Survey Platform
+
+
+
+
+
+
+ );
+};
+
+export default Login;
diff --git a/ipi-survey-platform/src/pages/Survey/Survey.jsx b/ipi-survey-platform/src/pages/Survey/Survey.jsx
new file mode 100644
index 0000000..28c5480
--- /dev/null
+++ b/ipi-survey-platform/src/pages/Survey/Survey.jsx
@@ -0,0 +1,397 @@
+import React from 'react';
+import { useLocation, useNavigate } from 'react-router-dom';
+import { submitSurvey, fetchSubmissionDetail } from '../../services/submissions/submissionService.js';
+import { fetchEstablishmentDetail } from '../../services/establishments/establishmentService.js';
+import EstablishmentInfo from '../../components/survey/EstablishmentInfo/EstablishmentInfo';
+import HeaderBar from '../../components/layout/HeaderBar';
+import ProductData from '../../components/survey/ProductData/ProductData';
+import ReviewSubmit from '../../components/survey/ReviewSubmit/ReviewSubmit';
+import CheckCircleActive from '../../assets/images/CircleFrame.svg';
+
+const createEmptyProduct = (id) => ({
+ id,
+ product: '',
+ unit: '',
+ capacity: '',
+ previousQuantity: '',
+ previousCost: '',
+ currentQuantity: '',
+ currentCost: '',
+ variationReason: '',
+ remarks: '',
+ forecastQuantity: '',
+ forecastCost: '',
+ zeroTargetReason: '',
+});
+
+const Survey = () => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const locationState = location?.state ?? {};
+ const initialStepFromState = React.useMemo(() => {
+ const stepValue = locationState.initialStep;
+ const parsed = Number(stepValue);
+ if (Number.isInteger(parsed) && parsed >= 1 && parsed <= 3) {
+ return parsed;
+ }
+ return 1;
+ }, [locationState.initialStep]);
+
+ const [step, setStep] = React.useState(initialStepFromState);
+ const [establishmentData, setEstablishmentData] = React.useState({
+ quarter: '',
+ year: '',
+ establishmentName: '',
+ permanentFactoryCode: '',
+ industryCode: '',
+ licenseNumber: '',
+ isicCode: '',
+ emirate: '',
+ employeeInfo: {
+ emiratiMale: '',
+ nonEmiratiMale: '',
+ emiratiFemale: '',
+ nonEmiratiFemale: '',
+ totalEmployees: '',
+ totalEmirati: '',
+ },
+ });
+ const [productsData, setProductsData] = React.useState([createEmptyProduct(1)]);
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
+ const [hasSubmitted, setHasSubmitted] = React.useState(false);
+ const [toast, setToast] = React.useState(null);
+ const [submissionDetail, setSubmissionDetail] = React.useState(null);
+ const [viewLoading, setViewLoading] = React.useState(false);
+ const [viewError, setViewError] = React.useState('');
+ const [establishmentLoading, setEstablishmentLoading] = React.useState(false);
+ const [establishmentError, setEstablishmentError] = React.useState('');
+ const isViewMode = React.useMemo(() => Boolean(locationState.submission), [locationState.submission]);
+ const toastTimeoutRef = React.useRef(null);
+
+ React.useEffect(() => {
+ setStep(initialStepFromState);
+ }, [initialStepFromState]);
+
+ React.useEffect(() => {
+ let cancelled = false;
+ if (isViewMode) return () => {
+ cancelled = true;
+ };
+
+ const ensureString = (value) => {
+ if (value === null || value === undefined) return '';
+ const str = String(value);
+ return str.trim() === '' ? '' : str;
+ };
+
+ const loadEstablishment = async () => {
+ setEstablishmentLoading(true);
+ setEstablishmentError('');
+ try {
+ const response = await fetchEstablishmentDetail();
+ if (cancelled) return;
+ const detail = response?.data ?? response ?? {};
+ setEstablishmentData((prev) => ({
+ ...prev,
+ establishmentName: ensureString(detail?.factory_name) || prev.establishmentName,
+ permanentFactoryCode: ensureString(detail?.permanent_factory_code) || prev.permanentFactoryCode,
+ industryCode: ensureString(detail?.industry_code) || prev.industryCode,
+ licenseNumber: ensureString(detail?.license_number) || prev.licenseNumber,
+ isicCode: ensureString(detail?.isic_code) || prev.isicCode,
+ emirate: ensureString(detail?.emirate) || prev.emirate,
+ employeeInfo: {
+ ...prev.employeeInfo,
+ emiratiMale: ensureString(detail?.emirati_male) || prev.employeeInfo.emiratiMale,
+ emiratiFemale: ensureString(detail?.emirati_female) || prev.employeeInfo.emiratiFemale,
+ nonEmiratiMale: ensureString(detail?.non_emirati_male) || prev.employeeInfo.nonEmiratiMale,
+ nonEmiratiFemale: ensureString(detail?.non_emirati_female) || prev.employeeInfo.nonEmiratiFemale,
+ totalEmirati: ensureString(detail?.total_emirati) || prev.employeeInfo.totalEmirati,
+ totalEmployees: ensureString(detail?.total_employees) || prev.employeeInfo.totalEmployees,
+ },
+ }));
+ } catch (error) {
+ if (cancelled) return;
+ const message = error?.response?.data?.message || error?.message || 'Failed to load establishment details.';
+ setEstablishmentError(message);
+ } finally {
+ if (!cancelled) {
+ setEstablishmentLoading(false);
+ }
+ }
+ };
+
+ loadEstablishment();
+ return () => {
+ cancelled = true;
+ };
+ }, [isViewMode]);
+
+ React.useEffect(() => {
+ let cancelled = false;
+ if (!isViewMode) return () => {
+ cancelled = true;
+ };
+
+ const submissionId = locationState?.submission?.id;
+ if (!submissionId) return () => {
+ cancelled = true;
+ };
+
+ const ensureString = (value) => {
+ if (value === null || value === undefined) return '';
+ const str = String(value);
+ return str.trim() === '' ? '' : str;
+ };
+
+ const mergeEstablishmentData = (detail) => {
+ const summary = locationState.submission || {};
+ return {
+ quarter: ensureString(detail?.quarter) || ensureString(summary.quarter) || '',
+ year: ensureString(detail?.year) || ensureString(summary.year) || '',
+ establishmentName: ensureString(summary.establishment_name) || establishmentData.establishmentName || '',
+ permanentFactoryCode:
+ ensureString(summary.permanent_factory_code) ||
+ ensureString(detail?.establishment_id) ||
+ establishmentData.permanentFactoryCode || '',
+ industryCode: ensureString(summary.industry_code) || establishmentData.industryCode || '',
+ licenseNumber: ensureString(summary.license_number) || establishmentData.licenseNumber || '',
+ isicCode: ensureString(summary.isic_code) || establishmentData.isicCode || '',
+ emirate: ensureString(summary.emirate) || establishmentData.emirate || '',
+ employeeInfo: { ...establishmentData.employeeInfo },
+ };
+ };
+
+ const mapProducts = (detailProducts = []) => {
+ if (!Array.isArray(detailProducts)) return [];
+ return detailProducts.map((product, index) => ({
+ id: product?.id ?? index + 1,
+ product: ensureString(product?.product?.product_name) || ensureString(product?.product_id) || '',
+ unit: ensureString(product?.unit?.name) || ensureString(product?.unit_id) || '',
+ capacity: ensureString(product?.annual_installed_capacity),
+ previousQuantity: ensureString(product?.previous_quantity),
+ previousCost: ensureString(product?.previous_cost),
+ currentQuantity: ensureString(product?.current_quantity),
+ currentCost: ensureString(product?.current_cost),
+ forecastQuantity: ensureString(product?.forecast_quantity),
+ forecastCost: ensureString(product?.forecast_cost),
+ variationReason: ensureString(product?.variation_reason_master_id),
+ otherVariationReason: ensureString(product?.other_variation_reason),
+ zeroTargetReason: ensureString(product?.zero_target_reason_master_id),
+ otherZeroTargetReason: ensureString(product?.other_zero_target_reason),
+ remarks: ensureString(product?.remarks),
+ }));
+ };
+
+ const loadDetail = async () => {
+ setViewLoading(true);
+ setViewError('');
+ try {
+ const response = await fetchSubmissionDetail(submissionId);
+ if (cancelled) return;
+ const detail = response?.data ?? response ?? {};
+ setSubmissionDetail(detail);
+ setEstablishmentData((prev) => ({
+ ...prev,
+ ...mergeEstablishmentData(detail),
+ }));
+ const mapped = mapProducts(detail?.products);
+ if (mapped.length > 0) {
+ setProductsData(mapped);
+ }
+ setHasSubmitted(true);
+ } catch (error) {
+ if (cancelled) return;
+ const message = error?.response?.data?.message || error?.message || 'Failed to load submission details.';
+ setViewError(message);
+ } finally {
+ if (!cancelled) {
+ setViewLoading(false);
+ }
+ }
+ };
+
+ loadDetail();
+ return () => {
+ cancelled = true;
+ };
+ // We intentionally avoid including establishmentData in dependencies to prevent unnecessary resets during view load.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isViewMode, locationState.submission?.id]);
+
+ const showToast = React.useCallback((type, message) => {
+ if (toastTimeoutRef.current) {
+ window.clearTimeout(toastTimeoutRef.current);
+ toastTimeoutRef.current = null;
+ }
+ setToast({ type, message });
+ toastTimeoutRef.current = window.setTimeout(() => {
+ setToast(null);
+ toastTimeoutRef.current = null;
+ }, 4000);
+ }, []);
+
+ React.useEffect(() => () => {
+ if (toastTimeoutRef.current) {
+ window.clearTimeout(toastTimeoutRef.current);
+ }
+ }, []);
+
+ const parseNumber = React.useCallback((value, fallback = 0) => {
+ if (value === null || value === undefined || value === '') return fallback;
+ const parsed = Number(value);
+ if (Number.isFinite(parsed)) return parsed;
+ const trimmed = String(value).trim();
+ const numeric = Number(trimmed.replace(/[^0-9.-]/g, ''));
+ return Number.isFinite(numeric) ? numeric : fallback;
+ }, []);
+
+ const stringify = React.useCallback((value) => {
+ if (value === null || value === undefined) return '';
+ return String(value);
+ }, []);
+
+ const buildSubmissionPayload = React.useCallback(() => {
+ const info = establishmentData?.employeeInfo ?? {};
+ return {
+ quarter: establishmentData?.quarter ?? '',
+ year: parseNumber(establishmentData?.year),
+ emirati_male: parseNumber(info.emiratiMale),
+ emirati_female: parseNumber(info.emiratiFemale),
+ non_emirati_male: parseNumber(info.nonEmiratiMale),
+ non_emirati_female: parseNumber(info.nonEmiratiFemale),
+ total_emirati: parseNumber(info.totalEmirati),
+ total_employees: parseNumber(info.totalEmployees),
+ products: productsData.map((product) => {
+ const variationId = parseNumber(product.variationReason, null);
+ const zeroTargetId = parseNumber(product.zeroTargetReason, null);
+ return {
+ product_id: parseNumber(product.product),
+ unit_id: parseNumber(product.unit),
+ annual_installed_capacity: stringify(product.capacity),
+ previous_quantity: stringify(product.previousQuantity),
+ previous_cost: stringify(product.previousCost),
+ current_quantity: stringify(product.currentQuantity),
+ current_cost: stringify(product.currentCost),
+ forecast_quantity: stringify(product.forecastQuantity),
+ forecast_cost: stringify(product.forecastCost),
+ variation_reason_master_id: Number.isFinite(variationId) ? variationId : null,
+ other_variation_reason: stringify(product.otherVariationReason),
+ zero_target_reason_master_id: Number.isFinite(zeroTargetId) ? zeroTargetId : null,
+ other_zero_target_reason: stringify(product.otherZeroTargetReason),
+ remarks: stringify(product.remarks),
+ };
+ }),
+ };
+ }, [establishmentData, parseNumber, productsData, stringify]);
+
+ const handleSubmit = React.useCallback(async () => {
+ setIsSubmitting(true);
+ try {
+ const payload = buildSubmissionPayload();
+ const response = await submitSurvey(payload);
+ console.log('Survey submission response:', response);
+ showToast('success', response?.message || 'You have successfully submitted the survey.');
+ setHasSubmitted(true);
+ } catch (error) {
+ console.error('Failed to submit survey', error);
+ const message = error?.response?.data?.message || error?.message || 'Failed to submit survey.';
+ showToast('error', message);
+ } finally {
+ setIsSubmitting(false);
+ }
+ }, [buildSubmissionPayload, showToast]);
+
+ const StepBadge = ({ state, number }) => {
+ if (state === 'active') {
+ return (
+
+ {number}
+
+ );
+ }
+ if (state === 'completed') {
+ return (
+
+
+
+ );
+ }
+ return (
+
+ {number}
+
+ );
+ };
+
+ return (
+ <>
+
+ {/* Step header bar (56px height, white bg) */}
+
+
+
+
+
+ 1 ? 'completed' : 'idle'} number={1} />
+ Establishment Information
+
+
+ 2 ? 'completed' : 'idle'} number={2} />
+ Product Data
+
+
+
+ Review & Submit
+
+
+
+
+
+
+ {/* Page content */}
+
+
+ {/* Step content */}
+ {step === 1 && (
+
setStep(2)}
+ onBack={() => navigate('/')}
+ />
+ )}
+ {step === 2 && (
+ setStep(1)}
+ onNext={() => setStep(3)}
+ />
+ )}
+ {step === 3 && (
+ setStep(2)}
+ onSubmit={handleSubmit}
+ toast={toast}
+ onToastClose={() => {
+ if (toastTimeoutRef.current) {
+ window.clearTimeout(toastTimeoutRef.current);
+ toastTimeoutRef.current = null;
+ }
+ setToast(null);
+ }}
+ isSubmitting={isSubmitting}
+ hasSubmitted={hasSubmitted}
+ />
+ )}
+
+
+ >
+ );
+};
+
+export default Survey;
\ No newline at end of file
diff --git a/ipi-survey-platform/src/routes/AppRoutes.jsx b/ipi-survey-platform/src/routes/AppRoutes.jsx
new file mode 100644
index 0000000..243e264
--- /dev/null
+++ b/ipi-survey-platform/src/routes/AppRoutes.jsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import { Routes, Route } from 'react-router-dom';
+import Dashboard from '../pages/Dashboard/Dashboard';
+import Survey from '../pages/Survey/Survey';
+import History from '../pages/History/History';
+import Configuration from '../pages/Admin/Configuration';
+import AdminDashboard from '../pages/Admin/AdminDashboard';
+
+const AppRoutes = () => {
+ return (
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ );
+};
+
+export default AppRoutes;
\ No newline at end of file
diff --git a/ipi-survey-platform/src/services/api/CommonService.js b/ipi-survey-platform/src/services/api/CommonService.js
new file mode 100644
index 0000000..1b8984c
--- /dev/null
+++ b/ipi-survey-platform/src/services/api/CommonService.js
@@ -0,0 +1,16 @@
+import apiClient from './apiClient.js';
+
+export const getRequest = (url, config = {}) => apiClient.get(url, config);
+
+export const postRequest = (url, data, config = {}) => apiClient.post(url, data, config);
+
+export const putRequest = (url, data, config = {}) => apiClient.put(url, data, config);
+
+export const deleteRequest = (url, config = {}) => apiClient.delete(url, config);
+
+export default {
+ getRequest,
+ postRequest,
+ putRequest,
+ deleteRequest,
+};
diff --git a/ipi-survey-platform/src/services/api/apiClient.js b/ipi-survey-platform/src/services/api/apiClient.js
new file mode 100644
index 0000000..c6a83fc
--- /dev/null
+++ b/ipi-survey-platform/src/services/api/apiClient.js
@@ -0,0 +1,39 @@
+import axios from 'axios';
+
+const baseURL = import.meta.env.VITE_API_BASE_URL;
+const appSignature = import.meta.env.VITE_API_APP_SIGNATURE;
+const fallbackToken = import.meta.env.VITE_API_TOKEN;
+
+const apiClient = axios.create({
+ baseURL,
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ ...(appSignature ? { APP_SIGNATURE: appSignature } : {}),
+ },
+});
+
+apiClient.interceptors.request.use((config) => {
+ config.headers = config.headers ?? {};
+ if (config.skipAuth) {
+ return config;
+ }
+
+ if (!config.headers.Authorization) {
+ let token;
+ try {
+ token = localStorage.getItem('auth_token');
+ } catch (error) {
+ token = undefined;
+ }
+ if (!token && fallbackToken) {
+ token = fallbackToken;
+ }
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ }
+ return config;
+});
+
+export default apiClient;
diff --git a/ipi-survey-platform/src/services/auth/authService.js b/ipi-survey-platform/src/services/auth/authService.js
new file mode 100644
index 0000000..943a391
--- /dev/null
+++ b/ipi-survey-platform/src/services/auth/authService.js
@@ -0,0 +1,22 @@
+import apiClient from '../api/apiClient.js';
+
+export const login = async ({ email, password }) => {
+ const response = await apiClient.post('/auth/login', { email, password }, { skipAuth: true });
+ return response.data;
+};
+
+export const changePassword = async ({ currentPassword, newPassword }) => {
+ const response = await apiClient.post('/auth/change-password', { currentPassword, newPassword });
+ return response.data;
+};
+
+export const requestPasswordReset = async ({ email }) => {
+ const response = await apiClient.post('/auth/forgot-password', { email }, { skipAuth: true });
+ return response.data;
+};
+
+export default {
+ login,
+ changePassword,
+ requestPasswordReset,
+};
diff --git a/ipi-survey-platform/src/services/establishments/establishmentService.js b/ipi-survey-platform/src/services/establishments/establishmentService.js
new file mode 100644
index 0000000..f1eaf32
--- /dev/null
+++ b/ipi-survey-platform/src/services/establishments/establishmentService.js
@@ -0,0 +1,30 @@
+import { getRequest } from '../api/CommonService.js';
+import resolveEstablishmentId from '../utils/establishment.js';
+
+const dashboardEndpoint = '/establishment_dashboard';
+const establishmentsEndpoint = '/establishments';
+
+export const fetchEstablishmentDashboard = async (establishmentId, config = {}) => {
+ const resolvedId = resolveEstablishmentId(establishmentId);
+ if (resolvedId === undefined) {
+ throw new Error('Missing establishment ID for dashboard request.');
+ }
+ const url = `${dashboardEndpoint}?establishment_id=${resolvedId}`;
+ const response = await getRequest(url, config);
+ return response.data;
+};
+
+export const fetchEstablishmentDetail = async (establishmentId, config = {}) => {
+ const resolvedId = resolveEstablishmentId(establishmentId);
+ if (resolvedId === undefined) {
+ throw new Error('Missing establishment ID for establishment detail request.');
+ }
+ const url = `${establishmentsEndpoint}/${resolvedId}`;
+ const response = await getRequest(url, config);
+ return response.data;
+};
+
+export default {
+ fetchEstablishmentDashboard,
+ fetchEstablishmentDetail,
+};
diff --git a/ipi-survey-platform/src/services/masters/masterService.js b/ipi-survey-platform/src/services/masters/masterService.js
new file mode 100644
index 0000000..f60f5df
--- /dev/null
+++ b/ipi-survey-platform/src/services/masters/masterService.js
@@ -0,0 +1,128 @@
+import { getRequest } from '../api/CommonService.js';
+
+const endpoints = {
+ variationReasons: '/variation_reasons',
+ zeroTargetReasons: '/zero_target_reason',
+ products: '/products',
+ units: '/unit_master',
+};
+
+const extractReasons = (payload) => {
+ if (!payload) return [];
+ if (Array.isArray(payload)) return payload;
+ if (Array.isArray(payload.data)) return payload.data;
+ return [];
+};
+
+const toStringOrNull = (value) => {
+ if (value === undefined || value === null) return null;
+ const text = String(value).trim();
+ return text === '' ? null : text;
+};
+
+const toReasonOption = (item) => {
+ if (item === undefined || item === null) return null;
+ if (typeof item === 'string' || typeof item === 'number') {
+ const value = toStringOrNull(item);
+ return value ? { label: value, value } : null;
+ }
+ if (typeof item === 'object') {
+ const label = toStringOrNull(item.name ?? item.reason ?? item.label ?? item.description);
+ const value = toStringOrNull(item.id ?? item.master_id ?? item.reason_id ?? item.value ?? item.key ?? label);
+ const finalLabel = label ?? value;
+ if (!finalLabel) return null;
+ return { label: finalLabel, value: value ?? finalLabel };
+ }
+ return null;
+};
+
+const toProductOption = (item) => {
+ if (item === undefined || item === null) return null;
+ if (typeof item === 'string' || typeof item === 'number') {
+ const value = toStringOrNull(item);
+ return value ? { label: value, value } : null;
+ }
+ if (typeof item === 'object') {
+ const id = toStringOrNull(item.id ?? item.product_id ?? item.value ?? item.code ?? item.hs_code);
+ const hs = toStringOrNull(item.hs_code ?? item.code);
+ const name = toStringOrNull(item.product_name ?? item.name);
+ const label = toStringOrNull(hs && name ? `${hs} - ${name}` : name ?? hs ?? item.label);
+ const finalLabel = label ?? id;
+ if (!finalLabel) return null;
+ return { label: finalLabel, value: id ?? finalLabel };
+ }
+ return null;
+};
+
+const toUnitOption = (item) => {
+ if (item === undefined || item === null) return null;
+ if (typeof item === 'string' || typeof item === 'number') {
+ const value = toStringOrNull(item);
+ return value ? { label: value, value } : null;
+ }
+ if (typeof item === 'object') {
+ const id = toStringOrNull(item.id ?? item.unit_id ?? item.value ?? item.code);
+ const short = toStringOrNull(item.uom_short_name ?? item.short_name);
+ const name = toStringOrNull(item.uom ?? item.name ?? item.label);
+ const label = toStringOrNull(short && name ? `${short} - ${name}` : name ?? short);
+ const finalLabel = label ?? id;
+ if (!finalLabel) return null;
+ return { label: finalLabel, value: id ?? finalLabel };
+ }
+ return null;
+};
+
+export const getVariationReasons = async (config = {}) => {
+ const response = await getRequest(endpoints.variationReasons, config);
+ return response.data;
+};
+
+export const fetchVariationReasons = async ({ signal } = {}) => {
+ const data = await getVariationReasons({ signal });
+ const reasons = extractReasons(data);
+ return reasons.map(toReasonOption).filter(Boolean);
+};
+
+export const getZeroTargetReasons = async (config = {}) => {
+ const response = await getRequest(endpoints.zeroTargetReasons, config);
+ return response.data;
+};
+
+export const fetchZeroTargetReasons = async ({ signal } = {}) => {
+ const data = await getZeroTargetReasons({ signal });
+ const reasons = extractReasons(data);
+ return reasons.map(toReasonOption).filter(Boolean);
+};
+
+export const getProducts = async (config = {}) => {
+ const response = await getRequest(endpoints.products, config);
+ return response.data;
+};
+
+export const fetchProducts = async ({ signal } = {}) => {
+ const data = await getProducts({ signal });
+ const list = extractReasons(data);
+ return list.map(toProductOption).filter(Boolean);
+};
+
+export const getUnits = async (config = {}) => {
+ const response = await getRequest(endpoints.units, config);
+ return response.data;
+};
+
+export const fetchUnits = async ({ signal } = {}) => {
+ const data = await getUnits({ signal });
+ const list = extractReasons(data);
+ return list.map(toUnitOption).filter(Boolean);
+};
+
+export default {
+ getVariationReasons,
+ fetchVariationReasons,
+ getZeroTargetReasons,
+ fetchZeroTargetReasons,
+ getProducts,
+ fetchProducts,
+ getUnits,
+ fetchUnits,
+};
diff --git a/ipi-survey-platform/src/services/submissions/submissionService.js b/ipi-survey-platform/src/services/submissions/submissionService.js
new file mode 100644
index 0000000..6ddfc9d
--- /dev/null
+++ b/ipi-survey-platform/src/services/submissions/submissionService.js
@@ -0,0 +1,39 @@
+import { getRequest, postRequest } from '../api/CommonService.js';
+import resolveEstablishmentId from '../utils/establishment.js';
+
+const endpoint = '/submissions';
+
+export const submitSurvey = async (payload = {}, config = {}) => {
+ const establishmentId = resolveEstablishmentId(payload.establishment_id);
+ const requestBody = {
+ ...payload,
+ ...(establishmentId !== undefined ? { establishment_id: establishmentId } : {}),
+ };
+ const response = await postRequest(endpoint, requestBody, config);
+ return response.data;
+};
+
+export const fetchSubmissionHistory = async (establishmentId, config = {}) => {
+ const resolvedId = resolveEstablishmentId(establishmentId);
+ if (resolvedId === undefined) {
+ throw new Error('Missing establishment ID for submission history request.');
+ }
+ const url = `${endpoint}/history/${resolvedId}`;
+ const response = await getRequest(url, config);
+ return response.data;
+};
+
+export const fetchSubmissionDetail = async (submissionId, config = {}) => {
+ if (!submissionId) {
+ throw new Error('Submission ID is required to fetch submission detail.');
+ }
+ const url = `${endpoint}/${submissionId}`;
+ const response = await getRequest(url, config);
+ return response.data;
+};
+
+export default {
+ submitSurvey,
+ fetchSubmissionHistory,
+ fetchSubmissionDetail,
+};
diff --git a/ipi-survey-platform/src/services/survey/surveyService.js b/ipi-survey-platform/src/services/survey/surveyService.js
new file mode 100644
index 0000000..fa8bd1f
--- /dev/null
+++ b/ipi-survey-platform/src/services/survey/surveyService.js
@@ -0,0 +1,12 @@
+export {
+ submitSurvey,
+ fetchSubmissionHistory,
+ fetchSubmissionDetail,
+} from '../submissions/submissionService.js';
+
+export {
+ fetchEstablishmentDashboard,
+ fetchEstablishmentDetail,
+} from '../establishments/establishmentService.js';
+
+export { default as resolveEstablishmentId } from '../utils/establishment.js';
diff --git a/ipi-survey-platform/src/services/utils/establishment.js b/ipi-survey-platform/src/services/utils/establishment.js
new file mode 100644
index 0000000..0456d84
--- /dev/null
+++ b/ipi-survey-platform/src/services/utils/establishment.js
@@ -0,0 +1,26 @@
+export const resolveEstablishmentId = (inputId) => {
+ if (typeof inputId === 'number' && Number.isFinite(inputId)) {
+ return inputId;
+ }
+ if (typeof inputId === 'string' && inputId.trim() !== '') {
+ const parsed = Number(inputId);
+ if (!Number.isNaN(parsed)) {
+ return parsed;
+ }
+ }
+ let storedId;
+ try {
+ storedId = localStorage.getItem('establishment_id');
+ } catch (error) {
+ storedId = undefined;
+ }
+ if (storedId !== undefined && storedId !== null && String(storedId).trim() !== '') {
+ const parsed = Number(storedId);
+ if (!Number.isNaN(parsed)) {
+ return parsed;
+ }
+ }
+ return undefined;
+};
+
+export default resolveEstablishmentId;
diff --git a/ipi-survey-platform/tailwind.config.js b/ipi-survey-platform/tailwind.config.js
new file mode 100644
index 0000000..a6193f6
--- /dev/null
+++ b/ipi-survey-platform/tailwind.config.js
@@ -0,0 +1,26 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ colors: {
+ primary: {
+ 50: '#eff6ff',
+ 500: '#3b82f6',
+ 600: '#2563eb',
+ 700: '#1d4ed8',
+ },
+ secondary: {
+ 50: '#f0fdfa',
+ 500: '#14b8a6',
+ 600: '#0d9488',
+ 700: '#0f766e',
+ }
+ }
+ },
+ },
+ plugins: [],
+}
\ No newline at end of file
diff --git a/ipi-survey-platform/vite.config.js b/ipi-survey-platform/vite.config.js
new file mode 100644
index 0000000..8b0f57b
--- /dev/null
+++ b/ipi-survey-platform/vite.config.js
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+})