commit a162fa89e541208c0c961a3af5464ca1bef0ad4d Author: Thiru-tenext Date: Fri Jun 5 17:28:05 2026 +0530 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef869ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Dependencies +node_modules/ + +# Build output +dist/ +dist-ssr/ + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Environment +.env +.env.local +.env.*.local + +# Editor / OS +.DS_Store +.vscode/ +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ced04c --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Doormile Console — Admin + +A modern, corporate logistics console for **Doormile**, a last-mile / courier delivery operation. Built with **React 18 + Vite + Material-UI v5**, themed around the Doormile brand red `#C01227`. + +## Quick start + +```bash +npm install +npm run dev # http://localhost:3000 +npm run build # production build → dist/ +npm run preview # preview the production build +``` + +## What's inside + +A clean, data-dense but breathable corporate shell — fixed **red top header** (search, notifications, messages, profile) + **collapsible red sidebar** (260px ↔ 78px icon rail) + light `#FAFAFB` content area — wrapping **27 screens**: + +| Area | Screens | +| --- | --- | +| Overview | Dashboard | +| Orders | Orders list · Order Details (tracking + timeline) · Create Order · Create Multiple Orders · Assign Orders | +| Deliveries | Deliveries (expandable products, status tabs) | +| Network | Tenants · Create Client · Customers · Create Customer · Pricing · Riders · Create Rider · Edit Rider | +| Reports | Order Summary · Order Details · Riders Summary · Riders Logs (live map) | +| Finance | Invoices · Invoice Preview (printable A4) · Requests | +| Account | User Profile | +| Auth & states | Login · 404 · 500 · Under Construction · Coming Soon | + +## Design system + +- **Brand red** `#C01227` (`lighter #F8E0E3 → darker #7E0B17`), white-on-red header/sidebar. +- **Status palette** — success `#00A854`, warning `#FFBF00`, info `#00A2AE`, error `#F04134`. +- **Public Sans** type scale, 6–10px radii, soft `0 1px 4px` card shadows, and a signature **red glow** on primary CTAs. +- All tuning lives in `src/theme/` (`palette.js`, `typography.js`, `shadows.js`, `componentsOverride.js`). + +## Project structure + +``` +src/ + theme/ Doormile red theme + MUI component overrides + layout/ MainLayout (red header + collapsible sidebar) · MinimalLayout + menu/ sidebar nav config + components/ PageHeader, StatCard, StatusChip, MainCard, EmptyState, + UserAvatar, MapPlaceholder, Logo, charts/ (Area, Donut) + data/mock.js static demo data powering every screen + pages/ all 27 screens (lazy-loaded) + App.jsx router · main.jsx app entry +``` + +Charts and maps are dependency-free SVG placeholders (`components/charts`, `MapPlaceholder`) — swap in Recharts / Leaflet / Google Maps when wiring real data. All data is mocked in `src/data/mock.js`; there is no backend. diff --git a/index.html b/index.html new file mode 100644 index 0000000..c64eb87 --- /dev/null +++ b/index.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + Doormile Console + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6df2d0a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2667 @@ +{ + "name": "doormile-console-admin", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "doormile-console-admin", + "version": "1.0.0", + "dependencies": { + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@mui/icons-material": "^5.15.20", + "@mui/lab": "^5.0.0-alpha.170", + "@mui/material": "^5.15.20", + "@mui/x-date-pickers": "^6.20.2", + "dayjs": "^1.11.11", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.23.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.1", + "vite": "^5.3.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@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/core/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/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@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.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "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.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.40-1", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40-1.tgz", + "integrity": "sha512-agKXuNNy0bHUmeU7pNmoZwNFr7Hiyhojkb9+2PVyDG5+6RafYuyMgbrav8CndsB7KUc/U51JAw9vKNDLYBzaUA==", + "deprecated": "This package has been replaced by @base-ui/react", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "~7.2.15", + "@mui/utils": "^5.17.1", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.18.0.tgz", + "integrity": "sha512-jbhwoQ1AY200PSSOrNXmrFCaSDSJWP7qk6urkTmIirvRXDROkqe+QwcLlUiw/PrREwsIF/vm3/dAXvjlMHF0RA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.18.0.tgz", + "integrity": "sha512-1s0vEZj5XFXDMmz3Arl/R7IncFqJ+WQ95LDp1roHWGDE2oCO3IS4/hmiOv1/8SD9r6B7tv9GLiqVZYHo+6PkTg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab": { + "version": "5.0.0-alpha.177", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.177.tgz", + "integrity": "sha512-bdCxxtNjlWAgN9rtrwlmFydJ1qxA3IIbb6OlomGFsIXw0zGoHomLyjvh72q/R3yUAC0kvSef18cHY1UalLylyQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40-1", + "@mui/system": "^5.18.0", + "@mui/types": "~7.2.15", + "@mui/utils": "^5.17.1", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": ">=5.15.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.18.0.tgz", + "integrity": "sha512-bbH/HaJZpFtXGvWg3TsBWG4eyt3gah3E7nCNU8GLyRjVoWcA91Vm/T+sjHfUcwgJSw9iLtucfHBoq+qW/T30aA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/core-downloads-tracker": "^5.18.0", + "@mui/system": "^5.18.0", + "@mui/types": "~7.2.15", + "@mui/utils": "^5.17.1", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.17.1.tgz", + "integrity": "sha512-XMxU0NTYcKqdsG8LRmSoxERPXwMbp16sIXPcLVgLGII/bVNagX0xaheWAwFv8+zDK7tI3ajllkuD3GZZE++ICQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.17.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.18.0.tgz", + "integrity": "sha512-BN/vKV/O6uaQh2z5rXV+MBlVrEkwoS/TK75rFQ2mjxA7+NBo8qtTAOA4UaM0XeJfn7kh2wZ+xQw2HAx0u+TiBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.18.0.tgz", + "integrity": "sha512-ojZGVcRWqWhu557cdO3pWHloIGJdzVtxs3rk0F9L+x55LsUjcMUVkEhiF7E4TMxZoF9MmIHGGs0ZX3FDLAf0Xw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.17.1", + "@mui/styled-engine": "^5.18.0", + "@mui/types": "~7.2.15", + "@mui/utils": "^5.17.1", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.17.1.tgz", + "integrity": "sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/types": "~7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers": { + "version": "6.20.2", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.20.2.tgz", + "integrity": "sha512-x1jLg8R+WhvkmUETRfX2wC+xJreMii78EXKLl6r3G+ggcAZlPyt0myID1Amf6hvJb9CtR7CgUo8BwR+1Vx9Ggw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.22", + "@mui/utils": "^5.14.16", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.8.6", + "@mui/system": "^5.8.0", + "date-fns": "^2.25.0 || ^3.2.0", + "date-fns-jalali": "^2.13.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.16", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz", + "integrity": "sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", + "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "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/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.368", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz", + "integrity": "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==", + "dev": true, + "license": "ISC" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "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/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "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/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "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/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "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/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "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==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "license": "MIT" + }, + "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": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "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/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "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/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c94e1ab --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "doormile-console-admin", + "version": "1.0.0", + "private": true, + "type": "module", + "description": "Doormile — corporate logistics & last-mile delivery admin console", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@mui/icons-material": "^5.15.20", + "@mui/lab": "^5.0.0-alpha.170", + "@mui/material": "^5.15.20", + "@mui/x-date-pickers": "^6.20.2", + "dayjs": "^1.11.11", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.23.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.1", + "vite": "^5.3.1" + } +} diff --git a/public/Doormile-logo.png b/public/Doormile-logo.png new file mode 100644 index 0000000..1cbe324 Binary files /dev/null and b/public/Doormile-logo.png differ diff --git a/public/preloader.png b/public/preloader.png new file mode 100644 index 0000000..92cb60e Binary files /dev/null and b/public/preloader.png differ diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..a50e7b9 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,76 @@ +import { Suspense, lazy } from 'react'; +import { Routes, Route, Navigate } from 'react-router-dom'; +import { Box, CircularProgress } from '@mui/material'; + +import MainLayout from '@/layout/MainLayout'; +import MinimalLayout from '@/layout/MinimalLayout'; + +const load = (factory) => { + const C = lazy(factory); + return ( + + + + } + > + + + ); +}; + +export default function App() { + return ( + + {/* Shell pages */} + }> + import('@/pages/Dashboard'))} /> + + import('@/pages/orders/OrdersList'))} /> + import('@/pages/orders/CreateOrder'))} /> + import('@/pages/orders/CreateMultipleOrders'))} /> + import('@/pages/orders/AssignOrders'))} /> + import('@/pages/orders/OrderDetails'))} /> + + import('@/pages/Deliveries'))} /> + + import('@/pages/tenants/Tenants'))} /> + import('@/pages/tenants/CreateClient'))} /> + + import('@/pages/customers/Customers'))} /> + import('@/pages/customers/CreateCustomer'))} /> + + import('@/pages/Pricing'))} /> + + import('@/pages/riders/Riders'))} /> + import('@/pages/riders/CreateRider'))} /> + import('@/pages/riders/EditRider'))} /> + + import('@/pages/reports/OrdersSummary'))} /> + import('@/pages/reports/OrdersDetails'))} /> + import('@/pages/reports/RidersSummary'))} /> + import('@/pages/reports/RidersLogs'))} /> + + import('@/pages/invoice/Invoices'))} /> + import('@/pages/invoice/InvoicePreview'))} /> + + import('@/pages/Requests'))} /> + import('@/pages/Profile'))} /> + import('@/pages/Settings'))} /> + + + {/* Full-bleed pages */} + }> + import('@/pages/auth/Login'))} /> + import('@/pages/maintenance/UnderConstruction'))} /> + import('@/pages/maintenance/ComingSoon'))} /> + import('@/pages/maintenance/Error500'))} /> + import('@/pages/maintenance/Error404'))} /> + + + } /> + import('@/pages/maintenance/Error404'))} /> + + ); +} diff --git a/src/components/EmptyState.jsx b/src/components/EmptyState.jsx new file mode 100644 index 0000000..32b6c5d --- /dev/null +++ b/src/components/EmptyState.jsx @@ -0,0 +1,33 @@ +import { Box, Typography } from '@mui/material'; +import InboxOutlinedIcon from '@mui/icons-material/InboxOutlined'; + +// ==============================|| EMPTY STATE ||============================== // + +export default function EmptyState({ icon: Icon = InboxOutlinedIcon, title = 'No records found', caption, sx }) { + return ( + + + + + + {title} + + {caption && ( + + {caption} + + )} + + ); +} diff --git a/src/components/Logo.jsx b/src/components/Logo.jsx new file mode 100644 index 0000000..723d4bd --- /dev/null +++ b/src/components/Logo.jsx @@ -0,0 +1,51 @@ +import { Box, Typography } from '@mui/material'; + +// ==============================|| DOORMILE WORDMARK LOGO ||============================== // +// Uses the brand wordmark asset (white PNG). `onDark` shows it as-is on dark/red +// surfaces; on light surfaces it is recoloured to near-black. `compact` (e.g. the +// collapsed sidebar) renders just the square "D" badge, since the wordmark won't fit. + +const LOGO_SRC = '/Doormile-logo.png'; + +export default function Logo({ onDark = false, compact = false, height = 26, sx }) { + if (compact) { + const mark = onDark ? '#FFFFFF' : '#C01227'; + const markText = onDark ? '#C01227' : '#FFFFFF'; + return ( + + + D + + + ); + } + + return ( + + + + ); +} diff --git a/src/components/MainCard.jsx b/src/components/MainCard.jsx new file mode 100644 index 0000000..b86272d --- /dev/null +++ b/src/components/MainCard.jsx @@ -0,0 +1,17 @@ +import { Card, CardHeader, CardContent, Divider, Box } from '@mui/material'; + +// ==============================|| MAIN CARD (titled surface) ||============================== // + +export default function MainCard({ title, action, children, divider = true, contentSx, sx, noPadding = false }) { + return ( + + {title && ( + <> + + {divider && } + + )} + {noPadding ? {children} : {children}} + + ); +} diff --git a/src/components/MapPlaceholder.jsx b/src/components/MapPlaceholder.jsx new file mode 100644 index 0000000..0f5e2e1 --- /dev/null +++ b/src/components/MapPlaceholder.jsx @@ -0,0 +1,59 @@ +import { Box, Typography, Chip } from '@mui/material'; +import RoomIcon from '@mui/icons-material/Room'; +import TwoWheelerIcon from '@mui/icons-material/TwoWheeler'; + +// Stylised static "map" surface — pins + red route line. Swap for Leaflet/Google later. +export default function MapPlaceholder({ height = 360, pins = [], showRoute = true, label = 'Live Tracking', riders = [] }) { + return ( + + {/* faux roads */} + + + + + {showRoute && ( + + + + )} + + {(pins.length ? pins : [ + { x: '18%', y: '78%', label: 'Pickup', color: '#00A854' }, + { x: '78%', y: '22%', label: 'Drop', color: '#C01227' } + ]).map((p, i) => ( + + + {p.label && ( + + )} + + ))} + + {riders.map((r, i) => ( + + + + + + ))} + + + + Map data © Doormile demo + + + ); +} diff --git a/src/components/PageHeader.jsx b/src/components/PageHeader.jsx new file mode 100644 index 0000000..2cc3a61 --- /dev/null +++ b/src/components/PageHeader.jsx @@ -0,0 +1,42 @@ +import { Box, Typography, Breadcrumbs, Link, Stack } from '@mui/material'; +import NavigateNextIcon from '@mui/icons-material/NavigateNext'; +import { Link as RouterLink } from 'react-router-dom'; + +// ==============================|| PAGE HEADER (title + breadcrumb + actions) ||============================== // + +export default function PageHeader({ title, breadcrumbs = [], action }) { + return ( + + + + {title} + + {breadcrumbs.length > 0 && ( + } sx={{ mt: 0.5 }}> + + Home + + {breadcrumbs.map((b, i) => + b.to && i < breadcrumbs.length - 1 ? ( + + {b.label} + + ) : ( + + {b.label} + + ) + )} + + )} + + {action && {action}} + + ); +} diff --git a/src/components/StatCard.jsx b/src/components/StatCard.jsx new file mode 100644 index 0000000..5adb385 --- /dev/null +++ b/src/components/StatCard.jsx @@ -0,0 +1,55 @@ +import { Card, CardContent, Box, Typography, Avatar, Stack } from '@mui/material'; +import ArrowUpwardRoundedIcon from '@mui/icons-material/ArrowUpwardRounded'; +import ArrowDownwardRoundedIcon from '@mui/icons-material/ArrowDownwardRounded'; + +// ==============================|| STAT / KPI CARD ||============================== // + +export default function StatCard({ title, value, icon: Icon, color = 'primary', trend, caption }) { + const trendUp = typeof trend === 'number' ? trend >= 0 : null; + + return ( + + + + + + {title} + + + {value} + + + {Icon && ( + + + + )} + + {(trendUp !== null || caption) && ( + + {trendUp !== null && ( + <> + {trendUp ? ( + + ) : ( + + )} + + {Math.abs(trend)}% + + + )} + {caption && ( + + {caption} + + )} + + )} + + + ); +} diff --git a/src/components/StatusChip.jsx b/src/components/StatusChip.jsx new file mode 100644 index 0000000..ce201e2 --- /dev/null +++ b/src/components/StatusChip.jsx @@ -0,0 +1,54 @@ +import { Chip } from '@mui/material'; + +// ==============================|| STATUS CHIP ||============================== // +// Soft-filled status chips used across orders, deliveries, riders, invoices. + +const MAP = { + // orders / deliveries + pending: { color: 'warning', label: 'Pending' }, + created: { color: 'info', label: 'Created' }, + assigned: { color: 'info', label: 'Assigned' }, + accepted: { color: 'info', label: 'Accepted' }, + arrived: { color: 'info', label: 'Arrived' }, + picked: { color: 'primary', label: 'Picked' }, + 'in-transit': { color: 'info', label: 'In Transit' }, + active: { color: 'primary', label: 'Active' }, + delivered: { color: 'success', label: 'Delivered' }, + completed: { color: 'success', label: 'Completed' }, + skipped: { color: 'warning', label: 'Skipped' }, + failed: { color: 'error', label: 'Failed' }, + cancelled: { color: 'error', label: 'Cancelled' }, + // riders / tenants + online: { color: 'success', label: 'Online' }, + offline: { color: 'default', label: 'Offline' }, + 'on-delivery': { color: 'info', label: 'On Delivery' }, + inactive: { color: 'default', label: 'Inactive' }, + // invoices + paid: { color: 'success', label: 'Paid' }, + open: { color: 'info', label: 'Open' }, + overdue: { color: 'error', label: 'Overdue' }, + prepaid: { color: 'success', label: 'Prepaid' }, + cod: { color: 'warning', label: 'COD' } +}; + +const TONE = { + success: { bg: '#E3F6EC', fg: '#00773B' }, + warning: { bg: '#FFF7E0', fg: '#8A6500' }, + info: { bg: '#E0F7F8', fg: '#00727B' }, + error: { bg: '#FEEAE9', fg: '#A82216' }, + primary: { bg: '#F8E0E3', fg: '#9E0E20' }, + default: { bg: '#F0F0F0', fg: '#595959' } +}; + +export default function StatusChip({ status, size = 'small', label, sx }) { + const key = String(status || '').toLowerCase().replace(/\s+/g, '-'); + const cfg = MAP[key] || { color: 'default', label: status }; + const tone = TONE[cfg.color] || TONE.default; + return ( + + ); +} diff --git a/src/components/TabLabelCount.jsx b/src/components/TabLabelCount.jsx new file mode 100644 index 0000000..8fe1b99 --- /dev/null +++ b/src/components/TabLabelCount.jsx @@ -0,0 +1,30 @@ +import { Stack, Box } from '@mui/material'; + +// ==============================|| TAB LABEL WITH INLINE COUNT PILL ||============================== // +// Renders a tab label with the count laid out inline (not an overlapping Badge), +// so adjacent tabs never clip the number. The pill highlights when its tab is active. + +export default function TabLabelCount({ label, count, active = false }) { + return ( + + {label} + + {count} + + + ); +} diff --git a/src/components/UserAvatar.jsx b/src/components/UserAvatar.jsx new file mode 100644 index 0000000..1e150f6 --- /dev/null +++ b/src/components/UserAvatar.jsx @@ -0,0 +1,12 @@ +import { Avatar } from '@mui/material'; +import { stringToColor, initials } from '@/utils/format'; + +// ==============================|| INITIALS AVATAR ||============================== // + +export default function UserAvatar({ name = '', size = 32, sx }) { + return ( + + {initials(name)} + + ); +} diff --git a/src/components/charts/AreaChart.jsx b/src/components/charts/AreaChart.jsx new file mode 100644 index 0000000..96615b5 --- /dev/null +++ b/src/components/charts/AreaChart.jsx @@ -0,0 +1,66 @@ +import { Box, useTheme } from '@mui/material'; + +// Lightweight dependency-free area/line chart. +// series: [{ name, color, data: number[] }], labels: string[] +export default function AreaChart({ series = [], labels = [], height = 260 }) { + const theme = useTheme(); + const W = 720; + const H = height; + const pad = { l: 40, r: 16, t: 16, b: 28 }; + const innerW = W - pad.l - pad.r; + const innerH = H - pad.t - pad.b; + + const all = series.flatMap((s) => s.data); + const max = Math.max(...all, 1); + const min = 0; + const n = labels.length; + + const x = (i) => pad.l + (n <= 1 ? 0 : (i / (n - 1)) * innerW); + const y = (v) => pad.t + innerH - ((v - min) / (max - min)) * innerH; + + const ticks = 4; + + return ( + + + {Array.from({ length: ticks + 1 }).map((_, i) => { + const gy = pad.t + (i / ticks) * innerH; + const val = Math.round(max - (i / ticks) * (max - min)); + return ( + + + + {val} + + + ); + })} + {labels.map((lb, i) => ( + + {lb} + + ))} + {series.map((s) => { + const line = s.data.map((v, i) => `${i === 0 ? 'M' : 'L'} ${x(i)} ${y(v)}`).join(' '); + const area = `${line} L ${x(s.data.length - 1)} ${pad.t + innerH} L ${x(0)} ${pad.t + innerH} Z`; + const gid = `grad-${s.name.replace(/\s/g, '')}`; + return ( + + + + + + + + {s.fill !== false && } + + {s.data.map((v, i) => ( + + ))} + + ); + })} + + + ); +} diff --git a/src/components/charts/DonutChart.jsx b/src/components/charts/DonutChart.jsx new file mode 100644 index 0000000..b85c52a --- /dev/null +++ b/src/components/charts/DonutChart.jsx @@ -0,0 +1,59 @@ +import { Box, Stack, Typography } from '@mui/material'; + +// Dependency-free donut chart. data: [{ label, value, color }] +export default function DonutChart({ data = [], size = 180, thickness = 26, centerLabel, centerValue }) { + const total = data.reduce((s, d) => s + d.value, 0) || 1; + const r = (size - thickness) / 2; + const c = 2 * Math.PI * r; + let offset = 0; + + return ( + + + + + + {data.map((d, i) => { + const len = (d.value / total) * c; + const seg = ( + + ); + offset += len; + return seg; + })} + + + + {centerValue ?? total} + {centerLabel ?? 'Total'} + + + + {data.map((d) => ( + + + {d.label} + {d.value.toLocaleString('en-IN')} + + ))} + + + ); +} diff --git a/src/data/mock.js b/src/data/mock.js new file mode 100644 index 0000000..72b9b53 --- /dev/null +++ b/src/data/mock.js @@ -0,0 +1,181 @@ +// ==============================|| DOORMILE - MOCK DATA ||============================== // +// Static demo data powering every screen. + +export const locations = ['Bengaluru', 'Mumbai', 'Delhi NCR', 'Hyderabad', 'Chennai', 'Pune']; + +export const tenantsList = [ + 'Freshly Foods', + 'UrbanCart', + 'MediQuick Pharma', + 'BloomBox Florists', + 'TechNova Retail', + 'GreenLeaf Organics' +]; + +export const orders = [ + { id: 'DM-10241', tenant: 'Freshly Foods', location: 'Bengaluru', pickup: 'Koramangala Hub', drop: 'HSR Layout, Sec 2', customer: 'Riya Sharma', qty: 3, cod: 0, kms: 4.2, charges: 86, notes: 'Leave at door', status: 'delivered', date: '2026-06-04', payment: 'prepaid' }, + { id: 'DM-10242', tenant: 'UrbanCart', location: 'Bengaluru', pickup: 'Indiranagar Store', drop: 'Whitefield, Phase 1', customer: 'Karthik Rao', qty: 1, cod: 1299, kms: 12.8, charges: 142, notes: 'Call on arrival', status: 'active', date: '2026-06-05', payment: 'cod' }, + { id: 'DM-10243', tenant: 'MediQuick Pharma', location: 'Mumbai', pickup: 'Andheri West', drop: 'Bandra East', customer: 'Neha Kulkarni', qty: 2, cod: 0, kms: 6.5, charges: 98, notes: '', status: 'picked', date: '2026-06-05', payment: 'prepaid' }, + { id: 'DM-10244', tenant: 'BloomBox Florists', location: 'Delhi NCR', pickup: 'Connaught Place', drop: 'Saket, Block C', customer: 'Arjun Mehta', qty: 1, cod: 450, kms: 9.1, charges: 110, notes: 'Fragile', status: 'pending', date: '2026-06-05', payment: 'cod' }, + { id: 'DM-10245', tenant: 'Freshly Foods', location: 'Bengaluru', pickup: 'Koramangala Hub', drop: 'BTM Layout', customer: 'Sneha Iyer', qty: 4, cod: 0, kms: 3.4, charges: 74, notes: '', status: 'cancelled', date: '2026-06-03', payment: 'prepaid' }, + { id: 'DM-10246', tenant: 'TechNova Retail', location: 'Hyderabad', pickup: 'Hitech City', drop: 'Gachibowli', customer: 'Vikram Reddy', qty: 1, cod: 4999, kms: 5.6, charges: 120, notes: 'ID required', status: 'created', date: '2026-06-05', payment: 'cod' }, + { id: 'DM-10247', tenant: 'GreenLeaf Organics', location: 'Pune', pickup: 'Koregaon Park', drop: 'Viman Nagar', customer: 'Pooja Desai', qty: 2, cod: 0, kms: 7.2, charges: 102, notes: '', status: 'delivered', date: '2026-06-04', payment: 'prepaid' }, + { id: 'DM-10248', tenant: 'UrbanCart', location: 'Chennai', pickup: 'T. Nagar', drop: 'Adyar', customer: 'Suresh Kumar', qty: 1, cod: 799, kms: 8.3, charges: 108, notes: 'Weekend slot', status: 'pending', date: '2026-06-05', payment: 'cod' } +]; + +export const orderTimeline = [ + { label: 'Order Placed', time: '09:12 AM', done: true }, + { label: 'Picked Up', time: '09:48 AM', done: true }, + { label: 'In Transit', time: '10:05 AM', done: true }, + { label: 'Delivered', time: '—', done: false } +]; + +export const deliveries = orders.map((o, i) => ({ + ...o, + rider: ['Mohan Das', 'Imran Sheikh', 'Ravi Teja', 'Sandeep Roy', 'Faisal Khan'][i % 5], + amount: o.charges, + products: [ + { name: 'Item A', description: 'Standard pack', qty: o.qty, cost: 40, price: 60, tax: 5, amount: 60 * o.qty } + ] +})); + +export const riders = [ + { id: 'RDR-001', userId: 'U1043', name: 'Mohan Das', phone: '+91 98450 11223', address: 'Koramangala, Bengaluru', vehicle: 'Honda Activa', vehicleNo: 'KA-05-HJ-4521', shift: 'Morning', start: '08:00', end: '16:00', fare: 320, fuel: 110, status: 'online', deliveries: 14, rating: 4.8 }, + { id: 'RDR-002', userId: 'U1044', name: 'Imran Sheikh', phone: '+91 98860 44781', address: 'Andheri, Mumbai', vehicle: 'TVS Jupiter', vehicleNo: 'MH-02-CD-9087', shift: 'Evening', start: '14:00', end: '22:00', fare: 280, fuel: 95, status: 'on-delivery', deliveries: 11, rating: 4.6 }, + { id: 'RDR-003', userId: 'U1045', name: 'Ravi Teja', phone: '+91 99000 22119', address: 'Gachibowli, Hyderabad', vehicle: 'Hero Splendor', vehicleNo: 'TS-09-AB-3344', shift: 'Morning', start: '08:00', end: '16:00', fare: 300, fuel: 120, status: 'online', deliveries: 9, rating: 4.9 }, + { id: 'RDR-004', userId: 'U1046', name: 'Sandeep Roy', phone: '+91 90080 55672', address: 'Saket, Delhi', vehicle: 'Bajaj Pulsar', vehicleNo: 'DL-3C-XY-1290', shift: 'Night', start: '22:00', end: '06:00', fare: 350, fuel: 130, status: 'offline', deliveries: 0, rating: 4.4 }, + { id: 'RDR-005', userId: 'U1047', name: 'Faisal Khan', phone: '+91 97410 88123', address: 'Adyar, Chennai', vehicle: 'Honda Dio', vehicleNo: 'TN-07-PQ-6655', shift: 'Evening', start: '14:00', end: '22:00', fare: 290, fuel: 100, status: 'on-delivery', deliveries: 7, rating: 4.7 } +]; + +export const riderLogs = [ + { location: '12.9352, 77.6245', battery: '82%', charging: 'No', speed: '24 km/h', accuracy: '5 m', time: '10:42 AM', order: 'DM-10242', status: 'active' }, + { location: '12.9298, 77.6848', battery: '80%', charging: 'No', speed: '0 km/h', accuracy: '8 m', time: '10:38 AM', order: 'DM-10242', status: 'arrived' }, + { location: '12.9101, 77.6446', battery: '84%', charging: 'No', speed: '31 km/h', accuracy: '4 m', time: '10:25 AM', order: 'DM-10242', status: 'picked' } +]; + +export const customers = [ + { id: 1, name: 'Riya Sharma', phone: '+91 98111 22334', email: 'riya.sharma@mail.com', address: '24, 1st Cross, HSR Layout', location: 'Bengaluru', city: 'Bengaluru', state: 'Karnataka', postcode: '560102', totalOrders: 18, joined: '2025-03-12' }, + { id: 2, name: 'Karthik Rao', phone: '+91 98222 33445', email: 'karthik.rao@mail.com', address: '8, Palm Meadows, Whitefield', location: 'Bengaluru', city: 'Bengaluru', state: 'Karnataka', postcode: '560066', totalOrders: 7, joined: '2025-07-21' }, + { id: 3, name: 'Neha Kulkarni', phone: '+91 98333 44556', email: 'neha.k@mail.com', address: '102, Sea View, Bandra East', location: 'Mumbai', city: 'Mumbai', state: 'Maharashtra', postcode: '400051', totalOrders: 31, joined: '2024-11-02' }, + { id: 4, name: 'Arjun Mehta', phone: '+91 98444 55667', email: 'arjun.m@mail.com', address: 'C-14, Saket', location: 'Delhi NCR', city: 'New Delhi', state: 'Delhi', postcode: '110017', totalOrders: 5, joined: '2025-09-18' }, + { id: 5, name: 'Pooja Desai', phone: '+91 98555 66778', email: 'pooja.d@mail.com', address: '4, Lane 5, Koregaon Park', location: 'Pune', city: 'Pune', state: 'Maharashtra', postcode: '411001', totalOrders: 12, joined: '2025-01-30' } +]; + +export const tenants = [ + { id: 1, name: 'Freshly Foods', contact: 'Anil Gupta', phone: '+91 80451 22000', email: 'ops@freshlyfoods.in', address: 'Koramangala 4th Block', city: 'Bengaluru', postcode: '560034', lat: '12.9352', lng: '77.6245', status: 'active', volume: 1240 }, + { id: 2, name: 'UrbanCart', contact: 'Meera Nair', phone: '+91 22480 11233', email: 'logistics@urbancart.com', address: 'Indiranagar 100ft Rd', city: 'Bengaluru', postcode: '560038', lat: '12.9719', lng: '77.6412', status: 'active', volume: 980 }, + { id: 3, name: 'MediQuick Pharma', contact: 'Rohit Sen', phone: '+91 22556 99001', email: 'dispatch@mediquick.in', address: 'Andheri West', city: 'Mumbai', postcode: '400058', lat: '19.1351', lng: '72.8290', status: 'pending', volume: 410 }, + { id: 4, name: 'BloomBox Florists', contact: 'Tina Roy', phone: '+91 11409 33221', email: 'hello@bloombox.in', address: 'Connaught Place', city: 'New Delhi', postcode: '110001', lat: '28.6315', lng: '77.2167', status: 'inactive', volume: 120 }, + { id: 5, name: 'TechNova Retail', contact: 'Sameer Joshi', phone: '+91 40229 77654', email: 'ops@technova.in', address: 'Hitech City', city: 'Hyderabad', postcode: '500081', lat: '17.4435', lng: '78.3772', status: 'active', volume: 760 } +]; + +export const tenantPricing = [ + { date: '2025-04-01', slab: '0-5 km', basePrice: 40, minKms: 2, pricePerKm: 9, otherCharges: 0 }, + { date: '2025-04-01', slab: '5-10 km', basePrice: 60, minKms: 5, pricePerKm: 8, otherCharges: 10 }, + { date: '2025-04-01', slab: '10+ km', basePrice: 90, minKms: 10, pricePerKm: 7, otherCharges: 15 } +]; + +export const pricing = [ + { id: 1, location: 'Bengaluru', pricingId: 'PR-001', name: 'Standard', slab: '0-5 km', basePrice: 40, minKm: 2, pricePerKm: 9, maxKm: 5, minOrders: 1 }, + { id: 2, location: 'Bengaluru', pricingId: 'PR-002', name: 'Express', slab: '5-10 km', basePrice: 65, minKm: 5, pricePerKm: 8, maxKm: 10, minOrders: 1 }, + { id: 3, location: 'Mumbai', pricingId: 'PR-003', name: 'Standard', slab: '0-5 km', basePrice: 45, minKm: 2, pricePerKm: 10, maxKm: 5, minOrders: 1 }, + { id: 4, location: 'Delhi NCR', pricingId: 'PR-004', name: 'Bulk', slab: '10+ km', basePrice: 95, minKm: 10, pricePerKm: 7, maxKm: 25, minOrders: 5 } +]; + +export const invoices = [ + { id: 1, invoiceId: 'INV-2026-0041', client: 'Freshly Foods', invoiceDate: '2026-06-01', dueDate: '2026-06-15', period: 'May 2026', count: 1240, amount: 106800, status: 'paid' }, + { id: 2, invoiceId: 'INV-2026-0042', client: 'UrbanCart', invoiceDate: '2026-06-01', dueDate: '2026-06-15', period: 'May 2026', count: 980, amount: 84300, status: 'open' }, + { id: 3, invoiceId: 'INV-2026-0043', client: 'MediQuick Pharma', invoiceDate: '2026-05-01', dueDate: '2026-05-15', period: 'Apr 2026', count: 410, amount: 35200, status: 'overdue' }, + { id: 4, invoiceId: 'INV-2026-0044', client: 'TechNova Retail', invoiceDate: '2026-06-01', dueDate: '2026-06-20', period: 'May 2026', count: 760, amount: 65400, status: 'open' }, + { id: 5, invoiceId: 'INV-2026-0045', client: 'GreenLeaf Organics', invoiceDate: '2026-05-01', dueDate: '2026-05-15', period: 'Apr 2026', count: 540, amount: 46900, status: 'paid' } +]; + +export const invoiceLineItems = [ + { particulars: 'Standard delivery (0-5 km)', unit: 'order', qty: 720, rate: 74, other: 0, amount: 53280 }, + { particulars: 'Express delivery (5-10 km)', unit: 'order', qty: 380, rate: 108, other: 0, amount: 41040 }, + { particulars: 'Bulk delivery (10+ km)', unit: 'order', qty: 140, rate: 142, other: 8, amount: 21000 }, + { particulars: 'COD handling fee', unit: 'order', qty: 260, rate: 6, other: 0, amount: 1560 } +]; + +export const requests = [ + { id: 1, requestor: 'Freshly Foods', bank: 'HDFC Bank', ifsc: 'HDFC0001234', refNo: 'RQ-88121', amount: 24500, reason: 'Weekly settlement payout', contact: 'Anil Gupta', address: 'Koramangala 4th Block', city: 'Bengaluru', zip: '560034', accountNo: '5012 3344 7788', pricing: [{ category: 'Standard', skill: 'Bike', cost: 9 }, { category: 'Express', skill: 'Bike', cost: 12 }] }, + { id: 2, requestor: 'UrbanCart', bank: 'ICICI Bank', ifsc: 'ICIC0004567', refNo: 'RQ-88122', amount: 18900, reason: 'Fuel reimbursement', contact: 'Meera Nair', address: 'Indiranagar', city: 'Bengaluru', zip: '560038', accountNo: '6022 1199 4455', pricing: [{ category: 'Standard', skill: 'Bike', cost: 8 }] }, + { id: 3, requestor: 'MediQuick Pharma', bank: 'Axis Bank', ifsc: 'UTIB0007788', refNo: 'RQ-88123', amount: 9700, reason: 'Adjustment - April', contact: 'Rohit Sen', address: 'Andheri West', city: 'Mumbai', zip: '400058', accountNo: '7033 5566 1122', pricing: [{ category: 'Standard', skill: 'Bike', cost: 10 }] } +]; + +// reports +export const ordersSummary = tenants.map((t, i) => ({ + id: t.id, + tenant: t.name, + location: t.city, + orders: { pending: [2, 1, 0, 3, 1][i], cancelled: [1, 0, 2, 0, 1][i], completed: [38, 27, 11, 4, 22][i] }, + deliveries: { pending: [1, 1, 0, 2, 0][i], cancelled: [0, 0, 1, 0, 1][i], completed: [38, 26, 10, 4, 22][i] }, + collection: [12400, 8800, 3200, 900, 6700][i], + kms: [184, 142, 66, 28, 121][i], + actualKms: [190, 148, 70, 30, 124][i], + amount: [4280, 3120, 1420, 520, 2410][i], + riders: [ + { rider: 'Mohan Das', orders: 14, deliveries: 14, pending: 1, cancelled: 0, completed: 13, collection: 5200, kms: 64, actualKms: 66, charges: 1480 }, + { rider: 'Imran Sheikh', orders: 11, deliveries: 10, pending: 0, cancelled: 1, completed: 9, collection: 4100, kms: 58, actualKms: 60, charges: 1280 } + ] +})); + +export const ordersDetailReport = orders.map((o, i) => ({ + id: o.id, + client: o.tenant, + pickup: o.pickup, + drop: o.drop, + status: o.status, + assigned: '09:12', + accepted: i % 5 === 3 ? '—' : '09:14', + arrived: ['09:40', '—', '09:35', '—', '—', '—', '09:50', '—'][i], + picked: ['09:48', '—', '09:42', '—', '—', '—', '09:58', '—'][i], + active: ['10:05', '—', '10:00', '—', '—', '—', '10:12', '—'][i], + delivered: o.status === 'delivered' ? '10:30' : '—', + cancelled: o.status === 'cancelled' ? '09:20' : '—', + notes: o.notes, + kms: o.kms, + charges: o.charges +})); + +export const ridersSummary = riders.map((r, i) => ({ + id: r.id, + rider: r.name, + orders: r.deliveries + [2, 1, 1, 0, 1][i], + pending: [1, 0, 1, 0, 0][i], + cancelled: [0, 1, 0, 0, 1][i], + delivered: r.deliveries, + kms: [64, 58, 47, 0, 39][i], + actualKms: [66, 60, 49, 0, 41][i], + amount: [1480, 1280, 1120, 0, 980][i], + clients: [ + { client: 'Freshly Foods', all: 8, pending: 1, completed: 7, cancelled: 0, kms: 32, actualKms: 33, amount: 740 }, + { client: 'UrbanCart', all: 6, pending: 0, completed: 6, cancelled: 0, kms: 32, actualKms: 33, amount: 740 } + ] +})); + +export const ridersLive = riders.map((r, i) => ({ + ...r, + userid: r.userId, + lastLog: ['10:42 AM', '10:39 AM', '10:40 AM', 'Yesterday', '10:35 AM'][i], + active: r.status !== 'offline', + lat: [12.9352, 19.1351, 17.4435, 28.6315, 13.0067][i], + lng: [77.6245, 72.829, 78.3772, 77.2167, 80.2206][i] +})); + +// dashboard chart series (monthly orders) +export const ordersTrend = [ + { m: 'Jan', orders: 820, delivered: 760 }, + { m: 'Feb', orders: 932, delivered: 880 }, + { m: 'Mar', orders: 1010, delivered: 965 }, + { m: 'Apr', orders: 1180, delivered: 1120 }, + { m: 'May', orders: 1290, delivered: 1240 }, + { m: 'Jun', orders: 1402, delivered: 1330 } +]; + +export const statusBreakdown = [ + { label: 'Delivered', value: 1240, color: '#00A854' }, + { label: 'In Transit', value: 96, color: '#00A2AE' }, + { label: 'Pending', value: 48, color: '#FFBF00' }, + { label: 'Cancelled', value: 18, color: '#F04134' } +]; diff --git a/src/layout/MainLayout/Header.jsx b/src/layout/MainLayout/Header.jsx new file mode 100644 index 0000000..46a9e16 --- /dev/null +++ b/src/layout/MainLayout/Header.jsx @@ -0,0 +1,261 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + AppBar, + Toolbar, + IconButton, + Box, + InputBase, + Badge, + Avatar, + Typography, + Menu, + MenuItem, + Divider, + ListItemIcon, + ListItemText, + Tooltip, + Button, + Stack, + alpha +} from '@mui/material'; +import MenuIcon from '@mui/icons-material/Menu'; +import SearchIcon from '@mui/icons-material/Search'; +import NotificationsNoneIcon from '@mui/icons-material/NotificationsNone'; +import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline'; +import PersonOutlineIcon from '@mui/icons-material/PersonOutline'; +import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined'; +import LogoutIcon from '@mui/icons-material/Logout'; +import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; +import TwoWheelerOutlinedIcon from '@mui/icons-material/TwoWheelerOutlined'; +import PaymentsOutlinedIcon from '@mui/icons-material/PaymentsOutlined'; +import AssignmentOutlinedIcon from '@mui/icons-material/AssignmentOutlined'; +import DoneAllIcon from '@mui/icons-material/DoneAll'; + +import Logo from '@/components/Logo'; + +const RED = '#C01227'; + +const INITIAL_NOTIFICATIONS = [ + { id: 1, icon: Inventory2OutlinedIcon, title: 'New order #ORD-10482 placed', time: '2 min ago', to: '/orders', read: false }, + { id: 2, icon: TwoWheelerOutlinedIcon, title: 'Rider Imran went online', time: '18 min ago', to: '/riders', read: false }, + { id: 3, icon: PaymentsOutlinedIcon, title: 'Invoice INV-2041 marked paid', time: '1 hr ago', to: '/invoice', read: false }, + { id: 4, icon: AssignmentOutlinedIcon, title: '3 new onboarding requests', time: '3 hrs ago', to: '/requests', read: true } +]; + +const MESSAGES = [ + { id: 1, name: 'Priya Nair', text: 'Can we reroute the MG Road batch?', time: '5 min ago', initials: 'PN' }, + { id: 2, name: 'Imran Khan', text: 'Reached the warehouse, loading now.', time: '22 min ago', initials: 'IK' }, + { id: 3, name: 'Acme Logistics', text: 'Please confirm the revised pricing.', time: '2 hrs ago', initials: 'AL' } +]; + +export default function Header({ onToggle }) { + const navigate = useNavigate(); + const [account, setAccount] = useState(null); + const [notifAnchor, setNotifAnchor] = useState(null); + const [msgAnchor, setMsgAnchor] = useState(null); + const [notifications, setNotifications] = useState(INITIAL_NOTIFICATIONS); + const [search, setSearch] = useState(''); + + const unread = notifications.filter((n) => !n.read).length; + + const openNotif = (e) => setNotifAnchor(e.currentTarget); + const closeNotif = () => setNotifAnchor(null); + const markAllRead = () => setNotifications((prev) => prev.map((n) => ({ ...n, read: true }))); + const onNotifClick = (n) => { + setNotifications((prev) => prev.map((x) => (x.id === n.id ? { ...x, read: true } : x))); + closeNotif(); + navigate(n.to); + }; + + const submitSearch = (e) => { + e.preventDefault(); + const q = search.trim(); + if (q) navigate(`/orders?q=${encodeURIComponent(q)}`); + }; + + return ( + t.zIndex.drawer + 1, boxShadow: '0 1px 0 rgba(0,0,0,0.06)' }} + > + + + + + + {/* Brand wordmark — left side */} + navigate('/dashboard')} + sx={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }} + > + + + + + + {/* Search — moved to the right */} + + + setSearch(e.target.value)} + placeholder="Search orders, riders, customers…" + sx={{ color: '#fff', fontSize: '0.875rem', flex: 1, '&::placeholder': { color: '#fff' } }} + inputProps={{ style: { color: '#fff' }, 'aria-label': 'search' }} + /> + + + + setMsgAnchor(e.currentTarget)}> + + + + + + + + + + + + + + setAccount(e.currentTarget)} + sx={{ display: 'flex', alignItems: 'center', gap: 1, ml: 0.5, cursor: 'pointer', py: 0.5, px: 0.5, borderRadius: 2, '&:hover': { bgcolor: alpha('#fff', 0.14) } }} + > + AD + + + Aman Deshmukh + + + Operations Admin + + + + + {/* Notifications dropdown */} + + + + Notifications + + + + + {notifications.length === 0 && ( + + + + )} + {notifications.map((n) => { + const Icon = n.icon; + return ( + onNotifClick(n)} sx={{ py: 1.25, whiteSpace: 'normal', alignItems: 'flex-start' }}> + + + + + + + {!n.read && } + + ); + })} + + { closeNotif(); navigate('/requests'); }} sx={{ justifyContent: 'center', color: 'primary.main', fontWeight: 600 }}> + View all activity + + + + {/* Messages dropdown */} + setMsgAnchor(null)} + transformOrigin={{ horizontal: 'right', vertical: 'top' }} + anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} + PaperProps={{ sx: { mt: 1, width: 340, maxWidth: '90vw' } }} + > + + Messages + + + {MESSAGES.map((m) => ( + setMsgAnchor(null)} sx={{ py: 1.25, whiteSpace: 'normal', alignItems: 'flex-start' }}> + + + {m.initials} + + + + + {m.time} + + + ))} + + + {/* Account dropdown */} + setAccount(null)} + transformOrigin={{ horizontal: 'right', vertical: 'top' }} + anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} + PaperProps={{ sx: { mt: 1, minWidth: 200 } }} + > + { setAccount(null); navigate('/profile'); }}> + + View Profile + + { setAccount(null); navigate('/settings'); }}> + + Settings + + + { setAccount(null); navigate('/login'); }} sx={{ color: 'error.main' }}> + + Logout + + + + + ); +} diff --git a/src/layout/MainLayout/Sidebar.jsx b/src/layout/MainLayout/Sidebar.jsx new file mode 100644 index 0000000..1be9cab --- /dev/null +++ b/src/layout/MainLayout/Sidebar.jsx @@ -0,0 +1,200 @@ +import { useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { + Drawer, + Box, + List, + ListItemButton, + ListItemIcon, + ListItemText, + Typography, + Collapse, + Tooltip, + Toolbar +} from '@mui/material'; +import ExpandLess from '@mui/icons-material/ExpandLess'; +import ExpandMore from '@mui/icons-material/ExpandMore'; +import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord'; + +import navItems from '@/menu/navItems'; +import Logo from '@/components/Logo'; + +export const DRAWER_WIDTH = 264; +export const MINI_WIDTH = 78; + +const RED = '#C01227'; + +function NavLeaf({ item, open, active, depth = 0, onClick }) { + const Icon = item.icon; + const button = ( + + + {depth > 0 && !Icon ? : Icon ? : null} + + {open && ( + + )} + + ); + + return open ? button : {button}; +} + +export default function Sidebar({ open, mobileOpen, onMobileClose, isMobile }) { + const location = useLocation(); + const navigate = useNavigate(); + const isActive = (url) => url && location.pathname.startsWith(url); + const expanded = open || isMobile; + + const initialOpen = navItems + .flatMap((g) => g.items) + .filter((i) => i.children && i.children.some((c) => isActive(c.url))) + .map((i) => i.id); + const [collapse, setCollapse] = useState(initialOpen); + + const go = (url) => { + navigate(url); + if (isMobile) onMobileClose(); + }; + + const content = ( + + + + + + {navItems.map((grp) => ( + + {expanded && ( + + {grp.group} + + )} + + {grp.items.map((item) => { + if (item.children) { + const opened = collapse.includes(item.id); + const childActive = item.children.some((c) => isActive(c.url)); + const Icon = item.icon; + const head = ( + + expanded + ? setCollapse((p) => (p.includes(item.id) ? p.filter((x) => x !== item.id) : [...p, item.id])) + : go(item.children[0].url) + } + sx={{ + minHeight: 44, + my: 0.25, + mx: expanded ? 1 : 0.75, + px: expanded ? 1.5 : 0, + justifyContent: expanded ? 'flex-start' : 'center', + borderRadius: 2, + color: childActive ? '#fff' : 'rgba(255,255,255,0.78)', + bgcolor: childActive && !opened ? 'rgba(255,255,255,0.12)' : 'transparent', + '&:hover': { bgcolor: 'rgba(255,255,255,0.12)', color: '#fff' } + }} + > + + + + {expanded && ( + <> + + {opened ? : } + + )} + + ); + return ( + + {expanded ? head : {head}} + {expanded && ( + + + {item.children.map((c) => ( + go(c.url)} /> + ))} + + + )} + + ); + } + return ( + go(item.url)} /> + ); + })} + + + ))} + + {expanded && ( + + + Doormile Console v1.0 + + + )} + + ); + + if (isMobile) { + return ( + + {content} + + ); + } + + return ( + t.transitions.create('width', { duration: t.transitions.duration.standard }) + } + }} + open={open} + > + {content} + + ); +} diff --git a/src/layout/MainLayout/index.jsx b/src/layout/MainLayout/index.jsx new file mode 100644 index 0000000..5caab1b --- /dev/null +++ b/src/layout/MainLayout/index.jsx @@ -0,0 +1,45 @@ +import { useState } from 'react'; +import { Outlet } from 'react-router-dom'; +import { Box, Toolbar, useMediaQuery } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; + +import Header from './Header'; +import Sidebar, { DRAWER_WIDTH, MINI_WIDTH } from './Sidebar'; + +export default function MainLayout() { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('lg')); + const [open, setOpen] = useState(true); + const [mobileOpen, setMobileOpen] = useState(false); + + const toggle = () => { + if (isMobile) setMobileOpen((p) => !p); + else setOpen((p) => !p); + }; + + return ( + +
+ setMobileOpen(false)} + /> + + + + + + + + ); +} diff --git a/src/layout/MinimalLayout.jsx b/src/layout/MinimalLayout.jsx new file mode 100644 index 0000000..d1357cf --- /dev/null +++ b/src/layout/MinimalLayout.jsx @@ -0,0 +1,11 @@ +import { Outlet } from 'react-router-dom'; +import { Box } from '@mui/material'; + +// Used by auth + maintenance pages — full-bleed, no shell. +export default function MinimalLayout() { + return ( + + + + ); +} diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..3997aa9 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { ThemeProvider, CssBaseline } from '@mui/material'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; + +import theme from '@/theme'; +import App from '@/App'; + +ReactDOM.createRoot(document.getElementById('root')).render( + + + + + + + + + + +); diff --git a/src/menu/navItems.jsx b/src/menu/navItems.jsx new file mode 100644 index 0000000..571e8c5 --- /dev/null +++ b/src/menu/navItems.jsx @@ -0,0 +1,55 @@ +import DashboardOutlinedIcon from '@mui/icons-material/DashboardOutlined'; +import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; +import MopedOutlinedIcon from '@mui/icons-material/MopedOutlined'; +import ApartmentOutlinedIcon from '@mui/icons-material/ApartmentOutlined'; +import PaymentsOutlinedIcon from '@mui/icons-material/PaymentsOutlined'; +import GroupsOutlinedIcon from '@mui/icons-material/GroupsOutlined'; +import TwoWheelerOutlinedIcon from '@mui/icons-material/TwoWheelerOutlined'; +import BarChartOutlinedIcon from '@mui/icons-material/BarChartOutlined'; +import ReceiptLongOutlinedIcon from '@mui/icons-material/ReceiptLongOutlined'; +import AssignmentOutlinedIcon from '@mui/icons-material/AssignmentOutlined'; +import SummarizeOutlinedIcon from '@mui/icons-material/SummarizeOutlined'; +import FactCheckOutlinedIcon from '@mui/icons-material/FactCheckOutlined'; +import MapOutlinedIcon from '@mui/icons-material/MapOutlined'; + +// ==============================|| DOORMILE - SIDEBAR NAV CONFIG ||============================== // + +const navItems = [ + { + group: 'Operations', + items: [ + { id: 'dashboard', title: 'Dashboard', url: '/dashboard', icon: DashboardOutlinedIcon }, + { id: 'orders', title: 'Orders', url: '/orders', icon: Inventory2OutlinedIcon }, + { id: 'deliveries', title: 'Deliveries', url: '/deliveries', icon: MopedOutlinedIcon } + ] + }, + { + group: 'Network', + items: [ + { id: 'tenants', title: 'Tenants', url: '/tenants', icon: ApartmentOutlinedIcon }, + { id: 'pricing', title: 'Pricing', url: '/pricing', icon: PaymentsOutlinedIcon }, + { id: 'customers', title: 'Customers', url: '/customers', icon: GroupsOutlinedIcon }, + { id: 'riders', title: 'Riders', url: '/riders', icon: TwoWheelerOutlinedIcon } + ] + }, + { + group: 'Finance & Insights', + items: [ + { + id: 'reports', + title: 'Reports', + icon: BarChartOutlinedIcon, + children: [ + { id: 'orders-summary', title: 'Order Summary', url: '/reports/orders-summary', icon: SummarizeOutlinedIcon }, + { id: 'orders-details', title: 'Order Details', url: '/reports/orders-details', icon: FactCheckOutlinedIcon }, + { id: 'riders-summary', title: 'Riders Summary', url: '/reports/riders-summary', icon: TwoWheelerOutlinedIcon }, + { id: 'riders-logs', title: 'Riders Logs', url: '/reports/riders-logs', icon: MapOutlinedIcon } + ] + }, + { id: 'invoice', title: 'Invoice', url: '/invoice', icon: ReceiptLongOutlinedIcon }, + { id: 'requests', title: 'Requests', url: '/requests', icon: AssignmentOutlinedIcon } + ] + } +]; + +export default navItems; diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx new file mode 100644 index 0000000..838ea5b --- /dev/null +++ b/src/pages/Dashboard.jsx @@ -0,0 +1,124 @@ +import { Grid, Card, CardContent, Stack, Typography, Box, Button, Divider, Table, TableBody, TableCell, TableHead, TableRow, Avatar, MenuItem, TextField } from '@mui/material'; +import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; +import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined'; +import TwoWheelerOutlinedIcon from '@mui/icons-material/TwoWheelerOutlined'; +import CurrencyRupeeIcon from '@mui/icons-material/CurrencyRupee'; +import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined'; + +import PageHeader from '@/components/PageHeader'; +import StatCard from '@/components/StatCard'; +import MainCard from '@/components/MainCard'; +import StatusChip from '@/components/StatusChip'; +import AreaChart from '@/components/charts/AreaChart'; +import DonutChart from '@/components/charts/DonutChart'; +import UserAvatar from '@/components/UserAvatar'; +import { ordersTrend, statusBreakdown, orders, riders } from '@/data/mock'; +import { inr } from '@/utils/format'; + +export default function Dashboard() { + return ( + <> + + + All Locations + Bengaluru + Mumbai + + + + } + /> + + + + + + + + + } + > + d.m)} + series={[ + { name: 'Orders', color: '#C01227', data: ordersTrend.map((d) => d.orders) }, + { name: 'Delivered', color: '#00A854', data: ordersTrend.map((d) => d.delivered) } + ]} + /> + + + + + + + + + + + + + + + + Order ID + Customer + Route + Status + Amount + + + + {orders.slice(0, 6).map((o) => ( + + {o.id} + {o.customer} + + {o.pickup} → {o.drop} + + + {inr(o.charges)} + + ))} + +
+
+
+ + + } spacing={0}> + {riders.slice(0, 5).map((r, i) => ( + + {i + 1} + + + {r.name} + {r.vehicle} · ⭐ {r.rating} + + + {r.deliveries} + deliveries + + + ))} + + + +
+ + ); +} + +function Legend({ color, label }) { + return ( + + + {label} + + ); +} diff --git a/src/pages/Deliveries.jsx b/src/pages/Deliveries.jsx new file mode 100644 index 0000000..ae51448 --- /dev/null +++ b/src/pages/Deliveries.jsx @@ -0,0 +1,279 @@ +import { useState, useMemo, Fragment } from 'react'; +import { + Grid, Card, Stack, Button, TextField, MenuItem, InputAdornment, Box, Tabs, Tab, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton, + Tooltip, TablePagination, Typography, Collapse, Menu +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import CalendarTodayOutlinedIcon from '@mui/icons-material/CalendarTodayOutlined'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import NotificationsActiveOutlinedIcon from '@mui/icons-material/NotificationsActiveOutlined'; +import SwapHorizOutlinedIcon from '@mui/icons-material/SwapHorizOutlined'; +import EditLocationAltOutlinedIcon from '@mui/icons-material/EditLocationAltOutlined'; +import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined'; +import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; +import HourglassEmptyOutlinedIcon from '@mui/icons-material/HourglassEmptyOutlined'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; + +import PageHeader from '@/components/PageHeader'; +import StatCard from '@/components/StatCard'; +import EmptyState from '@/components/EmptyState'; +import UserAvatar from '@/components/UserAvatar'; +import TabLabelCount from '@/components/TabLabelCount'; +import { deliveries, locations, tenantsList, riders } from '@/data/mock'; +import { inr } from '@/utils/format'; + +const TABS = [ + { key: 'assigned', label: 'Assigned' }, + { key: 'accepted', label: 'Accepted' }, + { key: 'arrived', label: 'Arrived' }, + { key: 'picked', label: 'Picked' }, + { key: 'active', label: 'Active' }, + { key: 'skipped', label: 'Skipped' }, + { key: 'delivered', label: 'Delivered' }, + { key: 'cancelled', label: 'Cancelled' } +]; + +function ProductTable({ products = [] }) { + return ( + + + Products + + + + + S.No + Product Name + Description + Quantity + Cost + Price + Tax + Amount + + + + {products.map((p, i) => ( + + {i + 1} + {p.name} + + {p.description} + + {p.qty} + {inr(p.cost)} + {inr(p.price)} + {p.tax}% + {inr(p.amount)} + + ))} + +
+
+ ); +} + +function DeliveryRow({ row, index }) { + const [open, setOpen] = useState(false); + const [anchor, setAnchor] = useState(null); + + return ( + + *': { borderBottom: open ? 'unset' : undefined } }}> + + setOpen((o) => !o)}> + {open ? : } + + + {index + 1} + {row.tenant} + {row.location} + {row.pickup} + {row.drop} + + + + {row.rider} + + + + {row.notes || '—'} + + {row.qty} + {row.cod ? inr(row.cod) : '—'} + {row.kms} + {inr(row.amount)} + + + setAnchor(e.currentTarget)}> + + + + setAnchor(null)}> + setAnchor(null)}> + Notify Rider + + setAnchor(null)}> + Change Rider + + setAnchor(null)}> + Update Delivery Status + + setAnchor(null)} sx={{ color: 'error.main' }}> + Cancel Delivery + + + + + + + + + + + + + ); +} + +export default function Deliveries() { + const [tab, setTab] = useState(0); + const [search, setSearch] = useState(''); + const [tenant, setTenant] = useState('all'); + const [location, setLocation] = useState('all'); + const [rider, setRider] = useState('all'); + const [headerLocation, setHeaderLocation] = useState('all'); + const [page, setPage] = useState(0); + const [rpp, setRpp] = useState(5); + + const tabKey = TABS[tab].key; + + const counts = useMemo(() => { + const c = {}; + TABS.forEach((t) => { c[t.key] = deliveries.filter((d) => d.status === t.key).length; }); + return c; + }, []); + + const stats = useMemo(() => ({ + created: deliveries.length, + pending: deliveries.filter((d) => d.status === 'pending').length, + delivered: deliveries.filter((d) => d.status === 'delivered').length, + cancelled: deliveries.filter((d) => d.status === 'cancelled').length + }), []); + + const filtered = useMemo( + () => + deliveries.filter((d) => { + const matchTab = d.status === tabKey; + const matchTenant = tenant === 'all' || d.tenant === tenant; + const matchLocation = location === 'all' || d.location === location; + const matchRider = rider === 'all' || d.rider === rider; + const matchSearch = + !search || + [d.id, d.tenant, d.pickup, d.drop, d.rider, d.location].join(' ').toLowerCase().includes(search.toLowerCase()); + return matchTab && matchTenant && matchLocation && matchRider && matchSearch; + }), + [tabKey, tenant, location, rider, search] + ); + + const paged = filtered.slice(page * rpp, page * rpp + rpp); + + return ( + <> + setHeaderLocation(e.target.value)} + sx={{ minWidth: 180 }} label="Location" + > + All Locations + {locations.map((l) => {l})} + + } + /> + + + + + + + + + + + + { setTenant(e.target.value); setPage(0); }} sx={{ minWidth: 160 }} label="Tenant"> + All Tenants + {tenantsList.map((t) => {t})} + + { setLocation(e.target.value); setPage(0); }} sx={{ minWidth: 150 }} label="Location"> + All Locations + {locations.map((l) => {l})} + + { setRider(e.target.value); setPage(0); }} sx={{ minWidth: 150 }} label="Rider"> + All Riders + {riders.map((r) => {r.name})} + + + { setSearch(e.target.value); setPage(0); }} + sx={{ minWidth: 220 }} + InputProps={{ startAdornment: }} + /> + + + + { setTab(v); setPage(0); }} variant="scrollable" scrollButtons="auto"> + {TABS.map((t, i) => ( + } /> + ))} + + + + + + + + + S.No + Tenant + Order Location + Pickup + Drop + Rider + Notes + Qty + COD + Kms + Amount + Action + + + + {paged.length === 0 ? ( + + + + + + ) : ( + paged.map((row, i) => ) + )} + +
+
+ setPage(p)} + rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[5, 10, 25]} + /> +
+ + ); +} diff --git a/src/pages/Pricing.jsx b/src/pages/Pricing.jsx new file mode 100644 index 0000000..231bffe --- /dev/null +++ b/src/pages/Pricing.jsx @@ -0,0 +1,93 @@ +import { useState, useMemo } from 'react'; +import { + Autocomplete, TextField, Box, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Chip, Typography +} from '@mui/material'; + +import PageHeader from '@/components/PageHeader'; +import MainCard from '@/components/MainCard'; +import EmptyState from '@/components/EmptyState'; +import { pricing, locations } from '@/data/mock'; +import { inr } from '@/utils/format'; + +const SLAB_COLORS = { '0-5 km': 'info', '5-10 km': 'warning', '10+ km': 'success' }; +const NAME_COLORS = { Standard: 'primary', Express: 'warning', Bulk: 'success' }; + +export default function Pricing() { + const [location, setLocation] = useState(null); + + const filtered = useMemo( + () => (location ? pricing.filter((p) => p.location === location) : pricing), + [location] + ); + + return ( + <> + setLocation(v)} + sx={{ minWidth: 240 }} + renderInput={(params) => } + /> + } + /> + + + {filtered.length === 0 ? ( + + ) : ( + + + + + S.No + Location + Pricing Id + Name + Slab + Base Price + MinKm + Price/Km + MaxKm + Min Orders + + + + {filtered.map((p, i) => ( + + {i + 1} + + + + + {p.pricingId} + + + + + + + + {inr(p.basePrice)} + {p.minKm} + {inr(p.pricePerKm)} + {p.maxKm} + + + + + ))} + +
+
+ )} +
+ + ); +} diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx new file mode 100644 index 0000000..a49e2b0 --- /dev/null +++ b/src/pages/Profile.jsx @@ -0,0 +1,69 @@ +import { Grid, Card, CardContent, Stack, Typography, TextField } from '@mui/material'; + +import PageHeader from '@/components/PageHeader'; +import MainCard from '@/components/MainCard'; +import UserAvatar from '@/components/UserAvatar'; + +const PROFILE = { + userId: 'U1001', + userName: 'Aarav Menon', + appLocation: 'Bengaluru', + authName: 'admin@doormile.in', + contactNo: '+91 98450 11223', + email: 'aarav.menon@doormile.in', + address: 'No. 7, Brigade Road, MG Road Area', + location: 'Bengaluru', + city: 'Bengaluru', + state: 'Karnataka', + postcode: '560001', + role: 'Operations Administrator' +}; + +const ro = (value) => ({ + fullWidth: true, + variant: 'standard', + value, + InputProps: { readOnly: true } +}); + +export default function Profile() { + return ( + <> + + + + + + + + +
+ {PROFILE.userName} + {PROFILE.role} +
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
+ + ); +} diff --git a/src/pages/Requests.jsx b/src/pages/Requests.jsx new file mode 100644 index 0000000..fe1a5b5 --- /dev/null +++ b/src/pages/Requests.jsx @@ -0,0 +1,162 @@ +import { useState } from 'react'; +import { + Card, Stack, Button, Box, Collapse, Tabs, Tab, Typography, Grid, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton, + TablePagination, Dialog, DialogTitle, DialogContent, DialogActions, TextField +} from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; + +import PageHeader from '@/components/PageHeader'; +import { requests } from '@/data/mock'; +import { inr } from '@/utils/format'; + +function RequestRow({ row, index }) { + const [open, setOpen] = useState(false); + const [tab, setTab] = useState(0); + + return ( + <> + *': { borderBottom: open ? 'unset' : undefined } }}> + {index + 1} + {row.requestor} + {row.bank} + {row.ifsc} + {row.refNo} + {inr(row.amount)} + {row.reason} + + setOpen((o) => !o)}> + {open ? : } + + + + + + + + setTab(v)} sx={{ mb: 2 }}> + + + + + {tab === 0 && ( + + + Contact Name + {row.contact} + + + Address + {row.address} + + + City + {row.city} + + + Zip Code + {row.zip} + + + )} + + {tab === 1 && ( + + + + # + Category + Skill + Cost/Hr + + + + {row.pricing.map((p, i) => ( + + {i + 1} + {p.category} + {p.skill} + {inr(p.cost)} + + ))} + +
+ )} +
+
+
+
+ + ); +} + +export default function Requests() { + const [page, setPage] = useState(0); + const [rpp, setRpp] = useState(10); + const [open, setOpen] = useState(false); + + const paged = requests.slice(page * rpp, page * rpp + rpp); + + return ( + <> + } onClick={() => setOpen(true)}> + Create Request + + } + /> + + + + + + + # + Requestor + Bank + IFSC + Ref No + Amount + Reason + + + + + {paged.map((row, idx) => ( + + ))} + +
+
+ setPage(p)} + rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[5, 10, 25, 100]} + /> +
+ + setOpen(false)} maxWidth="sm" fullWidth> + Create Request + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/pages/Settings.jsx b/src/pages/Settings.jsx new file mode 100644 index 0000000..2bac162 --- /dev/null +++ b/src/pages/Settings.jsx @@ -0,0 +1,206 @@ +import { useState } from 'react'; +import { + Grid, + Tabs, + Tab, + Box, + Stack, + TextField, + MenuItem, + Switch, + FormControlLabel, + Button, + Typography, + Divider, + Snackbar, + Alert +} from '@mui/material'; +import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined'; +import TuneOutlinedIcon from '@mui/icons-material/TuneOutlined'; +import NotificationsNoneIcon from '@mui/icons-material/NotificationsNone'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; + +import PageHeader from '@/components/PageHeader'; +import MainCard from '@/components/MainCard'; + +const TIMEZONES = ['Asia/Kolkata (IST)', 'Asia/Dubai (GST)', 'UTC', 'America/New_York (EST)']; +const LANGUAGES = ['English', 'हिन्दी (Hindi)', 'العربية (Arabic)']; + +function TabPanel({ value, index, children }) { + if (value !== index) return null; + return {children}; +} + +export default function Settings() { + const [tab, setTab] = useState(0); + const [toast, setToast] = useState(false); + + // General + const [general, setGeneral] = useState({ + orgName: 'Doormile Logistics Pvt. Ltd.', + supportEmail: 'support@doormile.in', + contact: '+91 98450 11223', + timezone: TIMEZONES[0], + language: LANGUAGES[0] + }); + + // Notifications + const [notify, setNotify] = useState({ + newOrders: true, + riderStatus: true, + invoicePaid: true, + weeklyDigest: false, + emailAlerts: true, + smsAlerts: false + }); + + // Security + const [security, setSecurity] = useState({ + currentPassword: '', + newPassword: '', + confirmPassword: '', + twoFactor: false + }); + + const setG = (k) => (e) => setGeneral((p) => ({ ...p, [k]: e.target.value })); + const setN = (k) => (e) => setNotify((p) => ({ ...p, [k]: e.target.checked })); + const setS = (k) => (e) => setSecurity((p) => ({ ...p, [k]: e.target.value ?? e.target.checked })); + + const save = () => setToast(true); + + return ( + <> + } onClick={save}> + Save Changes + + } + /> + + + + + setTab(v)} + sx={{ + '& .MuiTab-root': { alignItems: 'flex-start', textTransform: 'none', minHeight: 52, fontWeight: 600 } + }} + > + } iconPosition="start" label="General" /> + } iconPosition="start" label="Notifications" /> + } iconPosition="start" label="Security" /> + + + + + + {/* General */} + + + + + + + + + + + + + + + {TIMEZONES.map((t) => ( + {t} + ))} + + + + + {LANGUAGES.map((l) => ( + {l} + ))} + + + + + + + {/* Notifications */} + + + } spacing={0}> + {[ + { k: 'newOrders', t: 'New orders', d: 'Notify when a new order is placed' }, + { k: 'riderStatus', t: 'Rider status', d: 'When a rider goes online or offline' }, + { k: 'invoicePaid', t: 'Invoice paid', d: 'When a client settles an invoice' }, + { k: 'weeklyDigest', t: 'Weekly digest', d: 'A summary of operations every Monday' } + ].map((row) => ( + + + {row.t} + {row.d} + + + + ))} + + + Channels + + } label="Email alerts" /> + } label="SMS alerts" /> + + + + + {/* Security */} + + + + + + + + + + + + + + + + + + + + Authenticator app + + Require a one-time code at sign-in for extra security. + + + setSecurity((p) => ({ ...p, twoFactor: e.target.checked }))} /> + + + + + + + + setToast(false)} + anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} + > + setToast(false)} sx={{ width: '100%' }}> + Settings saved successfully. + + + + ); +} diff --git a/src/pages/auth/Login.jsx b/src/pages/auth/Login.jsx new file mode 100644 index 0000000..6b98b22 --- /dev/null +++ b/src/pages/auth/Login.jsx @@ -0,0 +1,124 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Box, + Grid, + Card, + Stack, + Typography, + TextField, + InputAdornment, + IconButton, + Button, + Checkbox, + FormControlLabel, + Link +} from '@mui/material'; +import Visibility from '@mui/icons-material/Visibility'; +import VisibilityOff from '@mui/icons-material/VisibilityOff'; +import BoltIcon from '@mui/icons-material/Bolt'; +import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined'; +import VerifiedOutlinedIcon from '@mui/icons-material/VerifiedOutlined'; + +import Logo from '@/components/Logo'; + +export default function Login() { + const navigate = useNavigate(); + const [show, setShow] = useState(false); + const [auth, setAuth] = useState('admin@doormile.in'); + const [pwd, setPwd] = useState(''); + + return ( + + {/* Brand panel */} + + + + + + + Move every parcel, +
on time, every time. +
+ + The command center for your last-mile operation — orders, riders, pricing and settlements in one corporate console. + + + {[ + { icon: BoltIcon, t: 'AI-assisted route optimisation' }, + { icon: LocalShippingOutlinedIcon, t: 'Real-time rider & delivery tracking' }, + { icon: VerifiedOutlinedIcon, t: 'Automated client invoicing & payouts' } + ].map((f) => ( + + + + + {f.t} + + ))} + +
+ + © 2026 Doormile Logistics Pvt. Ltd. + +
+ + {/* Form panel */} + + + + Welcome back + + Sign in to your Doormile operations account. + + + + + Auth Name + setAuth(e.target.value)} /> + + + Password + setPwd(e.target.value)} + InputProps={{ + endAdornment: ( + + setShow((s) => !s)} edge="end" size="small"> + {show ? : } + + + ) + }} + /> + + + } label={Remember me} /> + Forgot password? + + + + + +
+ ); +} diff --git a/src/pages/customers/CreateCustomer.jsx b/src/pages/customers/CreateCustomer.jsx new file mode 100644 index 0000000..e5eb1cf --- /dev/null +++ b/src/pages/customers/CreateCustomer.jsx @@ -0,0 +1,95 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Grid, Stack, Button, TextField, MenuItem, InputAdornment } from '@mui/material'; + +import PageHeader from '@/components/PageHeader'; +import MainCard from '@/components/MainCard'; +import { locations, tenantsList } from '@/data/mock'; + +const COUNTRY_CODES = ['+91', '+1', '+44', '+61', '+971']; + +export default function CreateCustomer() { + const navigate = useNavigate(); + const [code, setCode] = useState('+91'); + const [topLocation, setTopLocation] = useState(''); + const [client, setClient] = useState(''); + const [form, setForm] = useState({ + name: '', phone: '', email: '', doorNo: '', address: '', + location: '', city: '', state: '', postcode: '', landmark: '' + }); + const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value })); + + return ( + <> + + + + setTopLocation(e.target.value)} sx={{ minWidth: 220 }}> + {locations.map((l) => {l})} + + setClient(e.target.value)} sx={{ minWidth: 220 }}> + {tenantsList.map((t) => {t})} + + + + + + + + + + + setCode(e.target.value)} + InputProps={{ disableUnderline: true }} sx={{ minWidth: 56 }} + > + {COUNTRY_CODES.map((c) => {c})} + + + ) + }} + /> + + + + + + + + + + + + + {locations.map((l) => {l})} + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/pages/customers/Customers.jsx b/src/pages/customers/Customers.jsx new file mode 100644 index 0000000..514a3be --- /dev/null +++ b/src/pages/customers/Customers.jsx @@ -0,0 +1,245 @@ +import { useState, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Grid, Stack, Button, TextField, MenuItem, InputAdornment, Box, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton, + Tooltip, TablePagination, Typography, + Dialog, DialogTitle, DialogContent, DialogActions, Divider +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import AddIcon from '@mui/icons-material/Add'; +import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined'; +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; + +import PageHeader from '@/components/PageHeader'; +import MainCard from '@/components/MainCard'; +import EmptyState from '@/components/EmptyState'; +import UserAvatar from '@/components/UserAvatar'; +import { customers, locations } from '@/data/mock'; + +const EMPTY_FORM = { + name: '', phone: '', address: '', location: '', city: '', state: '', + postcode: '', landmark: '', lat: '', lng: '' +}; + +export default function Customers() { + const navigate = useNavigate(); + const [search, setSearch] = useState(''); + const [location, setLocation] = useState('all'); + const [page, setPage] = useState(0); + const [rpp, setRpp] = useState(5); + const [editOpen, setEditOpen] = useState(false); + const [form, setForm] = useState(EMPTY_FORM); + const [viewOpen, setViewOpen] = useState(false); + const [viewer, setViewer] = useState(null); + + const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value })); + + const filtered = useMemo( + () => + customers.filter((c) => { + const matchLocation = location === 'all' || c.location === location; + const matchSearch = + !search || + [c.name, c.phone, c.email, c.address, c.location, c.city] + .join(' ') + .toLowerCase() + .includes(search.toLowerCase()); + return matchLocation && matchSearch; + }), + [search, location] + ); + + const paged = filtered.slice(page * rpp, page * rpp + rpp); + + const openEdit = (c) => { + setForm({ + name: c.name, phone: c.phone, address: c.address, location: c.location, + city: c.city, state: c.state, postcode: c.postcode, landmark: c.landmark || '', + lat: c.lat || '', lng: c.lng || '' + }); + setEditOpen(true); + }; + + const openView = (c) => { + setViewer(c); + setViewOpen(true); + }; + + return ( + <> + + { setLocation(e.target.value); setPage(0); }} + sx={{ minWidth: 160 }} label="Location" + > + All Locations + {locations.map((l) => {l})} + + { setSearch(e.target.value); setPage(0); }} sx={{ minWidth: 220 }} + InputProps={{ startAdornment: }} + /> + + + } + /> + + + {filtered.length === 0 ? ( + + ) : ( + <> + + + + + # + Name + Contact + Address + Location + Action + + + + {paged.map((c, i) => ( + + {page * rpp + i + 1} + + + + {c.name} + + + + {c.phone} + {c.email} + + + {c.address} + {c.city}, {c.state} {c.postcode} + + {c.location} + + openView(c)}> + openEdit(c)}> + + + + ))} + +
+
+ setPage(p)} + rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} + rowsPerPageOptions={[5, 10, 25]} + /> + + )} +
+ + setViewOpen(false)} maxWidth="sm" fullWidth> + Customer Details + + + {viewer && ( + <> + + + + {viewer.name} + {viewer.location} + + + + {[ + ['Phone', viewer.phone], + ['Email', viewer.email], + ['Address', viewer.address], + ['City', viewer.city], + ['State', viewer.state], + ['Postcode', viewer.postcode], + ['Landmark', viewer.landmark || '—'], + ['Coordinates', viewer.lat && viewer.lng ? `${viewer.lat}, ${viewer.lng}` : '—'] + ].map(([label, value]) => ( + + {label} + {value} + + ))} + + + )} + + + + + + + + setEditOpen(false)} maxWidth="md" fullWidth> + Edit Customer + + + + + + + + +91 }} + /> + + + + + + + {locations.map((l) => {l})} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/pages/invoice/InvoicePreview.jsx b/src/pages/invoice/InvoicePreview.jsx new file mode 100644 index 0000000..6f02edc --- /dev/null +++ b/src/pages/invoice/InvoicePreview.jsx @@ -0,0 +1,180 @@ +import { useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { + Box, Card, Stack, Button, Typography, Chip, Divider, IconButton, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Grid, + Dialog, DialogTitle, DialogContent, DialogActions, TextField +} from '@mui/material'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import PrintOutlinedIcon from '@mui/icons-material/PrintOutlined'; +import PaymentsOutlinedIcon from '@mui/icons-material/PaymentsOutlined'; + +import PageHeader from '@/components/PageHeader'; +import Logo from '@/components/Logo'; +import { invoices, invoiceLineItems, tenants } from '@/data/mock'; +import { inr } from '@/utils/format'; + +export default function InvoicePreview() { + const navigate = useNavigate(); + const { id } = useParams(); + const [payOpen, setPayOpen] = useState(false); + + const invoice = invoices.find((i) => String(i.id) === String(id)) || invoices[0]; + const tenant = tenants[0]; + + const subTotal = invoiceLineItems.reduce((a, l) => a + l.amount, 0); + const discount = Math.round(subTotal * 0.05); + const taxable = subTotal - discount; + const tax = Math.round(taxable * 0.18); + const grandTotal = taxable + tax; + + return ( + <> + + navigate('/invoice')} sx={{ border: 1, borderColor: 'grey.300' }}> + + + + + + + } + /> + + + + {/* Top band */} + + + + From + Doormile Logistics Pvt. Ltd. + No. 7, Brigade Road + Bengaluru, Karnataka 560001 + GSTIN: 29ABCDE1234F1Z5 + billing@doormile.in + + + INVOICE + To + {tenant.name} + {tenant.contact} + {tenant.address} + {tenant.city} {tenant.postcode} + {tenant.email} + + + + + + {/* Invoice meta */} + + + Invoice No + {invoice.invoiceId} + + + Date + {invoice.invoiceDate} + + + Due Date + {invoice.dueDate} + + + Period + {invoice.period} + + + + {/* Line items */} + + + + + S.No + Particulars + Unit + Quantity + Rate + Other Charges + Amount + + + + {invoiceLineItems.map((l, idx) => ( + + {idx + 1} + {l.particulars} + {l.unit} + {l.qty} + {inr(l.rate)} + {inr(l.other)} + {inr(l.amount)} + + ))} + +
+
+ + {/* Totals */} + + + + Sub Total + {inr(subTotal)} + + + Discount (5%) + - {inr(discount)} + + + Tax (18% GST) + {inr(tax)} + + + + Grand Total + {inr(grandTotal)} + + + + + {/* Notes + accent */} + + Notes & Terms + + Payment is due within 15 days of the invoice date. Please make payments via NEFT/RTGS to the + registered account. A 1.5% monthly interest applies to overdue balances. This is a + computer-generated invoice and does not require a signature. + + + +
+
+ + {/* Update Payment Dialog */} + setPayOpen(false)} maxWidth="xs" fullWidth> + Update Payment + + + + + + + + + + + + + ); +} diff --git a/src/pages/invoice/Invoices.jsx b/src/pages/invoice/Invoices.jsx new file mode 100644 index 0000000..30df382 --- /dev/null +++ b/src/pages/invoice/Invoices.jsx @@ -0,0 +1,154 @@ +import { useState, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Grid, Card, Stack, Button, Box, Tabs, Tab, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton, + Tooltip, TablePagination, Dialog, DialogTitle, DialogContent, DialogActions, Typography +} from '@mui/material'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import dayjs from 'dayjs'; +import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined'; +import CalendarTodayOutlinedIcon from '@mui/icons-material/CalendarTodayOutlined'; +import ReceiptLongOutlinedIcon from '@mui/icons-material/ReceiptLongOutlined'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import HourglassEmptyOutlinedIcon from '@mui/icons-material/HourglassEmptyOutlined'; +import ErrorOutlineOutlinedIcon from '@mui/icons-material/ErrorOutlineOutlined'; + +import PageHeader from '@/components/PageHeader'; +import StatCard from '@/components/StatCard'; +import StatusChip from '@/components/StatusChip'; +import TabLabelCount from '@/components/TabLabelCount'; +import { invoices } from '@/data/mock'; +import { inr } from '@/utils/format'; + +const TABS = [ + { key: 'all', label: 'All' }, + { key: 'open', label: 'Open' }, + { key: 'overdue', label: 'Overdue' }, + { key: 'paid', label: 'Paid' } +]; + +export default function Invoices() { + const navigate = useNavigate(); + const [tab, setTab] = useState(0); + const [page, setPage] = useState(0); + const [rpp, setRpp] = useState(10); + const [dateOpen, setDateOpen] = useState(false); + const [from, setFrom] = useState(dayjs().startOf('month')); + const [to, setTo] = useState(dayjs()); + + const totals = useMemo(() => { + const sum = (s) => invoices.filter((i) => i.status === s).reduce((a, i) => a + i.amount, 0); + return { + billed: invoices.reduce((a, i) => a + i.amount, 0), + paid: sum('paid'), + open: sum('open'), + overdue: sum('overdue') + }; + }, []); + + const counts = { + all: invoices.length, + open: invoices.filter((i) => i.status === 'open').length, + overdue: invoices.filter((i) => i.status === 'overdue').length, + paid: invoices.filter((i) => i.status === 'paid').length + }; + + const tabKey = TABS[tab].key; + const filtered = useMemo( + () => invoices.filter((i) => (tabKey === 'all' ? true : i.status === tabKey)), + [tabKey] + ); + const paged = filtered.slice(page * rpp, page * rpp + rpp); + + return ( + <> + } onClick={() => setDateOpen(true)} sx={{ color: 'text.secondary', borderColor: 'grey.300' }}> + {from.format('MMM DD')} – {to.format('MMM DD')} + + } + /> + + + + + + + + + + + { setTab(v); setPage(0); }}> + {TABS.map((t, i) => ( + } /> + ))} + + + + + + + + S.No + Client + Invoice Id + Invoice Date + Due Date + Count + Amount + Status + Action + + + + {paged.map((row, idx) => ( + + {page * rpp + idx + 1} + {row.client} + navigate(`/invoice/${row.id}`)}>{row.invoiceId} + {row.invoiceDate} + {row.dueDate} + {row.count} + {inr(row.amount)} + + + + navigate(`/invoice/${row.id}`)}> + + + + + + ))} + +
+
+ setPage(p)} + rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[5, 10, 25, 100]} + /> +
+ + setDateOpen(false)} maxWidth="xs" fullWidth> + Filter by Date + + + Select a date range to filter invoices. + + + v && setFrom(v)} slotProps={{ textField: { fullWidth: true } }} /> + v && setTo(v)} slotProps={{ textField: { fullWidth: true } }} /> + + + + + + + + + ); +} diff --git a/src/pages/maintenance/ComingSoon.jsx b/src/pages/maintenance/ComingSoon.jsx new file mode 100644 index 0000000..de12bf3 --- /dev/null +++ b/src/pages/maintenance/ComingSoon.jsx @@ -0,0 +1,80 @@ +import { useState } from 'react'; +import { Box, Stack, Typography, TextField, Button } from '@mui/material'; + +import Logo from '@/components/Logo'; + +const COUNTDOWN = [ + { label: 'Days', value: 12 }, + { label: 'Hours', value: 8 }, + { label: 'Minutes', value: 45 }, + { label: 'Seconds', value: 30 } +]; + +export default function ComingSoon() { + const [email, setEmail] = useState(''); + + return ( + + + + Coming Soon + Something new is on its way + + {/* Countdown */} + + {COUNTDOWN.map((c, i) => ( + + + + {String(c.value).padStart(2, '0')} + + + {c.label} + + + {i < COUNTDOWN.length - 1 && ( + : + )} + + ))} + + + {/* Subscribe */} + + + Be the first to be notified when Doormile launches. + + + setEmail(e.target.value)} + /> + + + + + + ); +} diff --git a/src/pages/maintenance/Error404.jsx b/src/pages/maintenance/Error404.jsx new file mode 100644 index 0000000..afd0b4b --- /dev/null +++ b/src/pages/maintenance/Error404.jsx @@ -0,0 +1,34 @@ +import { useNavigate } from 'react-router-dom'; +import { Box, Stack, Typography, Button } from '@mui/material'; +import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'; + +export default function Error404() { + const navigate = useNavigate(); + + return ( + + + + 404 + + + Page Not Found + + The page you are looking for was moved, removed, renamed, or might never exist! + + + + + ); +} diff --git a/src/pages/maintenance/Error500.jsx b/src/pages/maintenance/Error500.jsx new file mode 100644 index 0000000..62998d9 --- /dev/null +++ b/src/pages/maintenance/Error500.jsx @@ -0,0 +1,34 @@ +import { useNavigate } from 'react-router-dom'; +import { Box, Stack, Typography, Button } from '@mui/material'; +import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'; + +export default function Error500() { + const navigate = useNavigate(); + + return ( + + + + 500 + + + Internal Server Error + + Server error 500. We are fixing the problem. Please try again at a later stage. + + + + + ); +} diff --git a/src/pages/maintenance/UnderConstruction.jsx b/src/pages/maintenance/UnderConstruction.jsx new file mode 100644 index 0000000..be57252 --- /dev/null +++ b/src/pages/maintenance/UnderConstruction.jsx @@ -0,0 +1,45 @@ +import { useNavigate } from 'react-router-dom'; +import { Box, Stack, Typography, Button } from '@mui/material'; +import ConstructionOutlinedIcon from '@mui/icons-material/ConstructionOutlined'; +import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'; + +export default function UnderConstruction() { + const navigate = useNavigate(); + + return ( + + + + + + Under Construction + + Hey! Please check out this site later. We are doing some maintenance on it right now. + + + + + ); +} diff --git a/src/pages/orders/AssignOrders.jsx b/src/pages/orders/AssignOrders.jsx new file mode 100644 index 0000000..d01b7dd --- /dev/null +++ b/src/pages/orders/AssignOrders.jsx @@ -0,0 +1,135 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Grid, Stack, Button, IconButton, Typography, Box, TextField, MenuItem, Chip, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow +} from '@mui/material'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import AutorenewIcon from '@mui/icons-material/Autorenew'; +import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; +import TwoWheelerOutlinedIcon from '@mui/icons-material/TwoWheelerOutlined'; +import MapOutlinedIcon from '@mui/icons-material/MapOutlined'; +import RouteOutlinedIcon from '@mui/icons-material/RouteOutlined'; +import PersonAddAltOutlinedIcon from '@mui/icons-material/PersonAddAltOutlined'; + +import PageHeader from '@/components/PageHeader'; +import StatCard from '@/components/StatCard'; +import MainCard from '@/components/MainCard'; +import StatusChip from '@/components/StatusChip'; +import UserAvatar from '@/components/UserAvatar'; +import { orders, riders } from '@/data/mock'; +import { inr } from '@/utils/format'; + +const ZONES = ['Zone A', 'Zone B', 'Zone C', 'Zone D']; +const PROFIT = [42, 58, 36, 50, 28, 64]; + +// derive assignment rows from orders + riders mock +const ROWS = orders.slice(0, 6).map((o, i) => ({ + ...o, + zone: ZONES[i % ZONES.length], + rider: riders[i % riders.length].name, + profit: PROFIT[i % PROFIT.length] +})); + +export default function AssignOrders() { + const navigate = useNavigate(); + const [payment, setPayment] = useState('all'); + const [rider, setRider] = useState('auto'); + + return ( + <> + + navigate('/orders')} size="small"> + Assign Orders + + } + breadcrumbs={[{ label: 'Orders', to: '/orders' }, { label: 'Assign Orders' }]} + action={ + + } + /> + + + + + + + + + + + + + + # + Zone + Tenant + Order Location + Pickup + Delivery + Notes + Rider + Type + Profit + Charges + KMS + + + + {ROWS.map((r) => ( + + {r.id} + + + + {r.tenant} + {r.location} + {r.pickup} + {r.drop} + {r.notes || '—'} + + + + {r.rider} + + + + {inr(r.profit)} + {inr(r.charges)} + {r.kms} + + ))} + +
+
+
+ + + + + setPayment(e.target.value)}> + All Payments + Prepaid + COD + + + + setRider(e.target.value)}> + Auto Assign + {riders.map((rd) => {rd.name})} + + + + + + + + + + + ); +} diff --git a/src/pages/orders/CreateMultipleOrders.jsx b/src/pages/orders/CreateMultipleOrders.jsx new file mode 100644 index 0000000..b5d948d --- /dev/null +++ b/src/pages/orders/CreateMultipleOrders.jsx @@ -0,0 +1,254 @@ +import { useState, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Grid, Card, CardContent, Stack, Button, TextField, MenuItem, Typography, Divider, Box, + RadioGroup, Radio, FormControlLabel, FormControl, FormLabel, Chip, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton, Tooltip, + Dialog, DialogTitle, DialogContent, DialogActions, List, ListItem, ListItemButton, + ListItemIcon, ListItemText, Checkbox, InputAdornment +} from '@mui/material'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import dayjs from 'dayjs'; +import UploadFileOutlinedIcon from '@mui/icons-material/UploadFileOutlined'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import SearchIcon from '@mui/icons-material/Search'; +import AddIcon from '@mui/icons-material/Add'; +import PersonAddAltOutlinedIcon from '@mui/icons-material/PersonAddAltOutlined'; + +import PageHeader from '@/components/PageHeader'; +import MainCard from '@/components/MainCard'; +import UserAvatar from '@/components/UserAvatar'; +import { locations, tenantsList, tenants, customers } from '@/data/mock'; +import { inr } from '@/utils/format'; + +const TIME_SLOTS = ['09:00 - 11:00', '11:00 - 13:00', '13:00 - 15:00', '15:00 - 17:00', '17:00 - 19:00']; + +// derive table rows from the customers mock +const buildRows = () => + customers.slice(0, 4).map((c, i) => ({ + id: c.id, + customer: c.name, + address: c.address, + qty: [2, 1, 3, 1][i], + cash: [0, 1299, 0, 450][i], + kms: [4.2, 12.8, 6.5, 9.1][i], + charge: [86, 142, 98, 110][i] + })); + +export default function CreateMultipleOrders() { + const navigate = useNavigate(); + const [date, setDate] = useState(dayjs()); + const [slot, setSlot] = useState(''); + const [dropMode, setDropMode] = useState('csv'); + const [rows, setRows] = useState(buildRows); + const [dialogOpen, setDialogOpen] = useState(false); + const [search, setSearch] = useState(''); + const [picked, setPicked] = useState([]); + + const totals = useMemo( + () => + rows.reduce( + (acc, r) => ({ + qty: acc.qty + r.qty, + cash: acc.cash + r.cash, + kms: +(acc.kms + r.kms).toFixed(1), + charge: acc.charge + r.charge + }), + { qty: 0, cash: 0, kms: 0, charge: 0 } + ), + [rows] + ); + + const removeRow = (id) => setRows((p) => p.filter((r) => r.id !== id)); + + const filteredCustomers = customers.filter( + (c) => !search || `${c.name} ${c.location}`.toLowerCase().includes(search.toLowerCase()) + ); + const toggle = (id) => setPicked((p) => (p.includes(id) ? p.filter((x) => x !== id) : [...p, id])); + + const addSelected = () => { + const existing = new Set(rows.map((r) => r.id)); + const additions = customers + .filter((c) => picked.includes(c.id) && !existing.has(c.id)) + .map((c) => ({ id: c.id, customer: c.name, address: c.address, qty: 1, cash: 0, kms: 5, charge: 90 })); + setRows((p) => [...p, ...additions]); + setPicked([]); + setDialogOpen(false); + }; + + return ( + <> + + + + + + {/* top selects */} + + + + {locations.map((l) => {l})} + + + + + {tenantsList.map((t) => {t})} + + + + + {tenants.map((t) => {t.address}, {t.city})} + + + + + + + + + + + setSlot(e.target.value)}> + {TIME_SLOTS.map((s) => {s})} + + + + + + Drop + + + Add drop points using + setDropMode(e.target.value)}> + } label="Excel / CSV" /> + } label="Selection" /> + + + + + {dropMode === 'csv' ? ( + + + {}} + sx={{ bgcolor: 'primary.lighter', color: 'primary.dark', fontWeight: 600 }} + /> + + ) : ( + + )} + + + + + + + + + + + S.No + Customer + Address + Quantity + Cash Collect + Kms + Charge + Action + + + + {rows.map((r, i) => ( + + {i + 1} + + + + {r.customer} + + + {r.address} + {r.qty} + {r.cash ? inr(r.cash) : '—'} + {r.kms} + {inr(r.charge)} + + + removeRow(r.id)}> + + + + + + ))} + + Total + {totals.qty} + {inr(totals.cash)} + {totals.kms} + {inr(totals.charge)} + + + +
+
+
+ + + + + + + + + +
+ + {/* Customer selection dialog */} + setDialogOpen(false)} fullWidth maxWidth="sm"> + Select Customers + + setSearch(e.target.value)} + sx={{ mb: 1.5 }} + InputProps={{ startAdornment: }} + /> + + {filteredCustomers.map((c) => ( + + toggle(c.id)}> + + + + + + + + ))} + + + + + + + + + ); +} diff --git a/src/pages/orders/CreateOrder.jsx b/src/pages/orders/CreateOrder.jsx new file mode 100644 index 0000000..5613814 --- /dev/null +++ b/src/pages/orders/CreateOrder.jsx @@ -0,0 +1,142 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Grid, Card, CardContent, Stack, Button, TextField, MenuItem, Typography, Divider, + Autocomplete, FormControlLabel, Checkbox, Box +} from '@mui/material'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import dayjs from 'dayjs'; +import AddIcon from '@mui/icons-material/Add'; + +import PageHeader from '@/components/PageHeader'; +import { locations, tenantsList, tenants } from '@/data/mock'; + +const TIME_SLOTS = ['09:00 - 11:00', '11:00 - 13:00', '13:00 - 15:00', '15:00 - 17:00', '17:00 - 19:00']; + +const SectionTitle = ({ children }) => ( + <> + {children} + + +); + +function AddressFields({ saveForLater, onSaveForLater }) { + return ( + + + + + + + + + + + + + + + + {locations.map((l) => {l})} + + + + + + + + + + onSaveForLater(e.target.checked)} />} + label="Save for later" + /> + + + ); +} + +export default function CreateOrder() { + const navigate = useNavigate(); + const [deliveryDate, setDeliveryDate] = useState(dayjs()); + const [slot, setSlot] = useState(''); + const [savePickup, setSavePickup] = useState(false); + const [saveDrop, setSaveDrop] = useState(false); + + return ( + <> + + + + + {/* Top row */} + + + } + /> + + + } + /> + + + `${t.name} — ${t.address}`)} + renderInput={(params) => } + /> + + + + + Pickup Details + + + + + Drop Details + + + + + Schedule + + + + + + setSlot(e.target.value)}> + {TIME_SLOTS.map((s) => {s})} + + + + + + + + + + + + + + + + + ); +} diff --git a/src/pages/orders/OrderDetails.jsx b/src/pages/orders/OrderDetails.jsx new file mode 100644 index 0000000..cab61d6 --- /dev/null +++ b/src/pages/orders/OrderDetails.jsx @@ -0,0 +1,170 @@ +import { useParams, useNavigate } from 'react-router-dom'; +import { + Grid, Card, CardContent, Stack, Typography, Box, Button, Divider, IconButton, Avatar, + Step, Stepper, StepLabel, StepConnector, stepConnectorClasses, styled, Chip +} from '@mui/material'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +import CallOutlinedIcon from '@mui/icons-material/CallOutlined'; +import PhoneOutlinedIcon from '@mui/icons-material/PhoneOutlined'; +import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; +import PersonOutlineIcon from '@mui/icons-material/PersonOutline'; +import CheckIcon from '@mui/icons-material/Check'; + +import PageHeader from '@/components/PageHeader'; +import MainCard from '@/components/MainCard'; +import StatusChip from '@/components/StatusChip'; +import MapPlaceholder from '@/components/MapPlaceholder'; +import UserAvatar from '@/components/UserAvatar'; +import { orders, orderTimeline, deliveries } from '@/data/mock'; +import { inr } from '@/utils/format'; + +const RedConnector = styled(StepConnector)(({ theme }) => ({ + [`& .${stepConnectorClasses.line}`]: { borderColor: theme.palette.grey[300], borderLeftWidth: 2, minHeight: 28 }, + [`&.${stepConnectorClasses.active} .${stepConnectorClasses.line}, &.${stepConnectorClasses.completed} .${stepConnectorClasses.line}`]: + { borderColor: theme.palette.primary.main } +})); + +function Dot({ active }) { + return ( + + {active ? : } + + ); +} + +export default function OrderDetails() { + const { id } = useParams(); + const navigate = useNavigate(); + const order = orders.find((o) => o.id === id) || orders[1]; + const delivery = deliveries.find((d) => d.id === order.id) || deliveries[1]; + + return ( + <> + + navigate('/orders')} size="small"> + Order Details + + } + breadcrumbs={[{ label: 'Orders', to: '/orders' }, { label: order.id }]} + action={ + + + + + } + /> + + + {/* Left column */} + + + + + + + Order ID + {order.id} + + + + + + + + } /> + + {inr(order.charges)}} /> + + + + + + + + + {order.customer} + Recipient + + + + + + + + + + } sx={{ ml: 0.5 }}> + {orderTimeline.map((s) => ( + + }> + + {s.label} + {s.time} + + + + ))} + + + + + + {/* Right column */} + + + + + + + + + + + + + + + + + + {delivery.rider} + Rider · +91 98450 11223 + + + + + + + + + + + ); +} + +const Row = ({ label, value }) => ( + + {label} + {typeof value === 'string' ? {value} : value} + +); + +const IconRow = ({ icon: Icon, text }) => ( + + + {text} + +); + +const RouteEnd = ({ color, title, text }) => ( + + + + {title} + {text} + + +); diff --git a/src/pages/orders/OrdersList.jsx b/src/pages/orders/OrdersList.jsx new file mode 100644 index 0000000..62a42bf --- /dev/null +++ b/src/pages/orders/OrdersList.jsx @@ -0,0 +1,195 @@ +import { useState, useMemo } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { + Grid, Card, Stack, Button, TextField, MenuItem, InputAdornment, Box, Tabs, Tab, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Checkbox, IconButton, + Tooltip, TablePagination, Typography, SpeedDial, SpeedDialAction, Chip +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import AddIcon from '@mui/icons-material/Add'; +import PostAddOutlinedIcon from '@mui/icons-material/PostAddOutlined'; +import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined'; +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import CalendarTodayOutlinedIcon from '@mui/icons-material/CalendarTodayOutlined'; +import AutoAwesomeOutlinedIcon from '@mui/icons-material/AutoAwesomeOutlined'; +import PersonAddAltOutlinedIcon from '@mui/icons-material/PersonAddAltOutlined'; +import TuneIcon from '@mui/icons-material/Tune'; + +import PageHeader from '@/components/PageHeader'; +import StatCard from '@/components/StatCard'; +import StatusChip from '@/components/StatusChip'; +import TabLabelCount from '@/components/TabLabelCount'; +import { orders } from '@/data/mock'; +import { inr } from '@/utils/format'; + +import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; +import HourglassEmptyOutlinedIcon from '@mui/icons-material/HourglassEmptyOutlined'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined'; + +const TABS = [ + { key: 'created', label: 'Created' }, + { key: 'pending', label: 'Pending' }, + { key: 'delivered', label: 'Delivered' }, + { key: 'cancelled', label: 'Cancelled' } +]; + +export default function OrdersList() { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const [tab, setTab] = useState(0); + const [search, setSearch] = useState(searchParams.get('q') || ''); + const [tenant, setTenant] = useState('all'); + const [page, setPage] = useState(0); + const [rpp, setRpp] = useState(5); + const [selected, setSelected] = useState([]); + + const tabKey = TABS[tab].key; + const filtered = useMemo( + () => + orders.filter((o) => { + const matchTab = tabKey === 'created' ? true : o.status === tabKey; + const matchTenant = tenant === 'all' || o.tenant === tenant; + const matchSearch = + !search || + [o.id, o.customer, o.pickup, o.drop, o.tenant].join(' ').toLowerCase().includes(search.toLowerCase()); + return matchTab && matchTenant && matchSearch; + }), + [tabKey, tenant, search] + ); + + const paged = filtered.slice(page * rpp, page * rpp + rpp); + const toggle = (id) => setSelected((p) => (p.includes(id) ? p.filter((x) => x !== id) : [...p, id])); + const counts = { + created: orders.length, + pending: orders.filter((o) => o.status === 'pending').length, + delivered: orders.filter((o) => o.status === 'delivered').length, + cancelled: orders.filter((o) => o.status === 'cancelled').length + }; + + return ( + <> + + + + + } + /> + + + + + + + + + + {/* filter toolbar */} + + setSearch(e.target.value)} + sx={{ minWidth: 240 }} + InputProps={{ startAdornment: }} + /> + + + setTenant(e.target.value)} sx={{ minWidth: 170 }} label="Tenant"> + All Tenants + {[...new Set(orders.map((o) => o.tenant))].map((t) => {t})} + + + All Locations + {[...new Set(orders.map((o) => o.location))].map((l) => {l})} + + + + + { setTab(v); setPage(0); }}> + {TABS.map((t, i) => ( + } /> + ))} + + + + {selected.length > 0 && ( + + {selected.length} selected + + + )} + + + + + + + 0 && selected.length < paged.length} + checked={paged.length > 0 && selected.length === paged.length} + onChange={(e) => setSelected(e.target.checked ? paged.map((o) => o.id) : [])} + /> + + # + Tenant + Location + Pickup + Drop + QTY + COD + KMS + Charges + Notes + Status + Actions + + + + {paged.map((o) => ( + + toggle(o.id)} /> + navigate(`/orders/${o.id}`)}>{o.id} + {o.tenant} + {o.location} + {o.pickup} + {o.drop} + {o.qty} + {o.cod ? inr(o.cod) : '—'} + {o.kms} + {inr(o.charges)} + {o.notes || '—'} + + + navigate(`/orders/${o.id}`)}> + + + + + ))} + +
+
+ setPage(p)} + rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[5, 10, 25]} + /> +
+ + } sx={{ position: 'fixed', bottom: 28, right: 28 }} FabProps={{ color: 'primary' }}> + } tooltipTitle="AI Optimisation" onClick={() => navigate('/orders/assign')} /> + } tooltipTitle="Manual Assign" onClick={() => navigate('/orders/assign')} /> + } tooltipTitle="Delete" /> + + + ); +} diff --git a/src/pages/reports/OrdersDetails.jsx b/src/pages/reports/OrdersDetails.jsx new file mode 100644 index 0000000..e052756 --- /dev/null +++ b/src/pages/reports/OrdersDetails.jsx @@ -0,0 +1,190 @@ +import { useState, useMemo } from 'react'; +import { + Grid, Card, Stack, Button, TextField, MenuItem, InputAdornment, Box, IconButton, Tooltip, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination, Typography, + Dialog, DialogTitle, DialogContent, DialogActions +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import CalendarTodayOutlinedIcon from '@mui/icons-material/CalendarTodayOutlined'; +import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined'; +import MapOutlinedIcon from '@mui/icons-material/MapOutlined'; +import CloseIcon from '@mui/icons-material/Close'; + +import PageHeader from '@/components/PageHeader'; +import StatusChip from '@/components/StatusChip'; +import MapPlaceholder from '@/components/MapPlaceholder'; +import { ordersDetailReport, locations, tenantsList } from '@/data/mock'; +import { inr } from '@/utils/format'; + +const STATUSES = ['all', 'created', 'pending', 'picked', 'active', 'delivered', 'cancelled']; + +export default function OrdersDetails() { + const [location, setLocation] = useState('all'); + const [tenant, setTenant] = useState('all'); + const [loc2, setLoc2] = useState('all'); + const [status, setStatus] = useState('all'); + const [search, setSearch] = useState(''); + const [page, setPage] = useState(0); + const [rpp, setRpp] = useState(10); + const [mapRow, setMapRow] = useState(null); + const [exportOpen, setExportOpen] = useState(false); + + const filtered = useMemo( + () => + ordersDetailReport.filter((o) => { + const matchStatus = status === 'all' || o.status === status; + const matchTenant = tenant === 'all' || o.client === tenant; + const matchSearch = + !search || + [o.id, o.client, o.pickup, o.drop].join(' ').toLowerCase().includes(search.toLowerCase()); + return matchStatus && matchTenant && matchSearch; + }), + [status, tenant, search] + ); + + const paged = filtered.slice(page * rpp, page * rpp + rpp); + + return ( + <> + setLocation(e.target.value)} sx={{ minWidth: 160 }} label="Location"> + All Locations + {locations.map((l) => {l})} +
+ } + /> + + + + setTenant(e.target.value)} sx={{ minWidth: 170 }} label="Tenant"> + All Tenants + {tenantsList.map((t) => {t})} + + setLoc2(e.target.value)} sx={{ minWidth: 160 }} label="Location"> + All Locations + {locations.map((l) => {l})} + + + { setStatus(e.target.value); setPage(0); }} sx={{ minWidth: 150 }} label="Status"> + {STATUSES.map((s) => {s === 'all' ? 'All Status' : s[0].toUpperCase() + s.slice(1)})} + + { setSearch(e.target.value); setPage(0); }} sx={{ minWidth: 220 }} + InputProps={{ startAdornment: }} + /> + + + + + + + + + # + + Client + Pickup + Drop + Status + Assigned + Accepted + Arrived + Picked + Active + Delivered + Cancelled + Notes + KMS + Charges + + + + {paged.map((o, i) => ( + + {page * rpp + i + 1} + + + setMapRow(o)}> + + + {o.client} + {o.pickup} + {o.drop} + + {o.assigned} + {o.accepted} + {o.arrived} + {o.picked} + {o.active} + {o.delivered} + {o.cancelled} + {o.notes || '—'} + {o.kms} + {inr(o.charges)} + + ))} + +
+
+ setPage(p)} + rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[10, 25, 50]} + /> +
+ + {/* Map dialog */} + setMapRow(null)} fullScreen> + + + Route — {mapRow?.id} + + {mapRow?.pickup} → {mapRow?.drop} + + + setMapRow(null)}> + + + + + + + {/* Export dialog */} + setExportOpen(false)} maxWidth="xs" fullWidth> + Export Report + + + The export will include {filtered.length} record(s) matching the current filters: + + + + + + + + + + + + + + + + + ); +} + +function Filter({ label, value }) { + return ( + + {label} + {value} + + ); +} diff --git a/src/pages/reports/OrdersSummary.jsx b/src/pages/reports/OrdersSummary.jsx new file mode 100644 index 0000000..f97320a --- /dev/null +++ b/src/pages/reports/OrdersSummary.jsx @@ -0,0 +1,195 @@ +import { useState, Fragment } from 'react'; +import { + Grid, Card, Stack, Button, TextField, MenuItem, Box, Collapse, IconButton, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography +} from '@mui/material'; +import CalendarTodayOutlinedIcon from '@mui/icons-material/CalendarTodayOutlined'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; +import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined'; + +import PageHeader from '@/components/PageHeader'; +import { ordersSummary, locations, tenantsList } from '@/data/mock'; +import { inr } from '@/utils/format'; + +// Show 0 in red, anything else normally. +function NumCell({ value, align = 'center', bold = false }) { + const zero = Number(value) === 0; + return ( + + {value} + + ); +} + +export default function OrdersSummary() { + const [open, setOpen] = useState({}); + const [location, setLocation] = useState('all'); + const [tenant, setTenant] = useState('all'); + const [loc2, setLoc2] = useState('all'); + + const toggle = (id) => setOpen((p) => ({ ...p, [id]: !p[id] })); + + const totals = ordersSummary.reduce( + (a, r) => ({ + oPending: a.oPending + r.orders.pending, + oCancelled: a.oCancelled + r.orders.cancelled, + oCompleted: a.oCompleted + r.orders.completed, + dPending: a.dPending + r.deliveries.pending, + dCancelled: a.dCancelled + r.deliveries.cancelled, + dCompleted: a.dCompleted + r.deliveries.completed, + collection: a.collection + r.collection, + kms: a.kms + r.kms, + actualKms: a.actualKms + r.actualKms, + amount: a.amount + r.amount + }), + { oPending: 0, oCancelled: 0, oCompleted: 0, dPending: 0, dCancelled: 0, dCompleted: 0, collection: 0, kms: 0, actualKms: 0, amount: 0 } + ); + + const headSx = { bgcolor: 'primary.lighter', fontWeight: 700, color: 'primary.dark', whiteSpace: 'nowrap' }; + + return ( + <> + setLocation(e.target.value)} sx={{ minWidth: 160 }} label="Location"> + All Locations + {locations.map((l) => {l})} +
+ } + /> + + + + + + setTenant(e.target.value)} sx={{ minWidth: 170 }} label="Tenant"> + All Tenants + {tenantsList.map((t) => {t})} + + setLoc2(e.target.value)} sx={{ minWidth: 160 }} label="Location"> + All Locations + {locations.map((l) => {l})} + + + + + + + + + # + Tenant / Location + Orders + Deliveries + Collection Amt + Kms / Actual + Amount + Action + + + Pending + Cancelled + Completed + Pending + Cancelled + Completed + + + + {ordersSummary.map((r, idx) => ( + + + + toggle(r.id)}> + {open[r.id] ? : } + + + {idx + 1} + + {r.tenant} + {r.location} + + + + + + + + {inr(r.collection)} + {r.kms} / {r.actualKms} + {inr(r.amount)} + + toggle(r.id)}> + + + + + + + Riders +
+ + + # + Rider + Orders + Deliveries + Pending + Cancelled + Completed + Collection Amt + Kms / Actual + Charges + + + + {r.riders.map((rd, i) => ( + + {i + 1} + {rd.rider} + + + + + + {inr(rd.collection)} + {rd.kms} / {rd.actualKms} + {inr(rd.charges)} + + ))} + +
+ + + + + + ))} + + {/* totals */} + + + Totals + + + + + + + {inr(totals.collection)} + {totals.kms} / {totals.actualKms} + {inr(totals.amount)} + + + + +
+
+ + ); +} diff --git a/src/pages/reports/RidersLogs.jsx b/src/pages/reports/RidersLogs.jsx new file mode 100644 index 0000000..c6ee68b --- /dev/null +++ b/src/pages/reports/RidersLogs.jsx @@ -0,0 +1,105 @@ +import { useState, useMemo } from 'react'; +import { + Box, Paper, Stack, Button, TextField, InputAdornment, IconButton, Checkbox, Typography, Divider, Tooltip +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import MenuIcon from '@mui/icons-material/Menu'; +import RefreshIcon from '@mui/icons-material/Refresh'; + +import PageHeader from '@/components/PageHeader'; +import MapPlaceholder from '@/components/MapPlaceholder'; +import { ridersLive } from '@/data/mock'; + +// spread active riders across the map at distinct positions +const POSITIONS = [ + { x: '24%', y: '32%' }, + { x: '58%', y: '28%' }, + { x: '40%', y: '60%' }, + { x: '72%', y: '55%' }, + { x: '30%', y: '72%' }, + { x: '64%', y: '78%' } +]; + +export default function RidersLogs() { + const [search, setSearch] = useState(''); + const [selected, setSelected] = useState(ridersLive.filter((r) => r.active).map((r) => r.userid)); + + const filtered = useMemo( + () => + ridersLive.filter((r) => + !search || [r.name, r.phone, r.userid].join(' ').toLowerCase().includes(search.toLowerCase()) + ), + [search] + ); + + const allChecked = filtered.length > 0 && filtered.every((r) => selected.includes(r.userid)); + const someChecked = filtered.some((r) => selected.includes(r.userid)) && !allChecked; + + const toggle = (id) => setSelected((p) => (p.includes(id) ? p.filter((x) => x !== id) : [...p, id])); + const toggleAll = (checked) => + setSelected(checked ? [...new Set([...selected, ...filtered.map((r) => r.userid)])] : selected.filter((id) => !filtered.some((r) => r.userid === id))); + + const mapRiders = ridersLive + .filter((r) => r.active && selected.includes(r.userid)) + .map((r, i) => ({ ...POSITIONS[i % POSITIONS.length], active: true })); + + return ( + <> + + + + {/* Left side panel */} + + + setSearch(e.target.value)} + InputProps={{ startAdornment: }} + /> + + + + toggleAll(e.target.checked)} /> + All + + {selected.length} selected + + + + {filtered.map((r) => ( + + toggle(r.userid)} sx={{ mt: -0.5 }} /> + + + + {r.name} + + {r.phone} + + {r.userid} + {r.lastLog} + + + + ))} + {filtered.length === 0 && ( + No riders found + )} + + + + {/* Map area */} + + + + Riders Locations + + + + + + + ); +} diff --git a/src/pages/reports/RidersSummary.jsx b/src/pages/reports/RidersSummary.jsx new file mode 100644 index 0000000..1203e58 --- /dev/null +++ b/src/pages/reports/RidersSummary.jsx @@ -0,0 +1,167 @@ +import { useState, Fragment } from 'react'; +import { + Card, Stack, Button, TextField, MenuItem, Box, Collapse, IconButton, Chip, Tooltip, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, + Dialog, DialogTitle, DialogContent +} from '@mui/material'; +import CalendarTodayOutlinedIcon from '@mui/icons-material/CalendarTodayOutlined'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; +import RoomOutlinedIcon from '@mui/icons-material/RoomOutlined'; +import CloseIcon from '@mui/icons-material/Close'; + +import PageHeader from '@/components/PageHeader'; +import UserAvatar from '@/components/UserAvatar'; +import MapPlaceholder from '@/components/MapPlaceholder'; +import { ridersSummary, locations } from '@/data/mock'; +import { inr } from '@/utils/format'; + +function NumCell({ value, align = 'center' }) { + const zero = Number(value) === 0; + return {value}; +} + +function KmsChips({ kms, actual }) { + return ( + + + + + ); +} + +export default function RidersSummary() { + const [open, setOpen] = useState({}); + const [location, setLocation] = useState('all'); + const [mapRider, setMapRider] = useState(null); + + const toggle = (id) => setOpen((p) => ({ ...p, [id]: !p[id] })); + const totalAmount = ridersSummary.reduce((a, r) => a + r.amount, 0); + + const headSx = { bgcolor: 'primary.lighter', fontWeight: 700, color: 'primary.dark', whiteSpace: 'nowrap' }; + + return ( + <> + setLocation(e.target.value)} sx={{ minWidth: 160 }} label="Location"> + All Locations + {locations.map((l) => {l})} + + } + /> + + + + + + + + + + + + # + Rider + Orders + Pending + Cancelled + Delivered + KMS + Amount + Action + + + + {ridersSummary.map((r, idx) => ( + + + {idx + 1} + + + + {r.rider} + + + + + + + + {inr(r.amount)} + + + toggle(r.id)}> + {open[r.id] ? : } + + + + setMapRider(r)}> + + + + + + + + Client Summary +
+ + + # + Client + All + Pending + Completed + Cancelled + Kms + Amount + + + + {r.clients.map((c, i) => ( + + {i + 1} + {c.client} + + + + + + {inr(c.amount)} + + ))} + +
+ + + + + + ))} + + +
+ + + Total Amount + {inr(totalAmount)} + +
+ + setMapRider(null)} maxWidth="md" fullWidth> + + {mapRider?.rider} — Location + setMapRider(null)}> + + + + + + + ); +} diff --git a/src/pages/riders/CreateRider.jsx b/src/pages/riders/CreateRider.jsx new file mode 100644 index 0000000..c84c08f --- /dev/null +++ b/src/pages/riders/CreateRider.jsx @@ -0,0 +1,71 @@ +import { useNavigate } from 'react-router-dom'; +import { + Grid, Card, CardContent, Stack, Button, TextField, Divider, InputAdornment +} from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; + +import PageHeader from '@/components/PageHeader'; + +export default function CreateRider() { + const navigate = useNavigate(); + + return ( + <> + + + + + + + + + + +91 }} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/pages/riders/EditRider.jsx b/src/pages/riders/EditRider.jsx new file mode 100644 index 0000000..898694b --- /dev/null +++ b/src/pages/riders/EditRider.jsx @@ -0,0 +1,180 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Grid, Card, CardContent, Stack, Button, TextField, MenuItem, Typography, Divider, Box, IconButton, InputAdornment +} from '@mui/material'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import dayjs from 'dayjs'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; + +import PageHeader from '@/components/PageHeader'; +import { riders, tenantsList } from '@/data/mock'; + +const SHIFTS = ['Morning', 'Evening', 'Night']; +const ACCOUNT_TYPES = ['Savings', 'Current']; +const VEHICLES = ['Honda Activa', 'TVS Jupiter', 'Hero Splendor', 'Bajaj Pulsar', 'Honda Dio']; + +const SectionTitle = ({ children }) => ( + <> + {children} + + +); + +export default function EditRider() { + const navigate = useNavigate(); + const rider = riders[0]; + const [firstName] = rider.name.split(' '); + const lastName = rider.name.split(' ').slice(1).join(' '); + const [insuranceExpiry, setInsuranceExpiry] = useState(dayjs().add(8, 'month')); + + return ( + <> + + navigate('/riders')}> + Edit Rider + + } + breadcrumbs={[{ label: 'Riders', to: '/riders' }, { label: 'Edit Rider' }]} + /> + + + + Contact Information + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {tenantsList.map((t) => {t})} + + + + + + Charges + + + + {SHIFTS.map((s) => {s})} + + + + ₹ }} /> + + + ₹ }} /> + + + ₹ }} /> + + + + + + Bank Details + + + + + + + + + + {ACCOUNT_TYPES.map((a) => {a})} + + + + + + + + + + + + + + + + Vehicle Details + + + + {VEHICLES.map((v) => {v})} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/pages/riders/Riders.jsx b/src/pages/riders/Riders.jsx new file mode 100644 index 0000000..fc03fad --- /dev/null +++ b/src/pages/riders/Riders.jsx @@ -0,0 +1,221 @@ +import { useState, useMemo, Fragment } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Grid, Card, Stack, Button, TextField, MenuItem, InputAdornment, Box, Tabs, Tab, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton, Tooltip, + TablePagination, Typography, Chip, Collapse +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import AddIcon from '@mui/icons-material/Add'; +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; +import GroupsOutlinedIcon from '@mui/icons-material/GroupsOutlined'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import TwoWheelerOutlinedIcon from '@mui/icons-material/TwoWheelerOutlined'; +import PowerSettingsNewOutlinedIcon from '@mui/icons-material/PowerSettingsNewOutlined'; + +import PageHeader from '@/components/PageHeader'; +import StatCard from '@/components/StatCard'; +import StatusChip from '@/components/StatusChip'; +import UserAvatar from '@/components/UserAvatar'; +import TabLabelCount from '@/components/TabLabelCount'; +import { riders, riderLogs, locations } from '@/data/mock'; +import { inr } from '@/utils/format'; + +const TABS = [ + { key: 'all', label: 'ALL' }, + { key: 'active', label: 'Active' } +]; + +export default function Riders() { + const navigate = useNavigate(); + const [tab, setTab] = useState(0); + const [search, setSearch] = useState(''); + const [location, setLocation] = useState('all'); + const [page, setPage] = useState(0); + const [rpp, setRpp] = useState(5); + const [expanded, setExpanded] = useState(null); + + const tabKey = TABS[tab].key; + const filtered = useMemo( + () => + riders.filter((r) => { + const matchTab = tabKey === 'all' ? true : r.status !== 'offline'; + const matchLocation = location === 'all' || r.address.includes(location); + const matchSearch = + !search || + [r.id, r.userId, r.name, r.phone, r.address, r.vehicle, r.vehicleNo] + .join(' ') + .toLowerCase() + .includes(search.toLowerCase()); + return matchTab && matchLocation && matchSearch; + }), + [tabKey, location, search] + ); + + const paged = filtered.slice(page * rpp, page * rpp + rpp); + + const counts = { + total: riders.length, + active: riders.filter((r) => r.status === 'online').length, + onDelivery: riders.filter((r) => r.status === 'on-delivery').length, + offline: riders.filter((r) => r.status === 'offline').length, + all: riders.length, + activeTab: riders.filter((r) => r.status !== 'offline').length + }; + + return ( + <> + + setLocation(e.target.value)} sx={{ minWidth: 170 }} label="Location"> + All Locations + {locations.map((l) => {l})} + + + + } + /> + + + + + + + + + + + { setSearch(e.target.value); setPage(0); }} + sx={{ minWidth: 240 }} + InputProps={{ startAdornment: }} + /> + + + + + { setTab(v); setPage(0); }}> + {TABS.map((t, i) => ( + } + /> + ))} + + + + + + + + S.NO + User ID + Rider + Address + Vehicle + Shift + Time + Fare + Fuel + Status + Action + + + + {paged.map((r, i) => ( + + + {page * rpp + i + 1} + {r.userId} + + + + + {r.name} + {r.phone} + + + + {r.address} + + {r.vehicle} + {r.vehicleNo} + + {r.shift} + + + + + + + {inr(r.fare)} + {inr(r.fuel)} + + + + navigate(`/riders/${r.id}/edit`)}> + + + setExpanded(expanded === r.id ? null : r.id)}> + {expanded === r.id ? : } + + + + + + + + + Rider Logs +
+ + + Location + Battery + Charging + Speed + Accuracy + Time + Order + Status + + + + {riderLogs.map((log, li) => ( + + {log.location} + {log.battery} + {log.charging} + {log.speed} + {log.accuracy} + {log.time} + {log.order} + + + ))} + +
+ + + + + + ))} + + +
+ setPage(p)} + rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[5, 10, 25]} + /> +
+ + ); +} diff --git a/src/pages/tenants/CreateClient.jsx b/src/pages/tenants/CreateClient.jsx new file mode 100644 index 0000000..d998415 --- /dev/null +++ b/src/pages/tenants/CreateClient.jsx @@ -0,0 +1,81 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Grid, Stack, Button, TextField, MenuItem, InputAdornment } from '@mui/material'; + +import PageHeader from '@/components/PageHeader'; +import MainCard from '@/components/MainCard'; + +const COUNTRY_CODES = ['+91', '+1', '+44', '+61', '+971']; + +export default function CreateClient() { + const navigate = useNavigate(); + const [code, setCode] = useState('+91'); + const [form, setForm] = useState({ + adminName: '', phone: '', email: '', address: '', + suburb: '', city: '', state: '', postcode: '', doorNo: '', landmark: '' + }); + const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value })); + + return ( + <> + + + + + + + + + + setCode(e.target.value)} + InputProps={{ disableUnderline: true }} sx={{ minWidth: 56 }} + > + {COUNTRY_CODES.map((c) => {c})} + + + ) + }} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/pages/tenants/Tenants.jsx b/src/pages/tenants/Tenants.jsx new file mode 100644 index 0000000..6dc9d17 --- /dev/null +++ b/src/pages/tenants/Tenants.jsx @@ -0,0 +1,258 @@ +import { useState, useMemo, Fragment } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Card, Stack, Button, TextField, InputAdornment, Box, Tabs, Tab, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton, + Tooltip, TablePagination, Typography, Collapse, Grid +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import AddIcon from '@mui/icons-material/Add'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; + +import PageHeader from '@/components/PageHeader'; +import StatusChip from '@/components/StatusChip'; +import EmptyState from '@/components/EmptyState'; +import UserAvatar from '@/components/UserAvatar'; +import TabLabelCount from '@/components/TabLabelCount'; +import { tenants, tenantPricing } from '@/data/mock'; +import { inr } from '@/utils/format'; + +const TABS = [ + { key: 'active', label: 'Active' }, + { key: 'pending', label: 'Pending' }, + { key: 'inactive', label: 'Inactive' } +]; + +function ReadField({ label, value }) { + return ( + + {label} + {value || '—'} + + ); +} + +function PricingTab() { + return ( + + + + + + + + + Date + Slab + Base Price + Min Kms + Price/Km + Other Charges + + + + {tenantPricing.map((p, i) => ( + + {p.date} + {p.slab} + {inr(p.basePrice)} + {p.minKms} + {inr(p.pricePerKm)} + {inr(p.otherCharges)} + + ))} + +
+
+
+ ); +} + +function EditTab({ tenant }) { + const [form, setForm] = useState({ + name: tenant.name, contact: tenant.contact, phone: tenant.phone, email: tenant.email, + address: tenant.address, city: tenant.city, postcode: tenant.postcode, lat: tenant.lat, lng: tenant.lng + }); + const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value })); + + return ( + + + + + + + + + + + + + + + + + ); +} + +function TenantRow({ row, index }) { + const [open, setOpen] = useState(false); + const [inner, setInner] = useState(0); + + return ( + + *': { borderBottom: open ? 'unset' : undefined } }}> + + setOpen((o) => !o)}> + {open ? : } + + + {index + 1} + + + + + {row.name} + {row.volume} orders / mo + + + + + {row.contact} + {row.phone} + + + {row.address} + {row.city} · {row.postcode} + + + + + + + + + setInner(v)} sx={{ px: 2 }}> + + + + + + + {inner === 0 && ( + + + + + + + + + + + + )} + {inner === 1 && } + {inner === 2 && } + + + + + + + ); +} + +export default function Tenants() { + const navigate = useNavigate(); + const [tab, setTab] = useState(0); + const [search, setSearch] = useState(''); + const [page, setPage] = useState(0); + const [rpp, setRpp] = useState(10); + + const tabKey = TABS[tab].key; + + const counts = useMemo(() => { + const c = {}; + TABS.forEach((t) => { c[t.key] = tenants.filter((d) => d.status === t.key).length; }); + return c; + }, []); + + const filtered = useMemo( + () => + tenants.filter((t) => { + const matchTab = t.status === tabKey; + const matchSearch = + !search || + [t.name, t.contact, t.email, t.phone, t.city].join(' ').toLowerCase().includes(search.toLowerCase()); + return matchTab && matchSearch; + }), + [tabKey, search] + ); + + const paged = filtered.slice(page * rpp, page * rpp + rpp); + + return ( + <> + } onClick={() => navigate('/tenants/create')}> + Create Client + + } + /> + + + + { setSearch(e.target.value); setPage(0); }} + sx={{ minWidth: 260 }} + InputProps={{ startAdornment: }} + /> + + + + + { setTab(v); setPage(0); }}> + {TABS.map((t, i) => ( + } /> + ))} + + + + + + + + + S.No + Client + Contact + Address + Actions + + + + {paged.length === 0 ? ( + + + + + + ) : ( + paged.map((row, i) => ) + )} + +
+
+ setPage(p)} + rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[5, 10, 25]} + /> +
+ + ); +} diff --git a/src/theme/componentsOverride.js b/src/theme/componentsOverride.js new file mode 100644 index 0000000..e3fa678 --- /dev/null +++ b/src/theme/componentsOverride.js @@ -0,0 +1,121 @@ +import customShadows from './shadows'; + +// ==============================|| DOORMILE THEME - COMPONENT OVERRIDES ||============================== // +// Clean, corporate Material Design tuning for the whole console. + +export default function componentsOverride(theme) { + const { palette } = theme; + + return { + MuiCssBaseline: { + styleOverrides: { + body: { backgroundColor: palette.background.default }, + '*::-webkit-scrollbar': { width: 8, height: 8 }, + '*::-webkit-scrollbar-thumb': { background: palette.grey[300], borderRadius: 8 }, + '*::-webkit-scrollbar-thumb:hover': { background: palette.grey[400] } + } + }, + MuiButton: { + defaultProps: { disableElevation: true }, + styleOverrides: { + root: { borderRadius: 6, fontWeight: 600, padding: '7px 18px' }, + containedPrimary: { + boxShadow: customShadows.primaryGlow, + '&:hover': { boxShadow: customShadows.primaryGlowHover, backgroundColor: palette.primary.dark } + }, + outlined: { borderColor: palette.grey[300] }, + sizeLarge: { padding: '10px 22px', fontSize: '0.9375rem' } + } + }, + MuiIconButton: { + styleOverrides: { root: { borderRadius: 8 } } + }, + MuiCard: { + styleOverrides: { + root: { + borderRadius: 10, + border: `1px solid ${palette.grey[200]}`, + boxShadow: customShadows.card, + backgroundImage: 'none' + } + } + }, + MuiCardHeader: { + defaultProps: { titleTypographyProps: { variant: 'h5' }, subheaderTypographyProps: { variant: 'caption' } }, + styleOverrides: { root: { padding: 20 } } + }, + MuiCardContent: { + styleOverrides: { root: { padding: 20, '&:last-child': { paddingBottom: 20 } } } + }, + MuiPaper: { + defaultProps: { elevation: 0 }, + styleOverrides: { rounded: { borderRadius: 10 } } + }, + MuiChip: { + styleOverrides: { + root: { borderRadius: 6, fontWeight: 600, fontSize: '0.75rem' }, + sizeSmall: { height: 22 }, + label: { paddingLeft: 8, paddingRight: 8 } + } + }, + MuiTableCell: { + styleOverrides: { + root: { borderColor: palette.grey[200], padding: '12px 16px', fontSize: '0.8125rem' }, + head: { + fontWeight: 600, + color: palette.grey[600], + backgroundColor: palette.grey[50], + textTransform: 'none', + whiteSpace: 'nowrap' + } + } + }, + MuiTableRow: { + styleOverrides: { + root: { '&:hover': { backgroundColor: palette.primary.lighter + '66' } } + } + }, + MuiOutlinedInput: { + styleOverrides: { + root: { + borderRadius: 8, + backgroundColor: palette.background.paper, + '& .MuiOutlinedInput-notchedOutline': { borderColor: palette.grey[300] }, + '&:hover .MuiOutlinedInput-notchedOutline': { borderColor: palette.grey[400] } + }, + input: { padding: '11px 14px' } + } + }, + MuiInputLabel: { + styleOverrides: { root: { color: palette.grey[600], fontSize: '0.875rem' } } + }, + MuiTab: { + styleOverrides: { + root: { textTransform: 'none', fontWeight: 600, minHeight: 46, fontSize: '0.875rem' } + } + }, + MuiTabs: { + styleOverrides: { indicator: { height: 3, borderRadius: 3 } } + }, + MuiTooltip: { + styleOverrides: { + tooltip: { backgroundColor: palette.grey[800], borderRadius: 6, fontSize: '0.75rem', padding: '6px 10px' } + } + }, + MuiDialog: { + styleOverrides: { paper: { borderRadius: 12 } } + }, + MuiAvatar: { + styleOverrides: { root: { fontWeight: 600, fontSize: '0.875rem' } } + }, + MuiListItemButton: { + styleOverrides: { root: { borderRadius: 8 } } + }, + MuiLinearProgress: { + styleOverrides: { root: { borderRadius: 8, height: 6, backgroundColor: palette.grey[200] } } + }, + MuiMenu: { + styleOverrides: { paper: { borderRadius: 10, boxShadow: customShadows.dropdown, marginTop: 4 } } + } + }; +} diff --git a/src/theme/index.js b/src/theme/index.js new file mode 100644 index 0000000..53e9cb8 --- /dev/null +++ b/src/theme/index.js @@ -0,0 +1,20 @@ +import { createTheme } from '@mui/material/styles'; + +import palette from './palette'; +import typography from './typography'; +import customShadows from './shadows'; +import componentsOverride from './componentsOverride'; + +// ==============================|| DOORMILE THEME - ENTRY ||============================== // + +let theme = createTheme({ + palette, + typography, + shape: { borderRadius: 6 }, + customShadows, + mixins: { toolbar: { minHeight: 64 } } +}); + +theme.components = componentsOverride(theme); + +export default theme; diff --git a/src/theme/palette.js b/src/theme/palette.js new file mode 100644 index 0000000..d4d03a5 --- /dev/null +++ b/src/theme/palette.js @@ -0,0 +1,102 @@ +// ==============================|| DOORMILE THEME - PALETTE ||============================== // +// Corporate red brand palette. Brand red #C01227. + +export const grey = { + 0: '#FFFFFF', + 50: '#FAFAFA', + 100: '#F5F5F5', + 200: '#F0F0F0', + 300: '#D9D9D9', + 400: '#BFBFBF', + 500: '#8C8C8C', + 600: '#595959', + 700: '#434343', + 800: '#262626', + 900: '#141414', + A50: '#FAFAFB', + A100: '#E6EBF1' +}; + +const palette = { + mode: 'light', + common: { black: '#000000', white: '#FFFFFF' }, + primary: { + lighter: '#F8E0E3', + 100: '#EFBBC1', + 200: '#E08A92', + light: '#D6515C', + 400: '#CC2E3C', + main: '#C01227', + dark: '#9E0E20', + 700: '#870C1B', + darker: '#7E0B17', + 900: '#520710', + contrastText: '#FFFFFF' + }, + secondary: { + lighter: grey[100], + 100: grey[100], + 200: grey[200], + light: grey[300], + 400: grey[400], + main: grey[500], + 600: grey[600], + dark: grey[700], + 800: grey[800], + darker: grey[900], + A100: grey[0], + A200: grey[400], + A300: grey[700], + contrastText: grey[0] + }, + error: { + lighter: '#FEEAE9', + light: '#F88078', + main: '#F04134', + dark: '#A82216', + darker: '#7A150C', + contrastText: '#FFFFFF' + }, + warning: { + lighter: '#FFF7E0', + light: '#FFD666', + main: '#FFBF00', + dark: '#B38600', + darker: '#805F00', + contrastText: '#262626' + }, + info: { + lighter: '#E0F7F8', + light: '#66CBD2', + main: '#00A2AE', + dark: '#00727B', + darker: '#005159', + contrastText: '#FFFFFF' + }, + success: { + lighter: '#E3F6EC', + light: '#5CC98C', + main: '#00A854', + dark: '#00773B', + darker: '#00552A', + contrastText: '#FFFFFF' + }, + grey, + text: { + primary: grey[800], + secondary: grey[600], + disabled: grey[400] + }, + action: { + disabled: grey[300], + hover: 'rgba(192, 18, 39, 0.04)', + selected: 'rgba(192, 18, 39, 0.08)' + }, + divider: grey[200], + background: { + paper: '#FFFFFF', + default: grey.A50 + } +}; + +export default palette; diff --git a/src/theme/shadows.js b/src/theme/shadows.js new file mode 100644 index 0000000..08abe00 --- /dev/null +++ b/src/theme/shadows.js @@ -0,0 +1,14 @@ +// ==============================|| DOORMILE THEME - CUSTOM SHADOWS ||============================== // +// Soft, subtle corporate elevation + a branded red glow for primary CTAs. + +const customShadows = { + card: '0px 1px 4px rgba(0, 0, 0, 0.08)', + cardHover: '0px 4px 16px rgba(0, 0, 0, 0.10)', + widget: '0px 2px 14px rgba(38, 38, 38, 0.06)', + dropdown: '0px 8px 24px rgba(38, 38, 38, 0.12)', + primaryGlow: '0px 6px 16px rgba(192, 18, 39, 0.28)', + primaryGlowHover: '0px 8px 20px rgba(192, 18, 39, 0.36)', + header: '0px 1px 0px rgba(0, 0, 0, 0.06)' +}; + +export default customShadows; diff --git a/src/theme/typography.js b/src/theme/typography.js new file mode 100644 index 0000000..89b32d7 --- /dev/null +++ b/src/theme/typography.js @@ -0,0 +1,25 @@ +// ==============================|| DOORMILE THEME - TYPOGRAPHY ||============================== // + +const typography = { + fontFamily: '"Public Sans", "Inter", "Helvetica", "Arial", sans-serif', + htmlFontSize: 16, + fontWeightLight: 300, + fontWeightRegular: 400, + fontWeightMedium: 500, + fontWeightBold: 600, + h1: { fontWeight: 700, fontSize: '2.375rem', lineHeight: 1.21 }, + h2: { fontWeight: 700, fontSize: '1.875rem', lineHeight: 1.27 }, + h3: { fontWeight: 600, fontSize: '1.5rem', lineHeight: 1.33 }, + h4: { fontWeight: 600, fontSize: '1.25rem', lineHeight: 1.4 }, + h5: { fontWeight: 600, fontSize: '1rem', lineHeight: 1.5 }, + h6: { fontWeight: 500, fontSize: '0.875rem', lineHeight: 1.57 }, + caption: { fontWeight: 400, fontSize: '0.75rem', lineHeight: 1.66 }, + body1: { fontSize: '0.875rem', lineHeight: 1.57 }, + body2: { fontSize: '0.75rem', lineHeight: 1.66 }, + subtitle1: { fontSize: '0.875rem', fontWeight: 600, lineHeight: 1.57 }, + subtitle2: { fontSize: '0.75rem', fontWeight: 500, lineHeight: 1.66 }, + overline: { fontSize: '0.6875rem', fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase' }, + button: { textTransform: 'capitalize', fontWeight: 600 } +}; + +export default typography; diff --git a/src/utils/format.js b/src/utils/format.js new file mode 100644 index 0000000..e5e76cb --- /dev/null +++ b/src/utils/format.js @@ -0,0 +1,20 @@ +// ==============================|| FORMAT HELPERS ||============================== // + +export const inr = (n) => + '₹' + Number(n || 0).toLocaleString('en-IN', { minimumFractionDigits: 0, maximumFractionDigits: 2 }); + +export const stringToColor = (string = '') => { + let hash = 0; + for (let i = 0; i < string.length; i += 1) hash = string.charCodeAt(i) + ((hash << 5) - hash); + const palette = ['#C01227', '#00A854', '#00A2AE', '#FFBF00', '#662582', '#1565C0', '#EF6C00', '#5C6BC0']; + return palette[Math.abs(hash) % palette.length]; +}; + +export const initials = (name = '') => + name + .split(' ') + .filter(Boolean) + .slice(0, 2) + .map((w) => w[0]) + .join('') + .toUpperCase(); diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..26d9664 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, 'src') + } + }, + server: { + port: 3000, + open: true + } +});