feat(admin): Phase 0 — Tailwind v4 foundation, shell rewrite, modern dashboard
- Install Tailwind CSS v4 via @tailwindcss/vite; configure vite.config.ts - Rewrite app.css: Tailwind base, Exo 2 font, brand tokens (orange #fd6216, navy #050026), scrollbar utility; fix @import order - Rewrite AdminShell.tsx: fixed header, fixed inset body grid (sidebar + main), session check, sub-tab system, logout, admin avatar/name/role - Rewrite AdminSidebar.tsx: collapsible w-64/w-20, orange active rail + badge/dot, CSS filter for SVG icon tinting, min-h-0 flex fix - Replace 84 route stub CSS classes (page-title, card, btn, table-wrap, etc.) with Tailwind equivalents via safe class-attr-only regex script - Rewrite admin dashboard: Lucide icons in colored chip backgrounds, 4-col KPI grid, Control Plane 6-module grid, hover lift animations - Disable SSR (ssr: false) to fix Vinxi dev manifest error; clear stale .vinxi cache - Add lucide-solid icon library - Add scripts/cleanup-css.mjs for class migration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0050277922
commit
a272276055
92 changed files with 1879 additions and 2871 deletions
|
|
@ -4,9 +4,8 @@
|
|||
{
|
||||
"name": "admin-solid",
|
||||
"runtimeExecutable": "sh",
|
||||
"runtimeArgs": ["-c", "cd /Users/ashwin/workspace/nxtgauge-admin-solid && npm run dev -- --port $PORT"],
|
||||
"port": 3002,
|
||||
"autoPort": true
|
||||
"runtimeArgs": ["-c", "cd /Users/ashwin/workspace/nxtgauge-admin-solid && npm run dev -- --port 3002"],
|
||||
"port": 3002
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
327
package-lock.json
generated
327
package-lock.json
generated
|
|
@ -9,7 +9,10 @@
|
|||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.15.0",
|
||||
"@solidjs/start": "^1.3.2",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"lucide-solid": "^1.0.1",
|
||||
"solid-js": "^1.9.5",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"vinxi": "^0.5.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -355,7 +358,6 @@
|
|||
"integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.0",
|
||||
"tslib": "^2.4.0"
|
||||
|
|
@ -367,7 +369,6 @@
|
|||
"integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
|
|
@ -378,7 +379,6 @@
|
|||
"integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
|
|
@ -968,7 +968,6 @@
|
|||
"integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1",
|
||||
|
|
@ -2311,6 +2310,272 @@
|
|||
"integrity": "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
|
||||
"integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"enhanced-resolve": "^5.19.0",
|
||||
"jiti": "^2.6.1",
|
||||
"lightningcss": "1.32.0",
|
||||
"magic-string": "^0.30.21",
|
||||
"source-map-js": "^1.2.1",
|
||||
"tailwindcss": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/node/node_modules/jiti": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz",
|
||||
"integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tailwindcss/oxide-android-arm64": "4.2.2",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.2.2",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.2.2",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.2.2",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.2.2",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.2.2",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.2.2",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.2.2",
|
||||
"@tailwindcss/oxide-wasm32-wasi": "4.2.2",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.2.2",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz",
|
||||
"integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz",
|
||||
"integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz",
|
||||
"integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz",
|
||||
"integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz",
|
||||
"integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz",
|
||||
"integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz",
|
||||
"integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz",
|
||||
"integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz",
|
||||
"integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz",
|
||||
"integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==",
|
||||
"bundleDependencies": [
|
||||
"@napi-rs/wasm-runtime",
|
||||
"@emnapi/core",
|
||||
"@emnapi/runtime",
|
||||
"@tybys/wasm-util",
|
||||
"@emnapi/wasi-threads",
|
||||
"tslib"
|
||||
],
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.8.1",
|
||||
"@emnapi/runtime": "^1.8.1",
|
||||
"@emnapi/wasi-threads": "^1.1.0",
|
||||
"@napi-rs/wasm-runtime": "^1.1.1",
|
||||
"@tybys/wasm-util": "^0.10.1",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz",
|
||||
"integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz",
|
||||
"integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz",
|
||||
"integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tailwindcss/node": "4.2.2",
|
||||
"@tailwindcss/oxide": "4.2.2",
|
||||
"tailwindcss": "4.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/directive-functions-plugin": {
|
||||
"version": "1.121.21",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/directive-functions-plugin/-/directive-functions-plugin-1.121.21.tgz",
|
||||
|
|
@ -2391,7 +2656,6 @@
|
|||
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
|
|
@ -4009,6 +4273,19 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.20.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
|
||||
"integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
|
|
@ -5019,7 +5296,6 @@
|
|||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
||||
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
||||
"license": "MPL-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
|
|
@ -5056,7 +5332,6 @@
|
|||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
|
|
@ -5077,7 +5352,6 @@
|
|||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
|
|
@ -5098,7 +5372,6 @@
|
|||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
|
|
@ -5119,7 +5392,6 @@
|
|||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
|
|
@ -5140,7 +5412,6 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
|
|
@ -5161,7 +5432,6 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
|
|
@ -5182,7 +5452,6 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
|
|
@ -5203,7 +5472,6 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
|
|
@ -5224,7 +5492,6 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
|
|
@ -5245,7 +5512,6 @@
|
|||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
|
|
@ -5266,7 +5532,6 @@
|
|||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
|
|
@ -5401,6 +5666,15 @@
|
|||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-solid": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lucide-solid/-/lucide-solid-1.0.1.tgz",
|
||||
"integrity": "sha512-fpo0UmEHdEmdvydbFS9lQUqQ9rqSqWXMY4w3n9tVr9FqBBWJB0YI/lTMSxCspHs706ZzHIS/1YEnDhLOIP+m+A==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.4.7"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
|
|
@ -7355,6 +7629,25 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
|
||||
"integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.1.tgz",
|
||||
"integrity": "sha512-b+u3CEM6FjDHru+nhUSjDofpWSBp2rINziJWgApm72wwGasQ/wKXftZe4tI2Y5HPv6OpzXSZHOFq87H4vfsgsw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.11",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@
|
|||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.15.0",
|
||||
"@solidjs/start": "^1.3.2",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"lucide-solid": "^1.0.1",
|
||||
"solid-js": "^1.9.5",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"vinxi": "^0.5.7"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
|||
167
scripts/cleanup-css.mjs
Normal file
167
scripts/cleanup-css.mjs
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Replace old hand-written CSS class names with Tailwind v4 equivalents.
|
||||
* SAFE version: only replaces inside class="..." attribute values.
|
||||
*
|
||||
* Run: node scripts/cleanup-css.mjs
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { globSync } from 'glob';
|
||||
|
||||
// Order matters: most-specific (multi-word) patterns first.
|
||||
// Each entry: [old-class-string, new-tailwind-classes]
|
||||
const REPLACEMENTS = [
|
||||
// ── Page structure ──────────────────────────────────────────────
|
||||
['page-hero-card page-actions', 'mb-6 flex items-start justify-between gap-4'],
|
||||
['page-actions-right', 'flex items-center gap-2'],
|
||||
['page-actions', 'mb-6 flex items-start justify-between gap-4'],
|
||||
['page-title-block', 'flex flex-col gap-1'],
|
||||
['page-title', 'text-2xl font-bold text-gray-900'],
|
||||
['page-subtitle', 'mt-1 text-sm text-gray-500'],
|
||||
|
||||
// ── Card ────────────────────────────────────────────────────────
|
||||
['card', 'rounded-xl border border-gray-200 bg-white shadow-sm'],
|
||||
|
||||
// ── Buttons (specific before generic) ───────────────────────────
|
||||
['btn navy', 'inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors'],
|
||||
['btn primary', 'inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors'],
|
||||
['btn danger', 'inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors'],
|
||||
['btn sm', 'inline-flex items-center rounded-md border border-gray-200 bg-white px-3 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-50 transition-colors'],
|
||||
['btn', 'inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors'],
|
||||
|
||||
// ── Table ────────────────────────────────────────────────────────
|
||||
['table-wrap', 'overflow-x-auto'],
|
||||
['list-table', 'w-full text-sm'],
|
||||
['perm-table', 'w-full text-sm'],
|
||||
['table-actions', 'flex items-center justify-end gap-1'],
|
||||
['align-right', 'text-right'],
|
||||
['row-selected', 'bg-orange-50'],
|
||||
|
||||
// ── Icon action buttons ──────────────────────────────────────────
|
||||
['action-icon-btn danger', 'rounded p-1.5 text-red-500 hover:bg-red-50 hover:text-red-700 text-sm'],
|
||||
['action-icon-btn', 'rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm'],
|
||||
|
||||
// ── Status chips (specific before generic) ───────────────────────
|
||||
['status-chip active', 'inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700'],
|
||||
['status-chip pending', 'inline-flex items-center rounded-full bg-amber-100 px-2.5 py-0.5 text-xs font-medium text-amber-700'],
|
||||
['status-chip inactive', 'inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-500'],
|
||||
['status-chip', 'inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600'],
|
||||
|
||||
// ── Error / notice ───────────────────────────────────────────────
|
||||
['error-box', 'mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700'],
|
||||
|
||||
// ── Form sections ────────────────────────────────────────────────
|
||||
['role-form-section', 'mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm'],
|
||||
['field-grid-2', 'mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2'],
|
||||
['module-picker', 'mt-3 flex flex-wrap gap-2'],
|
||||
['module-chip selected', 'flex cursor-pointer items-center gap-1.5 rounded-full border border-orange-300 bg-orange-50 px-3 py-1.5 text-xs font-medium text-orange-700'],
|
||||
['module-chip', 'flex cursor-pointer items-center gap-1.5 rounded-full border border-gray-200 bg-white px-3 py-1.5 text-xs font-medium text-gray-600 hover:border-gray-300'],
|
||||
|
||||
// ── Legacy tab nav (now handled by AdminShell) ────────────────────
|
||||
['admin-link-tabs', 'hidden'],
|
||||
['admin-link-tab active', 'hidden'],
|
||||
['admin-link-tab', 'hidden'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Replace old CSS class names only inside JSX class attribute values.
|
||||
* Handles:
|
||||
* class="foo bar baz"
|
||||
* class={'foo bar baz'}
|
||||
* class={`foo bar baz`}
|
||||
* class={condition ? 'foo' : 'bar'}
|
||||
*
|
||||
* We do this by extracting the string portion of class attributes and
|
||||
* replacing only within those extracted portions.
|
||||
*/
|
||||
function replaceInClassAttributes(src) {
|
||||
let changed = false;
|
||||
|
||||
// Process class="..." and class='...' (static strings)
|
||||
src = src.replace(/class=["']([^"']*)["']/g, (match, classes) => {
|
||||
let replaced = classes;
|
||||
for (const [from, to] of REPLACEMENTS) {
|
||||
// Replace whole-word occurrences within the class string
|
||||
// Use word boundaries to avoid partial matches
|
||||
const re = new RegExp(`(?<![\\w-])${escapeRe(from)}(?![\\w-])`, 'g');
|
||||
replaced = replaced.replace(re, to);
|
||||
}
|
||||
if (replaced !== classes) changed = true;
|
||||
const quote = match[6] === '"' ? '"' : "'"; // char after `class=`
|
||||
return `class=${quote}${replaced}${quote}`;
|
||||
});
|
||||
|
||||
// Process class={`...`} (template literals with static content)
|
||||
src = src.replace(/class=\{`([^`]*)`\}/g, (match, classes) => {
|
||||
let replaced = classes;
|
||||
for (const [from, to] of REPLACEMENTS) {
|
||||
const re = new RegExp(`(?<![\\w-])${escapeRe(from)}(?![\\w-])`, 'g');
|
||||
replaced = replaced.replace(re, to);
|
||||
}
|
||||
if (replaced !== classes) changed = true;
|
||||
return `class={\`${replaced}\`}`;
|
||||
});
|
||||
|
||||
// Process class={'...'} and class={"..."} (single-expression strings)
|
||||
src = src.replace(/class=\{(['"])([^'"]*)\1\}/g, (match, q, classes) => {
|
||||
let replaced = classes;
|
||||
for (const [from, to] of REPLACEMENTS) {
|
||||
const re = new RegExp(`(?<![\\w-])${escapeRe(from)}(?![\\w-])`, 'g');
|
||||
replaced = replaced.replace(re, to);
|
||||
}
|
||||
if (replaced !== classes) changed = true;
|
||||
return `class={${q}${replaced}${q}}`;
|
||||
});
|
||||
|
||||
// Process ternary class values: class={cond ? 'foo' : 'bar'}
|
||||
// Match string literals inside class={...} expressions
|
||||
src = src.replace(/class=\{[^}]+\}/g, (match) => {
|
||||
// Replace inside single/double-quoted string literals within the expression
|
||||
const replaced = match.replace(/'([^']*)'/g, (m, inner) => {
|
||||
let r = inner;
|
||||
for (const [from, to] of REPLACEMENTS) {
|
||||
const re = new RegExp(`(?<![\\w-])${escapeRe(from)}(?![\\w-])`, 'g');
|
||||
r = r.replace(re, to);
|
||||
}
|
||||
if (r !== inner) changed = true;
|
||||
return `'${r}'`;
|
||||
}).replace(/"([^"]*)"/g, (m, inner) => {
|
||||
// skip JSX attributes within the expression (avoid double replacement)
|
||||
let r = inner;
|
||||
for (const [from, to] of REPLACEMENTS) {
|
||||
const re = new RegExp(`(?<![\\w-])${escapeRe(from)}(?![\\w-])`, 'g');
|
||||
r = r.replace(re, to);
|
||||
}
|
||||
if (r !== inner) changed = true;
|
||||
return `"${r}"`;
|
||||
});
|
||||
return replaced;
|
||||
});
|
||||
|
||||
return { src, changed };
|
||||
}
|
||||
|
||||
function escapeRe(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
const files = globSync('src/routes/admin/**/*.tsx', {
|
||||
cwd: '/Users/ashwin/workspace/nxtgauge-admin-solid',
|
||||
absolute: true,
|
||||
});
|
||||
|
||||
let totalChanged = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const original = readFileSync(file, 'utf8');
|
||||
const { src, changed } = replaceInClassAttributes(original);
|
||||
|
||||
if (changed) {
|
||||
writeFileSync(file, src, 'utf8');
|
||||
totalChanged++;
|
||||
console.log(` updated: ${file.replace('/Users/ashwin/workspace/nxtgauge-admin-solid/', '')}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nDone. Updated ${totalChanged} / ${files.length} files.`);
|
||||
1746
src/app.css
1746
src/app.css
File diff suppressed because it is too large
Load diff
|
|
@ -3,7 +3,6 @@ import { createMemo, createSignal, onMount, type JSX } from 'solid-js';
|
|||
import AdminSidebar from './AdminSidebar';
|
||||
import { isExternalIdentity } from '~/lib/admin-auth';
|
||||
import { clearAdminSession, hasAdminSession, setAdminSession } from '~/lib/admin-session';
|
||||
import { sidebarCollapsed } from '~/lib/sidebar-state';
|
||||
|
||||
type Tab = { href: string; label: string; exact?: boolean };
|
||||
|
||||
|
|
@ -52,42 +51,45 @@ const TAB_SETS: Array<{ prefixes: string[]; tabs: Tab[] }> = [
|
|||
];
|
||||
|
||||
const PAGE_TITLES: Array<{ prefix: string; title: string }> = [
|
||||
{ prefix: '/admin/workspace', title: 'Dashboard Workspace' },
|
||||
{ prefix: '/admin/settings', title: 'Settings' },
|
||||
{ prefix: '/admin/role-modules', title: 'Role Modules' },
|
||||
{ prefix: '/admin/modules', title: 'Module Management' },
|
||||
{ prefix: '/admin/responses', title: 'Lead Responses' },
|
||||
{ prefix: '/admin/applications', title: 'Applications' },
|
||||
{ prefix: '/admin/financial', title: 'Financial Management' },
|
||||
{ prefix: '/admin/help', title: 'Support Management' },
|
||||
{ prefix: '/admin/verification-status', title: 'Verification Status' },
|
||||
{ prefix: '/admin/verification', title: 'Verification Review' },
|
||||
{ prefix: '/admin/employees', title: 'Employee Management' },
|
||||
{ prefix: '/admin/department', title: 'Department Management' },
|
||||
{ prefix: '/admin/designation', title: 'Designation Management' },
|
||||
{ prefix: '/admin/roles', title: 'Internal Role Management' },
|
||||
{ prefix: '/admin/runtime-roles', title: 'External Role Management' },
|
||||
{ prefix: '/admin/onboarding-schemas', title: 'External Onboarding Management' },
|
||||
{ prefix: '/admin/internal-dashboard-management', title: 'Internal Dashboard Management' },
|
||||
{ prefix: '/admin/external-dashboard-management', title: 'External Dashboard Management' },
|
||||
{ prefix: '/admin/role-ui-configs', title: 'External Dashboard Management' },
|
||||
{ prefix: '/admin/approval', title: 'Approval Management' },
|
||||
{ prefix: '/admin/users', title: 'Users Management' },
|
||||
{ prefix: '/admin/company', title: 'Company Management' },
|
||||
{ prefix: '/admin/customer', title: 'Customer Management' },
|
||||
{ prefix: '/admin/candidate', title: 'Candidate Management' },
|
||||
{ prefix: '/admin/customer', title: 'Customer Management' },
|
||||
{ prefix: '/admin/photographer', title: 'Photographer Management' },
|
||||
{ prefix: '/admin/makeup-artist', title: 'Makeup Artist Management' },
|
||||
{ prefix: '/admin/tutors', title: 'Tutors Management' },
|
||||
{ prefix: '/admin/developers', title: 'Developers Management' },
|
||||
{ prefix: '/admin/video-editors', title: 'Video Editor Management' },
|
||||
{ prefix: '/admin/fitness-trainers', title: 'Fitness Trainer Management' },
|
||||
{ prefix: '/admin/catering-services', title: 'Catering Services Management' },
|
||||
{ prefix: '/admin/graphic-designers', title: 'Graphics Designer Management' },
|
||||
{ prefix: '/admin/social-media-managers', title: 'Social Media Manager Management' },
|
||||
{ prefix: '/admin/jobs', title: 'Jobs Management' },
|
||||
{ prefix: '/admin/leads', title: 'Leads Management' },
|
||||
{ prefix: '/admin/requirements', title: 'Requirement Request' },
|
||||
{ prefix: '/admin/pricing', title: 'Pricing Management' },
|
||||
{ prefix: '/admin/invoice', title: 'Invoice Management' },
|
||||
{ prefix: '/admin/credit', title: 'Credit Management' },
|
||||
{ prefix: '/admin/ledger', title: 'Ledger Management' },
|
||||
{ prefix: '/admin/coupon', title: 'Coupon Management' },
|
||||
{ prefix: '/admin/discount', title: 'Discount Management' },
|
||||
{ prefix: '/admin/tax', title: 'Tax Management' },
|
||||
{ prefix: '/admin/order', title: 'Order Management' },
|
||||
{ prefix: '/admin/invoice', title: 'Invoice Management' },
|
||||
{ prefix: '/admin/review', title: 'Review Management' },
|
||||
{ prefix: '/admin/support', title: 'Support Management' },
|
||||
{ prefix: '/admin/kb', title: 'Knowledge Base Management' },
|
||||
{ prefix: '/admin/notifications', title: 'Notifications' },
|
||||
{ prefix: '/admin/report', title: 'Report Management' },
|
||||
{ prefix: '/admin/employees', title: 'Employee Management' },
|
||||
{ prefix: '/admin/roles', title: 'Internal Role Management' },
|
||||
{ prefix: '/admin/external-role-management', title: 'External Role Management' },
|
||||
{ prefix: '/admin/internal-role-management', title: 'Internal Role Management' },
|
||||
{ prefix: '/admin/runtime-roles', title: 'External Role Management' },
|
||||
{ prefix: '/admin/onboarding-management', title: 'External Onboarding Management' },
|
||||
{ prefix: '/admin/onboarding-schemas', title: 'External Onboarding Management' },
|
||||
{ prefix: '/admin/kb', title: 'KB Management' },
|
||||
{ prefix: '/admin/ledger', title: 'Ledger Management' },
|
||||
{ prefix: '/admin/workspace', title: 'Dashboard Workspace' },
|
||||
{ prefix: '/admin', title: 'Dashboard' },
|
||||
];
|
||||
|
||||
|
|
@ -96,6 +98,8 @@ export default function AdminShell(props: { children: JSX.Element }) {
|
|||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [checkedSession, setCheckedSession] = createSignal(false);
|
||||
const [adminName, setAdminName] = createSignal('Admin');
|
||||
const [adminRole, setAdminRole] = createSignal('Super Admin');
|
||||
|
||||
const tabs = createMemo<Tab[]>(() => {
|
||||
const path = location.pathname;
|
||||
|
|
@ -121,9 +125,6 @@ export default function AdminShell(props: { children: JSX.Element }) {
|
|||
});
|
||||
|
||||
onMount(() => {
|
||||
// ?_preview=1 or sessionStorage flag — bypass auth for UI testing without a live backend.
|
||||
// Sets the session cookie AND a sessionStorage flag so all subsequent pages in this tab
|
||||
// also skip the API check without needing ?_preview=1 in every URL.
|
||||
const isLocalDev =
|
||||
typeof window !== 'undefined' &&
|
||||
(window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
|
||||
|
|
@ -144,7 +145,6 @@ export default function AdminShell(props: { children: JSX.Element }) {
|
|||
navigate(`/login?from=${from}`, { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const accessToken =
|
||||
typeof sessionStorage !== 'undefined'
|
||||
|
|
@ -160,9 +160,9 @@ export default function AdminShell(props: { children: JSX.Element }) {
|
|||
credentials: 'include',
|
||||
});
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok || isExternalIdentity(payload)) {
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
if (!response.ok || isExternalIdentity(payload)) throw new Error('Unauthorized');
|
||||
if (payload?.full_name) setAdminName(payload.full_name);
|
||||
if (payload?.role?.name) setAdminRole(payload.role.name);
|
||||
setCheckedSession(true);
|
||||
} catch {
|
||||
clearAdminSession();
|
||||
|
|
@ -177,77 +177,109 @@ export default function AdminShell(props: { children: JSX.Element }) {
|
|||
const onLogout = async () => {
|
||||
await fetch('/api/gateway/users/auth/logout', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'x-portal-target': 'admin',
|
||||
},
|
||||
headers: { Accept: 'application/json', 'x-portal-target': 'admin' },
|
||||
credentials: 'include',
|
||||
}).catch(() => {});
|
||||
|
||||
clearAdminSession();
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.removeItem('nxtgauge_admin_access_token');
|
||||
sessionStorage.removeItem('nxtgauge_admin_preview');
|
||||
}
|
||||
navigate('/login', { replace: true });
|
||||
};
|
||||
|
||||
const initials = () => adminName().charAt(0).toUpperCase() || 'A';
|
||||
|
||||
return (
|
||||
<div class="admin-root">
|
||||
<header class="admin-header">
|
||||
<div class="admin-header-left">
|
||||
<div class="admin-brand">
|
||||
<img src="/nxtgauge-logo.png" alt="NXTGAUGE" />
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
{/* ── Fixed Header ── */}
|
||||
<header class="fixed top-0 z-50 flex h-16 w-full items-center justify-between border-b border-gray-200 bg-white px-6 shadow-sm">
|
||||
{/* Left: logo + page title */}
|
||||
<div class="flex items-center gap-8">
|
||||
<div class="flex h-10 items-center">
|
||||
<img src="/nxtgauge-logo.png" alt="NXTGAUGE" class="h-8 w-auto object-contain" />
|
||||
</div>
|
||||
<h1 class="admin-page-heading">{pageTitle()}</h1>
|
||||
<h1 class="ml-28 text-base font-semibold text-gray-800">{pageTitle()}</h1>
|
||||
</div>
|
||||
<div class="admin-header-actions">
|
||||
<button class="admin-notification-btn" type="button" aria-label="Notifications">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.73 21a2 2 0 0 1-3.46 0" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="admin-avatar-btn" type="button" aria-label="Admin profile">
|
||||
<span class="admin-avatar">A</span>
|
||||
<span class="admin-avatar-meta">
|
||||
<span class="admin-avatar-name">Admin</span>
|
||||
<span class="admin-avatar-role">Super Admin</span>
|
||||
</span>
|
||||
</button>
|
||||
<button class="admin-logout-btn" type="button" onClick={onLogout} aria-label="Logout">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
||||
|
||||
{/* Right: notifications + avatar + logout */}
|
||||
<div class="flex items-center gap-6">
|
||||
{/* Notification bell */}
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Notifications"
|
||||
class="relative rounded-full p-2 text-gray-500 transition-colors hover:bg-gray-100"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.73 21a2 2 0 0 1-3.46 0" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Avatar + name */}
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-3 rounded-lg p-1 pr-2 transition-colors hover:bg-gray-100"
|
||||
>
|
||||
<div class="flex h-8 w-8 items-center justify-center overflow-hidden rounded-full border border-orange-200 bg-orange-100 text-sm font-semibold text-orange-700">
|
||||
{initials()}
|
||||
</div>
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="mb-0.5 text-xs font-semibold leading-none text-gray-700">{adminName()}</span>
|
||||
<span class="text-[10px] leading-none text-gray-500">{adminRole()}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Logout */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onLogout}
|
||||
aria-label="Logout"
|
||||
class="rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* ── Body: sidebar + main (fixed, below header) ── */}
|
||||
{checkedSession() ? (
|
||||
<div class={`shell${sidebarCollapsed() ? ' sidebar-collapsed' : ''}`}>
|
||||
<aside class="sidebar-wrap">
|
||||
<AdminSidebar />
|
||||
</aside>
|
||||
<main class="main">
|
||||
{tabs().length > 0 ? (
|
||||
<div class="admin-tab-wrap">
|
||||
<nav class="admin-tabs">
|
||||
{tabs().map((tab) => (
|
||||
<A href={tab.href} class={`admin-tab ${isTabActive(tab) ? 'active' : ''}`}>
|
||||
{tab.label}
|
||||
</A>
|
||||
))}
|
||||
</nav>
|
||||
<div class="fixed inset-0 top-16 grid grid-cols-[auto_1fr]">
|
||||
{/* Sidebar */}
|
||||
<AdminSidebar />
|
||||
|
||||
{/* Main content */}
|
||||
<main class="scrollbar min-w-0 overflow-y-auto bg-gray-50 p-6">
|
||||
{/* Sub-tabs (shown for multi-tab sections) */}
|
||||
{tabs().length > 0 && (
|
||||
<div class="mb-6 flex gap-6 border-b border-gray-200">
|
||||
{tabs().map((tab) => (
|
||||
<A
|
||||
href={tab.href}
|
||||
class={`pb-3 text-sm font-medium transition-colors ${
|
||||
isTabActive(tab)
|
||||
? 'border-b-2 border-[#fd6216] text-gray-900'
|
||||
: 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</A>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<div class="main-inner">{props.children}</div>
|
||||
)}
|
||||
{props.children}
|
||||
</main>
|
||||
</div>
|
||||
) : (
|
||||
<div class="shell">
|
||||
<main class="main">
|
||||
<div class="card">
|
||||
<p class="notice">Checking session...</p>
|
||||
</div>
|
||||
/* Session check loading state */
|
||||
<div class="fixed inset-0 top-16 grid grid-cols-[auto_1fr]">
|
||||
<div class="w-64 border-r border-slate-200 bg-[#fcfcfd]" />
|
||||
<main class="flex items-center justify-center bg-gray-50">
|
||||
<p class="text-sm text-gray-400">Checking session…</p>
|
||||
</main>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,94 +1,140 @@
|
|||
import { A, useLocation } from '@solidjs/router';
|
||||
import { sidebarCollapsed, toggleSidebar } from '~/lib/sidebar-state';
|
||||
import { createSignal } from 'solid-js';
|
||||
|
||||
type LinkItem = { legacyHref: string; href: string; label: string; icon: string; aliasPrefix?: string };
|
||||
type LinkItem = {
|
||||
href: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
aliasPrefix?: string;
|
||||
};
|
||||
|
||||
const links: LinkItem[] = [
|
||||
{ legacyHref: '/', href: '/admin', label: 'Dashboard', icon: 'dashboard.svg' },
|
||||
{ legacyHref: '/roles?scope=internal', href: '/admin/roles', label: 'Internal Role Management', icon: 'role.svg' },
|
||||
{ legacyHref: '/runtime-roles', href: '/admin/runtime-roles', label: 'External Role Management', icon: 'role.svg' },
|
||||
{ legacyHref: '/onboarding-management', href: '/admin/onboarding-schemas', label: 'External Onboarding Management', icon: 'reviews.svg' },
|
||||
{ legacyHref: '/internal-dashboard-management', href: '/admin/internal-dashboard-management', label: 'Internal Dashboard Management', icon: 'dashboard.svg' },
|
||||
{ legacyHref: '/external-dashboard-management', href: '/admin/external-dashboard-management', label: 'External Dashboard Management', icon: 'dashboard.svg', aliasPrefix: '/admin/role-ui-configs' },
|
||||
{ legacyHref: '/approval', href: '/admin/approval', label: 'Approval Management', icon: 'approval.svg' },
|
||||
{ legacyHref: '/department', href: '/admin/department', label: 'Department Management', icon: 'department.svg' },
|
||||
{ legacyHref: '/designation', href: '/admin/designation', label: 'Designation Management', icon: 'designation.svg' },
|
||||
{ legacyHref: '/employees', href: '/admin/employees', label: 'Employee Management', icon: 'users.svg' },
|
||||
{ legacyHref: '/users', href: '/admin/users', label: 'Users Management', icon: 'users.svg' },
|
||||
{ legacyHref: '/company', href: '/admin/company', label: 'Company Management', icon: 'company.svg' },
|
||||
{ legacyHref: '/candidate', href: '/admin/candidate', label: 'Candidate Management', icon: 'candidate.svg' },
|
||||
{ legacyHref: '/customer', href: '/admin/customer', label: 'Customer Management', icon: 'users.svg' },
|
||||
{ legacyHref: '/photographer', href: '/admin/photographer', label: 'Photographer Management', icon: 'photographer.svg' },
|
||||
{ legacyHref: '/makeup-artist', href: '/admin/makeup-artist', label: 'Makeup Artist Management', icon: 'makeup-artist.svg' },
|
||||
{ legacyHref: '/tutors', href: '/admin/tutors', label: 'Tutors Management', icon: 'tutor.svg' },
|
||||
{ legacyHref: '/developers', href: '/admin/developers', label: 'Developers Management', icon: 'developers.svg' },
|
||||
{ legacyHref: '/video-editors', href: '/admin/video-editors', label: 'Video Editor Management', icon: 'developers.svg' },
|
||||
{ legacyHref: '/fitness-trainers', href: '/admin/fitness-trainers', label: 'Fitness Trainer Management', icon: 'tutor.svg' },
|
||||
{ legacyHref: '/catering-services', href: '/admin/catering-services', label: 'Catering Services Management', icon: 'company.svg' },
|
||||
{ legacyHref: '/graphic-designers', href: '/admin/graphic-designers', label: 'Graphics Designer Management', icon: 'developers.svg' },
|
||||
{ legacyHref: '/social-media-managers', href: '/admin/social-media-managers', label: 'Social Media Manager Management', icon: 'developers.svg' },
|
||||
{ legacyHref: '/jobs', href: '/admin/jobs', label: 'Jobs Management', icon: 'jobs.svg' },
|
||||
{ legacyHref: '/leads', href: '/admin/leads', label: 'Leads Management', icon: 'leads.svg' },
|
||||
{ legacyHref: '/pricing', href: '/admin/pricing', label: 'Pricing Management', icon: 'pricing.svg' },
|
||||
{ legacyHref: '/credit', href: '/admin/credit', label: 'Credit Management', icon: 'credits.svg' },
|
||||
{ legacyHref: '/coupon', href: '/admin/coupon', label: 'Coupon Management', icon: 'coupon.svg' },
|
||||
{ legacyHref: '/discount', href: '/admin/discount', label: 'Discount Management', icon: 'discount.svg' },
|
||||
{ legacyHref: '/tax', href: '/admin/tax', label: 'Tax Management', icon: 'tax.svg' },
|
||||
{ legacyHref: '/order', href: '/admin/order', label: 'Order Management', icon: 'order.svg' },
|
||||
{ legacyHref: '/invoice', href: '/admin/invoice', label: 'Invoice Management', icon: 'invoice.svg' },
|
||||
{ legacyHref: '/review', href: '/admin/review', label: 'Review Management', icon: 'reviews.svg' },
|
||||
{ legacyHref: '/help', href: '/admin/support', label: 'Support Management', icon: 'support.svg' },
|
||||
{ legacyHref: '/report', href: '/admin/report', label: 'Report Management', icon: 'report.svg' },
|
||||
{ legacyHref: '/ledger', href: '/admin/ledger', label: 'Ledger Management', icon: 'ledger.svg' },
|
||||
{ legacyHref: '/kb', href: '/admin/kb', label: 'KB Management', icon: 'reviews.svg' },
|
||||
{ legacyHref: '/notifications', href: '/admin/notifications', label: 'Notifications', icon: 'reviews.svg' },
|
||||
{ href: '/admin', label: 'Dashboard', icon: 'dashboard.svg' },
|
||||
{ href: '/admin/department', label: 'Department Management', icon: 'department.svg' },
|
||||
{ href: '/admin/designation', label: 'Designation Management', icon: 'designation.svg' },
|
||||
{ href: '/admin/employees', label: 'Employee Management', icon: 'users.svg' },
|
||||
{ href: '/admin/roles', label: 'Internal Role Management', icon: 'role.svg' },
|
||||
{ href: '/admin/runtime-roles', label: 'External Role Management', icon: 'role.svg' },
|
||||
{ href: '/admin/onboarding-schemas', label: 'External Onboarding Management', icon: 'reviews.svg' },
|
||||
{ href: '/admin/internal-dashboard-management', label: 'Internal Dashboard Management', icon: 'dashboard.svg' },
|
||||
{ href: '/admin/external-dashboard-management', label: 'External Dashboard Management', icon: 'dashboard.svg', aliasPrefix: '/admin/role-ui-configs' },
|
||||
{ href: '/admin/approval', label: 'Approval Management', icon: 'approval.svg' },
|
||||
{ href: '/admin/users', label: 'Users Management', icon: 'users.svg' },
|
||||
{ href: '/admin/company', label: 'Company Management', icon: 'company.svg' },
|
||||
{ href: '/admin/candidate', label: 'Candidate Management', icon: 'candidate.svg' },
|
||||
{ href: '/admin/customer', label: 'Customer Management', icon: 'users.svg' },
|
||||
{ href: '/admin/photographer', label: 'Photographer Management', icon: 'photographer.svg' },
|
||||
{ href: '/admin/makeup-artist', label: 'Makeup Artist Management', icon: 'makeup-artist.svg' },
|
||||
{ href: '/admin/tutors', label: 'Tutors Management', icon: 'tutor.svg' },
|
||||
{ href: '/admin/developers', label: 'Developers Management', icon: 'developers.svg' },
|
||||
{ href: '/admin/video-editors', label: 'Video Editor Management', icon: 'developers.svg' },
|
||||
{ href: '/admin/fitness-trainers', label: 'Fitness Trainer Management', icon: 'tutor.svg' },
|
||||
{ href: '/admin/catering-services', label: 'Catering Services Management', icon: 'company.svg' },
|
||||
{ href: '/admin/graphic-designers', label: 'Graphics Designer Management', icon: 'developers.svg' },
|
||||
{ href: '/admin/social-media-managers', label: 'Social Media Manager Management', icon: 'developers.svg' },
|
||||
{ href: '/admin/jobs', label: 'Jobs Management', icon: 'jobs.svg' },
|
||||
{ href: '/admin/leads', label: 'Leads Management', icon: 'leads.svg' },
|
||||
{ href: '/admin/pricing', label: 'Pricing Management', icon: 'pricing.svg' },
|
||||
{ href: '/admin/credit', label: 'Credit Management', icon: 'credits.svg' },
|
||||
{ href: '/admin/coupon', label: 'Coupon Management', icon: 'coupon.svg' },
|
||||
{ href: '/admin/discount', label: 'Discount Management', icon: 'discount.svg' },
|
||||
{ href: '/admin/tax', label: 'Tax Management', icon: 'tax.svg' },
|
||||
{ href: '/admin/order', label: 'Order Management', icon: 'order.svg' },
|
||||
{ href: '/admin/invoice', label: 'Invoice Management', icon: 'invoice.svg' },
|
||||
{ href: '/admin/review', label: 'Review Management', icon: 'reviews.svg' },
|
||||
{ href: '/admin/support', label: 'Support Management', icon: 'support.svg' },
|
||||
{ href: '/admin/kb', label: 'Knowledge Base Management', icon: 'reviews.svg' },
|
||||
{ href: '/admin/notifications', label: 'Notifications', icon: 'reviews.svg' },
|
||||
{ href: '/admin/report', label: 'Report Management', icon: 'report.svg' },
|
||||
{ href: '/admin/ledger', label: 'Ledger Management', icon: 'ledger.svg' },
|
||||
];
|
||||
|
||||
export default function AdminSidebar() {
|
||||
const location = useLocation();
|
||||
const collapsed = sidebarCollapsed;
|
||||
const [collapsed, setCollapsed] = createSignal(false);
|
||||
|
||||
const isLinkActive = (href: string, aliasPrefix?: string) => {
|
||||
const pathOnly = href.split('?')[0] || href;
|
||||
if (pathOnly === '/admin') return location.pathname === '/admin';
|
||||
const isActive = (href: string, aliasPrefix?: string) => {
|
||||
if (href === '/admin') return location.pathname === '/admin';
|
||||
if (aliasPrefix && location.pathname.startsWith(aliasPrefix)) return true;
|
||||
return location.pathname === pathOnly || location.pathname.startsWith(`${pathOnly}/`);
|
||||
return location.pathname === href || location.pathname.startsWith(`${href}/`);
|
||||
};
|
||||
|
||||
return (
|
||||
<aside class={`sidebar${collapsed() ? ' sidebar-collapsed' : ''}`}>
|
||||
<div class="sidebar-toggle-row">
|
||||
<aside
|
||||
class={`flex h-full flex-col border-r border-slate-200 bg-[#fcfcfd] px-3 pb-3 pt-5 transition-all duration-300 ${
|
||||
collapsed() ? 'w-20' : 'w-64'
|
||||
}`}
|
||||
>
|
||||
{/* Collapse toggle */}
|
||||
<div class="mb-4 flex justify-end px-2">
|
||||
<button
|
||||
type="button"
|
||||
class="sidebar-toggle-btn"
|
||||
onClick={() => setCollapsed((v) => !v)}
|
||||
aria-label={collapsed() ? 'Expand sidebar' : 'Collapse sidebar'}
|
||||
onClick={toggleSidebar}
|
||||
class="rounded-lg p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700"
|
||||
>
|
||||
<span class={`sidebar-chevron${collapsed() ? ' collapsed' : ''}`}>‹</span>
|
||||
<svg
|
||||
class={`h-4 w-4 transition-transform ${collapsed() ? 'rotate-180' : ''}`}
|
||||
fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<nav class="sidebar-nav scrollbar">
|
||||
|
||||
{/* Nav items */}
|
||||
<nav class="scrollbar min-h-0 flex-1 space-y-1.5 overflow-y-auto pr-1">
|
||||
{links.map((item) => {
|
||||
const active = isLinkActive(item.href, item.aliasPrefix);
|
||||
const active = isActive(item.href, item.aliasPrefix);
|
||||
return (
|
||||
<A
|
||||
href={item.href}
|
||||
class={`nav-item ${active ? 'active' : ''}`}
|
||||
activeClass=""
|
||||
inactiveClass=""
|
||||
data-legacy-href={item.legacyHref}
|
||||
title={collapsed() ? item.label : undefined}
|
||||
class={`group relative flex items-center gap-3 rounded-xl border px-3 py-3 text-[15px] leading-5 transition-all ${
|
||||
collapsed() ? 'justify-center px-2' : ''
|
||||
} ${
|
||||
active
|
||||
? 'border-orange-200 bg-gradient-to-r from-orange-50 to-orange-100/70 text-slate-900 shadow-[inset_3px_0_0_0_#FD6216]'
|
||||
: 'border-transparent text-slate-500 hover:border-slate-200 hover:bg-white hover:text-slate-800'
|
||||
}`}
|
||||
>
|
||||
<span class="nav-active-rail" />
|
||||
<img class="nav-icon" src={`/sidebar-icons/${item.icon}`} alt="" />
|
||||
{/* Active left rail */}
|
||||
<span
|
||||
class={`absolute bottom-2 left-0 top-2 w-[3px] rounded-r-full bg-orange-500 transition-opacity ${
|
||||
active ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Icon */}
|
||||
<img
|
||||
src={`/sidebar-icons/${item.icon}`}
|
||||
alt=""
|
||||
class="h-[18px] w-[18px] shrink-0 transition-all"
|
||||
style={active
|
||||
? 'filter: invert(42%) sepia(78%) saturate(1200%) hue-rotate(360deg) brightness(95%) contrast(95%); opacity:1'
|
||||
: 'opacity:0.45'
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Label */}
|
||||
{!collapsed() && (
|
||||
<span class="nav-title">{item.label}</span>
|
||||
<span class={`truncate font-medium ${active ? 'text-slate-900' : 'text-slate-600 group-hover:text-slate-800'}`}>
|
||||
{item.label}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Active badge (expanded) */}
|
||||
{!collapsed() && active && (
|
||||
<span class="active-badge">Active</span>
|
||||
<span class="ml-auto rounded-full border border-orange-200 bg-orange-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-orange-700">
|
||||
Active
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Active dot (collapsed) */}
|
||||
{collapsed() && active && (
|
||||
<span class="collapsed-dot" />
|
||||
<span class="absolute -right-0.5 top-1/2 h-2 w-2 -translate-y-1/2 rounded-full bg-orange-500" />
|
||||
)}
|
||||
</A>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -47,13 +47,13 @@ export default function LegacyModuleShellPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<h1 class="page-title">{moduleName()}</h1>
|
||||
<p class="page-subtitle">
|
||||
<h1 class="text-2xl font-bold text-gray-900">{moduleName()}</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Live legacy module embedded for exact design and functionality parity during migration.
|
||||
</p>
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="actions">
|
||||
<A class="btn" href={legacyUrl()} target="_blank">Open Module In New Tab</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={legacyUrl()} target="_blank">Open Module In New Tab</A>
|
||||
</div>
|
||||
<iframe
|
||||
src={legacyUrl()}
|
||||
|
|
|
|||
|
|
@ -84,20 +84,20 @@ export default function ApplicationsPage() {
|
|||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card">
|
||||
<h1 class="page-title">Applications</h1>
|
||||
<p class="page-subtitle">Review submitted applications and update acceptance status.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Applications</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review submitted applications and update acceptance status.</p>
|
||||
</div>
|
||||
|
||||
<Show when={payload.loading}>
|
||||
<div class="card"><p class="notice">Loading applications...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading applications...</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={error()}>
|
||||
<div class="card"><p class="error-note">{error()}</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="error-note">{error()}</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={!payload.loading && applications().length === 0}>
|
||||
<div class="card"><p class="notice">No applications found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">No applications found.</p></div>
|
||||
</Show>
|
||||
|
||||
<div class="list-grid" style="grid-template-columns:1fr;gap:14px">
|
||||
|
|
@ -105,7 +105,7 @@ export default function ApplicationsPage() {
|
|||
{(app) => {
|
||||
const req = createMemo(() => requirementFor(app.requirementId));
|
||||
return (
|
||||
<article class="card">
|
||||
<article class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start;flex-wrap:wrap">
|
||||
<div>
|
||||
<h2 style="margin:0;font-size:19px">{req()?.title || 'Unknown Requirement'}</h2>
|
||||
|
|
@ -119,7 +119,7 @@ export default function ApplicationsPage() {
|
|||
<p class="kv-value" style="font-weight:500">{app.message || 'No message provided.'}</p>
|
||||
</div>
|
||||
|
||||
<div class="field-grid-2" style="margin-top:12px">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2" style="margin-top:12px">
|
||||
<div class="kv-item">
|
||||
<p class="kv-label">Quote</p>
|
||||
<p class="kv-value">₹ {app.quote || 0}</p>
|
||||
|
|
@ -140,15 +140,15 @@ export default function ApplicationsPage() {
|
|||
|
||||
<div class="actions">
|
||||
<Show when={app.status === 'SUBMITTED'}>
|
||||
<button class="btn primary" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'ACCEPTED')}>Accept Bid</button>
|
||||
<button class="btn danger" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'REJECTED')}>Decline</button>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'ACCEPTED')}>Accept Bid</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'REJECTED')}>Decline</button>
|
||||
</Show>
|
||||
<Show when={app.status === 'SHORTLISTED'}>
|
||||
<button class="btn primary" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'ACCEPTED')}>Accept Bid</button>
|
||||
<button class="btn danger" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'REJECTED')}>Decline</button>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'ACCEPTED')}>Accept Bid</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'REJECTED')}>Decline</button>
|
||||
</Show>
|
||||
<Show when={app.status === 'SUBMITTED' || app.status === 'SHORTLISTED'}>
|
||||
<button class="btn" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'WITHDRAWN')}>Withdraw</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'WITHDRAWN')}>Withdraw</button>
|
||||
</Show>
|
||||
</div>
|
||||
</article>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default function ApprovalManagementAliasPage() {
|
|||
onMount(() => navigate('/admin/approval', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<p class="notice">Redirecting to Approval Management...</p>
|
||||
</div>
|
||||
</AdminShell>
|
||||
|
|
|
|||
|
|
@ -254,11 +254,11 @@ function RoleTypeBadge(props: { type?: RoleType }) {
|
|||
function StatusBadge(props: { status: string; isDocRequest?: boolean }) {
|
||||
const s = props.status.toUpperCase();
|
||||
const label = props.isDocRequest ? 'MORE DOCS REQUIRED' : s.replace(/_/g, ' ');
|
||||
if (s === 'APPROVED') return <span class="status-chip active">{label}</span>;
|
||||
if (s === 'REJECTED') return <span class="status-chip" style="background:#ef4444;color:#fff;border-color:#ef4444">{label}</span>;
|
||||
if (s === 'CHANGES_REQUESTED') return <span class="status-chip" style={`background:${props.isDocRequest ? '#3b82f6' : '#f97316'};color:#fff;border-color:currentColor`}>{label}</span>;
|
||||
if (s === 'CANCELLED') return <span class="status-chip" style="background:#94a3b8;color:#fff;border-color:#94a3b8">{label}</span>;
|
||||
return <span class="status-chip" style="background:#f59e0b;color:#fff;border-color:#f59e0b">{label}</span>;
|
||||
if (s === 'APPROVED') return <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">{label}</span>;
|
||||
if (s === 'REJECTED') return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#ef4444;color:#fff;border-color:#ef4444">{label}</span>;
|
||||
if (s === 'CHANGES_REQUESTED') return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style={`background:${props.isDocRequest ? '#3b82f6' : '#f97316'};color:#fff;border-color:currentColor`}>{label}</span>;
|
||||
if (s === 'CANCELLED') return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#94a3b8;color:#fff;border-color:#94a3b8">{label}</span>;
|
||||
return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#f59e0b;color:#fff;border-color:#f59e0b">{label}</span>;
|
||||
}
|
||||
|
||||
// ────────────────────────────── API calls ──────────────────────────────
|
||||
|
|
@ -676,10 +676,10 @@ export default function ApprovalPage() {
|
|||
return (
|
||||
<AdminShell>
|
||||
{/* Page header */}
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Approval Management</h1>
|
||||
<p class="page-subtitle">Review, approve, reject and configure approval workflows.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Approval Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review, approve, reject and configure approval workflows.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -713,22 +713,22 @@ export default function ApprovalPage() {
|
|||
</div>
|
||||
|
||||
<Show when={actionError()}>
|
||||
<div class="error-box" style="margin-bottom:12px">{actionError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
|
||||
</Show>
|
||||
<Show when={approvals.error}>
|
||||
<div class="error-box" style="margin-bottom:12px">{String((approvals.error as any)?.message || approvals.error)}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{String((approvals.error as any)?.message || approvals.error)}</div>
|
||||
</Show>
|
||||
|
||||
<Show when={!approvals.loading && !snapshot.loading}>
|
||||
<div class="card" style="margin-bottom:12px;background:#f8fafc;border-color:#e2e8f0">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:12px;background:#f8fafc;border-color:#e2e8f0">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap">
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
<span class="status-chip" style="background:#e0f2fe;color:#075985;border-color:#bae6fd">Pending Jobs: {summary().jobs}</span>
|
||||
<span class="status-chip" style="background:#ecfeff;color:#155e75;border-color:#a5f3fc">Pending Requirements: {summary().requirements}</span>
|
||||
<span class="status-chip" style="background:#eef2ff;color:#3730a3;border-color:#c7d2fe">Pending Profiles: {summary().profilePending}</span>
|
||||
<span class="status-chip" style="background:#fef3c7;color:#92400e;border-color:#fde68a">Total Pending: {summary().totalPending}</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#e0f2fe;color:#075985;border-color:#bae6fd">Pending Jobs: {summary().jobs}</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#ecfeff;color:#155e75;border-color:#a5f3fc">Pending Requirements: {summary().requirements}</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#eef2ff;color:#3730a3;border-color:#c7d2fe">Pending Profiles: {summary().profilePending}</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fef3c7;color:#92400e;border-color:#fde68a">Total Pending: {summary().totalPending}</span>
|
||||
</div>
|
||||
<button class="btn" type="button" onClick={() => { refetchApprovals(); refetchSnapshot(); }}>Refresh</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={() => { refetchApprovals(); refetchSnapshot(); }}>Refresh</button>
|
||||
</div>
|
||||
<Show when={summary().totalPending === 0}>
|
||||
<p style="margin:10px 0 0;color:#64748b;font-size:13px">
|
||||
|
|
@ -747,7 +747,7 @@ export default function ApprovalPage() {
|
|||
<Show when={activeTab() !== 'rules'}>
|
||||
<Show when={!showDetail()}>
|
||||
{/* Filter bar */}
|
||||
<div class="card" style="margin-bottom:12px;display:flex;gap:10px;flex-wrap:wrap;align-items:center">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:12px;display:flex;gap:10px;flex-wrap:wrap;align-items:center">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search requester, email, type or ID..."
|
||||
|
|
@ -773,9 +773,9 @@ export default function ApprovalPage() {
|
|||
<span style="font-size:12px;color:#64748b;margin-left:auto">{filteredApprovals().length} record{filteredApprovals().length !== 1 ? 's' : ''}</span>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Requester</th>
|
||||
|
|
@ -784,7 +784,7 @@ export default function ApprovalPage() {
|
|||
<th>Document Request</th>
|
||||
<th>Status</th>
|
||||
<th>Submitted</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -837,10 +837,10 @@ export default function ApprovalPage() {
|
|||
{(item.createdAt || item.created_at) ? new Date((item.createdAt || item.created_at)!).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
{/* View detail */}
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
type="button"
|
||||
title="View Request"
|
||||
style="font-size:12px;padding:4px 10px"
|
||||
|
|
@ -849,12 +849,12 @@ export default function ApprovalPage() {
|
|||
View
|
||||
</button>
|
||||
<Show when={item._viewHref}>
|
||||
<A class="btn" href={item._viewHref!} style="font-size:12px;padding:4px 10px" title="Open full request page">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={item._viewHref!} style="font-size:12px;padding:4px 10px" title="Open full request page">
|
||||
Full Request
|
||||
</A>
|
||||
</Show>
|
||||
<Show when={item._supportsSubmissionView}>
|
||||
<A class="btn" href={`/admin/approval/${item.id}`} style="font-size:12px;padding:4px 10px" title="Open full profile review">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/approval/${item.id}`} style="font-size:12px;padding:4px 10px" title="Open full profile review">
|
||||
Profile Review
|
||||
</A>
|
||||
</Show>
|
||||
|
|
@ -862,7 +862,7 @@ export default function ApprovalPage() {
|
|||
<Show when={status === 'PENDING'}>
|
||||
{/* Request More Documents */}
|
||||
<button
|
||||
class="action-icon-btn"
|
||||
class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm"
|
||||
type="button"
|
||||
title="Request More Documents"
|
||||
disabled={isActing}
|
||||
|
|
@ -873,7 +873,7 @@ export default function ApprovalPage() {
|
|||
</button>
|
||||
{/* Request Changes */}
|
||||
<button
|
||||
class="action-icon-btn"
|
||||
class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm"
|
||||
type="button"
|
||||
title="Request Changes"
|
||||
disabled={isActing}
|
||||
|
|
@ -884,7 +884,7 @@ export default function ApprovalPage() {
|
|||
</button>
|
||||
{/* Approve */}
|
||||
<button
|
||||
class="action-icon-btn"
|
||||
class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm"
|
||||
type="button"
|
||||
title="Approve"
|
||||
disabled={isActing}
|
||||
|
|
@ -895,7 +895,7 @@ export default function ApprovalPage() {
|
|||
</button>
|
||||
{/* Reject */}
|
||||
<button
|
||||
class="action-icon-btn danger"
|
||||
class="rounded p-1.5 text-red-500 hover:bg-red-50 hover:text-red-700 text-sm"
|
||||
type="button"
|
||||
title="Reject"
|
||||
disabled={isActing}
|
||||
|
|
@ -906,7 +906,7 @@ export default function ApprovalPage() {
|
|||
</Show>
|
||||
<Show when={status === 'CHANGES_REQUESTED'}>
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
type="button"
|
||||
title="Update requested documents"
|
||||
style="font-size:12px;padding:4px 10px;background:#eff6ff;color:#1d4ed8;border-color:#bfdbfe"
|
||||
|
|
@ -921,7 +921,7 @@ export default function ApprovalPage() {
|
|||
<Show when={status === 'APPROVED'}>
|
||||
<A
|
||||
href={dest.href}
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
style="font-size:11px;padding:3px 8px;background:#f0fdf4;color:#15803d;border-color:#bbf7d0;white-space:nowrap"
|
||||
title={`Open ${dest.label}`}
|
||||
>
|
||||
|
|
@ -939,9 +939,9 @@ export default function ApprovalPage() {
|
|||
</table>
|
||||
</div>
|
||||
<div class="admin-pagination">
|
||||
<button class="btn" type="button" disabled={currentPage() <= 1} onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}>← Prev</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" disabled={currentPage() <= 1} onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}>← Prev</button>
|
||||
<span>Page {currentPage()} of {totalPages()}</span>
|
||||
<button class="btn" type="button" disabled={currentPage() >= totalPages()} onClick={() => setCurrentPage((p) => Math.min(totalPages(), p + 1))}>Next →</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" disabled={currentPage() >= totalPages()} onClick={() => setCurrentPage((p) => Math.min(totalPages(), p + 1))}>Next →</button>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
|
|
@ -963,20 +963,20 @@ export default function ApprovalPage() {
|
|||
{/* ── Rules Tab ── */}
|
||||
<Show when={activeTab() === 'rules'}>
|
||||
<Show when={ruleError()}>
|
||||
<div class="error-box" style="margin-bottom:12px">{ruleError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{ruleError()}</div>
|
||||
</Show>
|
||||
|
||||
<div class="page-actions" style="margin-bottom:16px">
|
||||
<div class="mb-6 flex items-start justify-between gap-4" style="margin-bottom:16px">
|
||||
<div />
|
||||
<button class="btn navy" onClick={() => setShowAddRule((v) => !v)}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={() => setShowAddRule((v) => !v)}>
|
||||
{showAddRule() ? 'Cancel' : '+ Add Rule'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Show when={showAddRule()}>
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
|
||||
<h3 style="font-size:15px;font-weight:600;color:#0f172a;margin:0 0 14px">New Approval Rule</h3>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Rule Name <span style="color:#ef4444">*</span></label>
|
||||
<input
|
||||
|
|
@ -1003,24 +1003,24 @@ export default function ApprovalPage() {
|
|||
</div>
|
||||
</div>
|
||||
<div class="actions" style="margin-top:14px">
|
||||
<button class="btn navy" disabled={submittingRule()} onClick={handleAddRule}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={submittingRule()} onClick={handleAddRule}>
|
||||
{submittingRule() ? 'Saving...' : 'Save Rule'}
|
||||
</button>
|
||||
<button class="btn" onClick={() => setShowAddRule(false)}>Cancel</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => setShowAddRule(false)}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rule Name</th>
|
||||
<th>Entity Type</th>
|
||||
<th>Approver Type</th>
|
||||
<th>Priority</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -1039,9 +1039,9 @@ export default function ApprovalPage() {
|
|||
<td style="color:#475569">{rule.approverType || rule.approver_type || '—'}</td>
|
||||
<td style="color:#475569">{rule.priority ?? '—'}</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button
|
||||
class="btn danger"
|
||||
class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
|
||||
disabled={deletingRule() === rule.id}
|
||||
onClick={() => handleDeleteRule(rule.id)}
|
||||
>
|
||||
|
|
@ -1083,19 +1083,19 @@ function ApprovalDetailPanel(props: {
|
|||
|
||||
return (
|
||||
<Show when={a()} fallback={
|
||||
<div class="card"><p class="notice">Select an approval from the list.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Select an approval from the list.</p></div>
|
||||
}>
|
||||
<div class="page-hero-card page-actions" style="margin-bottom:12px">
|
||||
<div class="mb-6 flex items-start justify-between gap-4" style="margin-bottom:12px">
|
||||
<div>
|
||||
<h2 class="page-title" style="font-size:18px">Approval Detail</h2>
|
||||
<p class="page-subtitle">{a()!._typeLabel || 'Request'}</p>
|
||||
<h2 class="text-2xl font-bold text-gray-900" style="font-size:18px">Approval Detail</h2>
|
||||
<p class="mt-1 text-sm text-gray-500">{a()!._typeLabel || 'Request'}</p>
|
||||
</div>
|
||||
<button class="btn" type="button" onClick={props.onBack}>← Back to List</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={props.onBack}>← Back to List</button>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px">
|
||||
{/* Request info */}
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Request Summary</h3>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px">
|
||||
<tbody>
|
||||
|
|
@ -1110,7 +1110,7 @@ function ApprovalDetailPanel(props: {
|
|||
</div>
|
||||
|
||||
{/* Requester info */}
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Requester</h3>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px">
|
||||
<tbody>
|
||||
|
|
@ -1124,21 +1124,21 @@ function ApprovalDetailPanel(props: {
|
|||
{/* Actions */}
|
||||
<div style="margin-top:16px;display:flex;flex-wrap:wrap;gap:8px">
|
||||
<Show when={isPending()}>
|
||||
<button class="btn" style="background:#eff6ff;color:#1d4ed8;border-color:#bfdbfe;font-size:13px" disabled={isActing()} onClick={() => props.onRequestMoreDocuments(a()!.id)}>📄 Request Documents</button>
|
||||
<button class="btn" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;font-size:13px" disabled={isActing()} onClick={() => props.onRequestChanges(a()!.id)}>✎ Request Changes</button>
|
||||
<button class="btn" style="background:#f0fdf4;color:#15803d;border-color:#bbf7d0;font-size:13px" disabled={isActing()} onClick={() => props.onApprove(a()!.id)}>✓ Approve</button>
|
||||
<button class="btn danger" style="font-size:13px" disabled={isActing()} onClick={() => props.onReject(a()!.id)}>✕ Reject</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" style="background:#eff6ff;color:#1d4ed8;border-color:#bfdbfe;font-size:13px" disabled={isActing()} onClick={() => props.onRequestMoreDocuments(a()!.id)}>📄 Request Documents</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;font-size:13px" disabled={isActing()} onClick={() => props.onRequestChanges(a()!.id)}>✎ Request Changes</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" style="background:#f0fdf4;color:#15803d;border-color:#bbf7d0;font-size:13px" disabled={isActing()} onClick={() => props.onApprove(a()!.id)}>✓ Approve</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" style="font-size:13px" disabled={isActing()} onClick={() => props.onReject(a()!.id)}>✕ Reject</button>
|
||||
</Show>
|
||||
<Show when={status() === 'APPROVED'}>
|
||||
<A href={dest().href} class="btn" style="background:#f0fdf4;color:#15803d;border-color:#bbf7d0;font-size:13px">
|
||||
<A href={dest().href} class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" style="background:#f0fdf4;color:#15803d;border-color:#bbf7d0;font-size:13px">
|
||||
Open {dest().label}
|
||||
</A>
|
||||
</Show>
|
||||
<Show when={a()!._viewHref}>
|
||||
<A href={a()!._viewHref!} class="btn" style="font-size:13px">Open Full Request</A>
|
||||
<A href={a()!._viewHref!} class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" style="font-size:13px">Open Full Request</A>
|
||||
</Show>
|
||||
<Show when={a()!._supportsSubmissionView}>
|
||||
<A href={`/admin/approval/${a()!.id}`} class="btn" style="font-size:13px">Open Profile Review</A>
|
||||
<A href={`/admin/approval/${a()!.id}`} class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" style="font-size:13px">Open Profile Review</A>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1146,7 +1146,7 @@ function ApprovalDetailPanel(props: {
|
|||
|
||||
{/* Submitted fields */}
|
||||
<Show when={a()!._parsedReason?.values && Object.keys(a()!._parsedReason!.values!).length > 0}>
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
|
||||
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Submitted Data</h3>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px">
|
||||
{Object.entries(a()!._parsedReason!.values!).map(([k, v]) => (
|
||||
|
|
@ -1161,7 +1161,7 @@ function ApprovalDetailPanel(props: {
|
|||
|
||||
{/* Document request spotlight (USP) */}
|
||||
<Show when={docRemark()}>
|
||||
<div class="card" style="margin-bottom:16px;border:1px solid #bfdbfe;background:#eff6ff">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px;border:1px solid #bfdbfe;background:#eff6ff">
|
||||
<h3 style="margin:0 0 10px;font-size:15px;font-weight:700;color:#1d4ed8">Requested Documents</h3>
|
||||
<p style="margin:0;font-size:13px;color:#0f172a">{docRemark()!.comment}</p>
|
||||
<Show when={(docRemark()!.fields || []).length > 0}>
|
||||
|
|
@ -1174,7 +1174,7 @@ function ApprovalDetailPanel(props: {
|
|||
|
||||
{/* Admin remarks history */}
|
||||
<Show when={remarks().length > 0}>
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Admin Remarks History</h3>
|
||||
<div style="display:flex;flex-direction:column;gap:8px">
|
||||
<For each={remarks()}>
|
||||
|
|
|
|||
|
|
@ -247,20 +247,20 @@ export default function ApprovalDetailPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Submission Review</h1>
|
||||
<p class="page-subtitle">Review a user's onboarding form submission and take action.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Submission Review</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review a user's onboarding form submission and take action.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/approval">← Back to Approvals</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval">← Back to Approvals</A>
|
||||
</div>
|
||||
|
||||
<Show when={actionError()}>
|
||||
<div class="error-box" style="margin-bottom:12px">{actionError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
|
||||
</Show>
|
||||
|
||||
<Show when={actionDone()}>
|
||||
<div class="card" style="margin-bottom:12px;border-left:4px solid #22c55e;padding:12px 16px">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:12px;border-left:4px solid #22c55e;padding:12px 16px">
|
||||
<p style="margin:0;font-weight:600;color:#166534">
|
||||
{actionDone() === 'APPROVED' ? '✓ Profile approved successfully.' : '✕ Profile rejected.'}
|
||||
</p>
|
||||
|
|
@ -271,11 +271,11 @@ export default function ApprovalDetailPage() {
|
|||
</Show>
|
||||
|
||||
<Show when={data.loading}>
|
||||
<div class="card"><p class="notice">Loading submission...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading submission...</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={!data.loading && !data()}>
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<p class="notice">Submission not found or user does not have an onboarding record for this role.</p>
|
||||
<p style="font-size:13px;color:#64748b;margin-top:8px">
|
||||
Make sure the URL includes <code>?roleKey=PHOTOGRAPHER</code> (or the relevant role key).
|
||||
|
|
@ -286,7 +286,7 @@ export default function ApprovalDetailPage() {
|
|||
<Show when={data()}>
|
||||
{/* ── Action bar ── */}
|
||||
<Show when={!actionDone()}>
|
||||
<div class="card" style="display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:16px;padding:12px 16px">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:16px;padding:12px 16px">
|
||||
<span style={`display:inline-block;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;${ROLE_COLORS[roleType()]}`}>
|
||||
{(roleKey() || 'UNKNOWN').replace(/_/g, ' ')}
|
||||
</span>
|
||||
|
|
@ -298,7 +298,7 @@ export default function ApprovalDetailPage() {
|
|||
<div style="flex:1" />
|
||||
<Show when={data()!.onboarding?.status === 'COMPLETED' && !actionDone()}>
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
style="background:#f0fdf4;color:#15803d;border-color:#bbf7d0"
|
||||
disabled={!!acting()}
|
||||
onClick={handleApprove}
|
||||
|
|
@ -306,7 +306,7 @@ export default function ApprovalDetailPage() {
|
|||
{acting() === 'APPROVE' ? 'Approving...' : '✓ Approve Profile'}
|
||||
</button>
|
||||
<button
|
||||
class="btn danger"
|
||||
class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
|
||||
disabled={!!acting()}
|
||||
onClick={handleReject}
|
||||
>
|
||||
|
|
@ -318,7 +318,7 @@ export default function ApprovalDetailPage() {
|
|||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px">
|
||||
{/* User info */}
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">User Info</h3>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px">
|
||||
<tbody>
|
||||
|
|
@ -328,7 +328,7 @@ export default function ApprovalDetailPage() {
|
|||
<tr>
|
||||
<td style="color:#64748b;padding:5px 10px 5px 0">Account Status</td>
|
||||
<td>
|
||||
<span class={`status-chip${data()!.user.status === 'ACTIVE' ? ' active' : ''}`}>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600${data()!.user.status === 'ACTIVE' ? ' active' : ''}`}>
|
||||
{data()!.user.status}
|
||||
</span>
|
||||
</td>
|
||||
|
|
@ -346,7 +346,7 @@ export default function ApprovalDetailPage() {
|
|||
</div>
|
||||
|
||||
{/* Submission status */}
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600;color:#0f172a">Submission Info</h3>
|
||||
<Show when={data()!.onboarding} fallback={
|
||||
<div style="background:#fef2f2;border:1px solid #fecaca;border-radius:8px;padding:14px">
|
||||
|
|
@ -383,7 +383,7 @@ export default function ApprovalDetailPage() {
|
|||
|
||||
{/* ── No form data fallback ── */}
|
||||
<Show when={data()!.onboarding && submittedRows().length === 0}>
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
|
||||
<h3 style="margin:0 0 10px;font-size:15px;font-weight:600;color:#0f172a">Submitted Form Answers</h3>
|
||||
<p class="notice">Onboarding state is present but contains no displayable field data.</p>
|
||||
</div>
|
||||
|
|
@ -417,7 +417,7 @@ function SubmissionViewer(props: { rows: Array<{ key: string; value: string }> }
|
|||
<>
|
||||
{/* ── Text / data fields ── */}
|
||||
<Show when={textFields().length > 0}>
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
|
||||
<h3 style="margin:0 0 14px;font-size:15px;font-weight:600;color:#0f172a">
|
||||
Submitted Form Data
|
||||
<span style="margin-left:8px;font-size:12px;font-weight:400;color:#64748b">{textFields().length} fields</span>
|
||||
|
|
@ -449,7 +449,7 @@ function SubmissionViewer(props: { rows: Array<{ key: string; value: string }> }
|
|||
|
||||
{/* ── Documents & Images ── */}
|
||||
<Show when={mediaFields().length > 0}>
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
|
||||
<h3 style="margin:0 0 14px;font-size:15px;font-weight:600;color:#0f172a">
|
||||
Documents & Media
|
||||
<span style="margin-left:8px;font-size:12px;font-weight:400;color:#64748b">{mediaFields().length} file{mediaFields().length !== 1 ? 's' : ''}</span>
|
||||
|
|
@ -595,7 +595,7 @@ function SubmissionViewer(props: { rows: Array<{ key: string; value: string }> }
|
|||
</a>
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
style="padding:4px 10px;font-size:13px"
|
||||
onClick={() => setPdfViewer(null)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default function ApprovalsAliasPage() {
|
|||
onMount(() => navigate('/admin/approval', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<p class="notice">Redirecting to Approval Management...</p>
|
||||
</div>
|
||||
</AdminShell>
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export default function CandidatePage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Candidate Management</h1>
|
||||
<p class="page-subtitle">Manage all job seeker accounts on the platform.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Candidate Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all job seeker accounts on the platform.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -64,15 +64,15 @@ export default function CandidatePage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Registered</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -93,22 +93,22 @@ export default function CandidatePage() {
|
|||
<td style="color:#475569">{item.email}</td>
|
||||
<td>
|
||||
{item.status?.toUpperCase() === 'ACTIVE' && (
|
||||
<span class="status-chip active">ACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'INACTIVE' && (
|
||||
<span class="status-chip">INACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'PENDING' && (
|
||||
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
)}
|
||||
{!item.status && <span class="status-chip">—</span>}
|
||||
{!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">—</span>}
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/users/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export default function CateringServicesPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Catering Services Management</h1>
|
||||
<p class="page-subtitle">Manage all catering services accounts on the platform.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Catering Services Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all catering services accounts on the platform.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -64,15 +64,15 @@ export default function CateringServicesPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Registered</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -93,22 +93,22 @@ export default function CateringServicesPage() {
|
|||
<td style="color:#475569">{item.email}</td>
|
||||
<td>
|
||||
{item.status?.toUpperCase() === 'ACTIVE' && (
|
||||
<span class="status-chip active">ACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'INACTIVE' && (
|
||||
<span class="status-chip">INACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'PENDING' && (
|
||||
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
)}
|
||||
{!item.status && <span class="status-chip">—</span>}
|
||||
{!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">—</span>}
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/users/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@ async function fetchCompanies(): Promise<Company[]> {
|
|||
|
||||
function StatusBadge(props: { status: string }) {
|
||||
if (props.status === 'ACTIVE') {
|
||||
return <span class="status-chip active">ACTIVE</span>;
|
||||
return <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>;
|
||||
}
|
||||
if (props.status === 'PENDING') {
|
||||
return <span class="status-chip" style="background:#f59e0b;color:#fff">PENDING</span>;
|
||||
return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#f59e0b;color:#fff">PENDING</span>;
|
||||
}
|
||||
return <span class="status-chip">{props.status}</span>;
|
||||
return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">{props.status}</span>;
|
||||
}
|
||||
|
||||
export default function CompanyPage() {
|
||||
|
|
@ -72,20 +72,20 @@ export default function CompanyPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Company Management</h1>
|
||||
<p class="page-subtitle">Manage all company accounts on the platform.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Company Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all company accounts on the platform.</p>
|
||||
</div>
|
||||
<A class="btn navy" href="/admin/company/create">Create Company</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/company/create">Create Company</A>
|
||||
</div>
|
||||
|
||||
<Show when={actionError()}>
|
||||
<div class="error-box">{actionError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{actionError()}</div>
|
||||
</Show>
|
||||
|
||||
{/* Search */}
|
||||
<div class="card" style="margin-bottom:16px;display:flex;gap:12px;flex-wrap:wrap;align-items:center;">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px;display:flex;gap:12px;flex-wrap:wrap;align-items:center;">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by name or email..."
|
||||
|
|
@ -95,9 +95,9 @@ export default function CompanyPage() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Company Name</th>
|
||||
|
|
@ -105,7 +105,7 @@ export default function CompanyPage() {
|
|||
<th>City</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -132,10 +132,10 @@ export default function CompanyPage() {
|
|||
<StatusBadge status={item.status} />
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/company/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/company/${item.id}`}>View</A>
|
||||
<button
|
||||
class="btn danger"
|
||||
class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
|
||||
disabled={deleting() === item.id}
|
||||
onClick={() => handleDelete(item.id, displayName)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -96,39 +96,39 @@ export default function CompanyDetailPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">{name()}</h1>
|
||||
<p class="page-subtitle">{companyId()}</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">{name()}</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">{companyId()}</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class={`status-pill ${isVerified() ? 'status-approved' : 'status-pending'}`}>
|
||||
{isVerified() ? 'Verified - Can Post Jobs' : 'Not Verified'}
|
||||
</span>
|
||||
<A class="btn" href="/admin/company">Back to Companies</A>
|
||||
<A class="btn navy" href="/admin/approval">Open Approval Management</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/company">Back to Companies</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/approval">Open Approval Management</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={bundle.loading}>
|
||||
<div class="card"><p class="notice">Loading company...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading company...</p></div>
|
||||
</Show>
|
||||
<Show when={!bundle.loading && !company()}>
|
||||
<div class="card"><p class="notice">Company not found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Company not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={company()}>
|
||||
<>
|
||||
<div class="field-grid-2" style="margin-bottom:16px">
|
||||
<div class="card">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2" style="margin-bottom:16px">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<p class="kv-label">Email</p>
|
||||
<p class="kv-value">{company()!.email || '—'}</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<p class="kv-label">Phone</p>
|
||||
<p class="kv-value">{company()!.phone || '—'}</p>
|
||||
</div>
|
||||
<div class="card" style="grid-column:1 / -1">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="grid-column:1 / -1">
|
||||
<p class="kv-label">Website</p>
|
||||
<p class="kv-value">
|
||||
<Show when={website()} fallback={'—'}>
|
||||
|
|
@ -140,9 +140,9 @@ export default function CompanyDetailPage() {
|
|||
|
||||
<div class="detail-layout">
|
||||
<div style="display:flex;flex-direction:column;gap:16px">
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin-bottom:8px">Company Information</h2>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="kv-item">
|
||||
<p class="kv-label">Company Name</p>
|
||||
<p class="kv-value">{name()}</p>
|
||||
|
|
@ -186,9 +186,9 @@ export default function CompanyDetailPage() {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin-bottom:8px">Contact Details</h2>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="kv-item">
|
||||
<p class="kv-label">City</p>
|
||||
<p class="kv-value">{contact()?.city || company()!.city || '—'}</p>
|
||||
|
|
@ -216,9 +216,9 @@ export default function CompanyDetailPage() {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin-bottom:8px">Banking Details</h2>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="kv-item">
|
||||
<p class="kv-label">Account Holder</p>
|
||||
<p class="kv-value">{banking()?.accountHolderName || '—'}</p>
|
||||
|
|
@ -248,7 +248,7 @@ export default function CompanyDetailPage() {
|
|||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:16px">
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin-bottom:8px">Verification Status</h2>
|
||||
<div class={`sub-card ${isVerified() ? 'sub-card-success' : 'sub-card-warning'}`} style="margin-top:0">
|
||||
<p class="kv-label">Overall Status</p>
|
||||
|
|
@ -273,7 +273,7 @@ export default function CompanyDetailPage() {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin-bottom:8px">Metadata</h2>
|
||||
<div class="kv-item">
|
||||
<p class="kv-label">Created At</p>
|
||||
|
|
|
|||
|
|
@ -52,20 +52,20 @@ export default function CreateCompanyPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Create Company</h1>
|
||||
<p class="page-subtitle">Add a new organization profile to the admin company catalog.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Create Company</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Add a new organization profile to the admin company catalog.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/company">Back to Companies</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/company">Back to Companies</A>
|
||||
</div>
|
||||
|
||||
<Show when={error()}>
|
||||
<div class="error-box">{error()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
|
||||
</Show>
|
||||
|
||||
<form class="card" onSubmit={submit}>
|
||||
<div class="field-grid-2">
|
||||
<form class="rounded-xl border border-gray-200 bg-white shadow-sm" onSubmit={submit}>
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Company Name *</label>
|
||||
<input value={form().companyName} onInput={(e) => setField('companyName', e.currentTarget.value)} />
|
||||
|
|
@ -100,8 +100,8 @@ export default function CreateCompanyPage() {
|
|||
</div>
|
||||
</div>
|
||||
<div class="actions" style="justify-content:flex-end">
|
||||
<A class="btn" href="/admin/company">Cancel</A>
|
||||
<button class="btn navy" type="submit" disabled={saving()}>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/company">Cancel</A>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Creating...' : 'Create Company'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -142,10 +142,10 @@ export default function CouponPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Coupon Management</h1>
|
||||
<p class="page-subtitle">Reusable coupon codes for package checkout</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Coupon Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Reusable coupon codes for package checkout</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -168,9 +168,9 @@ export default function CouponPage() {
|
|||
</div>
|
||||
|
||||
<Show when={activeTab() === 'list'}>
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
|
|
@ -179,7 +179,7 @@ export default function CouponPage() {
|
|||
<th>Value</th>
|
||||
<th>Max Uses</th>
|
||||
<th>Status</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -202,15 +202,15 @@ export default function CouponPage() {
|
|||
<td style="color:#475569">{item.type === 'PERCENT' ? `${item.value}%` : `₹${item.value}`}</td>
|
||||
<td style="color:#475569">{item.usage_limit != null ? item.usage_limit : '—'}</td>
|
||||
<td>
|
||||
<span class={`status-chip ${item.is_active ? 'active' : ''}`}>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${item.is_active ? 'active' : ''}`}>
|
||||
{item.is_active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<button class="btn" onClick={() => startEdit(item)}>Edit</button>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => startEdit(item)}>Edit</button>
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
disabled={toggling() === item.id}
|
||||
onClick={() => handleToggle(item)}
|
||||
>
|
||||
|
|
@ -229,10 +229,10 @@ export default function CouponPage() {
|
|||
</Show>
|
||||
|
||||
<Show when={activeTab() === 'create'}>
|
||||
<section class="card" style="max-width:520px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:520px">
|
||||
<h2 style="margin:0 0 20px;font-size:16px;font-weight:700">{form().id ? 'Edit Coupon' : 'Create Coupon'}</h2>
|
||||
<Show when={formError()}>
|
||||
<div class="error-box" style="margin-bottom:12px">{formError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{formError()}</div>
|
||||
</Show>
|
||||
<form onSubmit={handleSave} style="display:flex;flex-direction:column;gap:14px">
|
||||
<div class="field">
|
||||
|
|
@ -256,7 +256,7 @@ export default function CouponPage() {
|
|||
placeholder="e.g. 10% off for companies"
|
||||
/>
|
||||
</div>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Type</label>
|
||||
<select
|
||||
|
|
@ -278,7 +278,7 @@ export default function CouponPage() {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Min Order Amount (₹)</label>
|
||||
<input
|
||||
|
|
@ -320,11 +320,11 @@ export default function CouponPage() {
|
|||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn navy" type="submit" disabled={saving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Saving...' : (form().id ? 'Update Coupon' : 'Save Coupon')}
|
||||
</button>
|
||||
<Show when={form().id}>
|
||||
<button type="button" class="btn" onClick={resetForm}>Cancel Edit</button>
|
||||
<button type="button" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={resetForm}>Cancel Edit</button>
|
||||
</Show>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -136,10 +136,10 @@ export default function CreditPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Credit Management</h1>
|
||||
<p class="page-subtitle">Audit TraceCoin balances and adjust credits</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Credit Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Audit TraceCoin balances and adjust credits</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ export default function CreditPage() {
|
|||
{/* Balance & Ledger Tab */}
|
||||
<Show when={activeTab() === 'ledger'}>
|
||||
<div style="display:flex;flex-direction:column;gap:24px">
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin:0 0 16px;font-size:16px;font-weight:700;color:#1e293b">Search Account Balance</h2>
|
||||
<div style="display:flex;gap:10px">
|
||||
<input
|
||||
|
|
@ -173,7 +173,7 @@ export default function CreditPage() {
|
|||
style="flex:1;padding:8px 12px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px"
|
||||
/>
|
||||
<button
|
||||
class="btn navy"
|
||||
class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
|
||||
onClick={handleSearch}
|
||||
disabled={searchLoading()}
|
||||
>
|
||||
|
|
@ -181,7 +181,7 @@ export default function CreditPage() {
|
|||
</button>
|
||||
</div>
|
||||
<Show when={searchError()}>
|
||||
<div class="error-box" style="margin-top:10px">{searchError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-top:10px">{searchError()}</div>
|
||||
</Show>
|
||||
</section>
|
||||
|
||||
|
|
@ -193,14 +193,14 @@ export default function CreditPage() {
|
|||
<p style="font-size:12px;color:#bfdbfe;margin:8px 0 0">User: {searchedUserId()}</p>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding:20px;overflow:hidden">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:20px;overflow:hidden">
|
||||
<h3 style="margin:0 0 16px;font-size:15px;font-weight:700;color:#0f172a">TraceCoin Ledger</h3>
|
||||
<Show when={ledger().length === 0}>
|
||||
<p style="text-align:center;padding:32px;color:#94a3b8;font-style:italic">No transactions found for this account.</p>
|
||||
</Show>
|
||||
<Show when={ledger().length > 0}>
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
|
|
@ -248,7 +248,7 @@ export default function CreditPage() {
|
|||
|
||||
{/* Reward / Deduct Tab */}
|
||||
<Show when={activeTab() === 'adjust'}>
|
||||
<section class="card" style="max-width:460px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:460px">
|
||||
<h2 style="margin:0 0 6px;font-size:16px;font-weight:700;color:#1e293b">Reward or Adjust TraceCoins</h2>
|
||||
<p style="margin:0 0 20px;font-size:13px;color:#64748b">
|
||||
Use this to reward TraceCoins for a valid support case, process a refund, or correct a balance manually.
|
||||
|
|
@ -259,7 +259,7 @@ export default function CreditPage() {
|
|||
</div>
|
||||
</Show>
|
||||
<Show when={adjError()}>
|
||||
<div class="error-box" style="margin-bottom:14px">{adjError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:14px">{adjError()}</div>
|
||||
</Show>
|
||||
<form onSubmit={handleAdjust} style="display:flex;flex-direction:column;gap:14px">
|
||||
<div class="field">
|
||||
|
|
@ -272,7 +272,7 @@ export default function CreditPage() {
|
|||
style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;box-sizing:border-box"
|
||||
/>
|
||||
</div>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Amount</label>
|
||||
<input
|
||||
|
|
@ -316,7 +316,7 @@ export default function CreditPage() {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn navy" type="submit" disabled={adjLoading()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={adjLoading()}>
|
||||
{adjLoading() ? 'Adjusting...' : 'Apply Adjustment'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -327,13 +327,13 @@ export default function CreditPage() {
|
|||
{/* Reconcile Tab */}
|
||||
<Show when={activeTab() === 'reconcile'}>
|
||||
<div style="display:flex;flex-direction:column;gap:20px">
|
||||
<section class="card" style="max-width:460px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:460px">
|
||||
<h2 style="margin:0 0 16px;font-size:16px;font-weight:700;color:#1e293b">Ledger Reconciliation</h2>
|
||||
<Show when={reconError()}>
|
||||
<div class="error-box" style="margin-bottom:14px">{reconError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:14px">{reconError()}</div>
|
||||
</Show>
|
||||
<form onSubmit={handleReconcile} style="display:flex;flex-direction:column;gap:14px">
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Start Date</label>
|
||||
<input
|
||||
|
|
@ -356,7 +356,7 @@ export default function CreditPage() {
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn navy" type="submit" disabled={reconLoading()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={reconLoading()}>
|
||||
{reconLoading() ? 'Running...' : 'Run Reconciliation'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -368,9 +368,9 @@ export default function CreditPage() {
|
|||
<p style="color:#16a34a;font-weight:600;font-size:14px">No discrepancies found.</p>
|
||||
</Show>
|
||||
<Show when={(reconResults()?.length ?? 0) > 0}>
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User ID</th>
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export default function CustomerPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Customer Management</h1>
|
||||
<p class="page-subtitle">Manage all customer accounts on the platform.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Customer Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all customer accounts on the platform.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -64,15 +64,15 @@ export default function CustomerPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Registered</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -93,22 +93,22 @@ export default function CustomerPage() {
|
|||
<td style="color:#475569">{item.email}</td>
|
||||
<td>
|
||||
{item.status?.toUpperCase() === 'ACTIVE' && (
|
||||
<span class="status-chip active">ACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'INACTIVE' && (
|
||||
<span class="status-chip">INACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'PENDING' && (
|
||||
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
)}
|
||||
{!item.status && <span class="status-chip">—</span>}
|
||||
{!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">—</span>}
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/users/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -207,13 +207,13 @@ export default function DepartmentPage() {
|
|||
return (
|
||||
<AdminShell>
|
||||
{/* Header */}
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Departments</h1>
|
||||
<p class="page-subtitle">Manage organization departments</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Departments</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage organization departments</p>
|
||||
</div>
|
||||
<button
|
||||
class="btn navy"
|
||||
class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
|
||||
onClick={() => {
|
||||
setShowCreate((v) => !v);
|
||||
setCreateError('');
|
||||
|
|
@ -225,9 +225,9 @@ export default function DepartmentPage() {
|
|||
|
||||
{/* Create form */}
|
||||
<Show when={showCreate()}>
|
||||
<section class="card role-form-section">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<form onSubmit={handleCreate}>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Name *</label>
|
||||
<input
|
||||
|
|
@ -249,12 +249,12 @@ export default function DepartmentPage() {
|
|||
</div>
|
||||
</div>
|
||||
<Show when={createError()}>
|
||||
<p class="error-box" style="margin-top:10px">{createError()}</p>
|
||||
<p class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-top:10px">{createError()}</p>
|
||||
</Show>
|
||||
<div class="actions" style="margin-top:16px">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
onClick={() => {
|
||||
setShowCreate(false);
|
||||
setCreateError('');
|
||||
|
|
@ -262,7 +262,7 @@ export default function DepartmentPage() {
|
|||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn navy" disabled={creating()}>
|
||||
<button type="submit" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={creating()}>
|
||||
{creating() ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -288,19 +288,19 @@ export default function DepartmentPage() {
|
|||
|
||||
{/* Action error */}
|
||||
<Show when={actionError()}>
|
||||
<div class="error-box" style="margin-bottom:12px">{actionError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
|
||||
</Show>
|
||||
|
||||
{/* Table */}
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Created At</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -334,16 +334,16 @@ export default function DepartmentPage() {
|
|||
<td style="color:#475569">{item.description || '—'}</td>
|
||||
<td style="color:#475569">{fmtDate(item.createdAt || item.created_at)}</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
onClick={() => startEdit(item)}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<Show when={tab() === 'active'}>
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
disabled={busy() === item.id}
|
||||
onClick={() => handleArchive(item.id)}
|
||||
>
|
||||
|
|
@ -352,7 +352,7 @@ export default function DepartmentPage() {
|
|||
</Show>
|
||||
<Show when={tab() === 'archived'}>
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
disabled={busy() === item.id}
|
||||
onClick={() => handleRestore(item.id)}
|
||||
>
|
||||
|
|
@ -360,7 +360,7 @@ export default function DepartmentPage() {
|
|||
</button>
|
||||
</Show>
|
||||
<button
|
||||
class="btn danger"
|
||||
class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
|
||||
disabled={busy() === item.id}
|
||||
onClick={() => handleDelete(item.id, deptLabel(item))}
|
||||
>
|
||||
|
|
@ -373,7 +373,7 @@ export default function DepartmentPage() {
|
|||
<Show when={editingId() === item.id}>
|
||||
<tr>
|
||||
<td colspan="4" style="background:#f8fafc;padding:16px">
|
||||
<div class="field-grid-2" style="margin-bottom:10px">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2" style="margin-bottom:10px">
|
||||
<div class="field">
|
||||
<label>Name *</label>
|
||||
<input
|
||||
|
|
@ -393,14 +393,14 @@ export default function DepartmentPage() {
|
|||
</div>
|
||||
</div>
|
||||
<Show when={editError()}>
|
||||
<p class="error-box" style="margin-bottom:8px">{editError()}</p>
|
||||
<p class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:8px">{editError()}</p>
|
||||
</Show>
|
||||
<div class="actions">
|
||||
<button class="btn" type="button" onClick={cancelEdit}>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={cancelEdit}>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="btn navy"
|
||||
class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
|
||||
type="button"
|
||||
disabled={saving()}
|
||||
onClick={() => handleUpdate(item.id)}
|
||||
|
|
|
|||
|
|
@ -189,13 +189,13 @@ export default function DesignationPage() {
|
|||
return (
|
||||
<AdminShell>
|
||||
{/* Header */}
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Designations</h1>
|
||||
<p class="page-subtitle">Manage job designations</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Designations</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage job designations</p>
|
||||
</div>
|
||||
<button
|
||||
class="btn navy"
|
||||
class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
|
||||
onClick={() => {
|
||||
setShowCreate((v) => !v);
|
||||
setCreateError('');
|
||||
|
|
@ -210,9 +210,9 @@ export default function DesignationPage() {
|
|||
|
||||
{/* Create form */}
|
||||
<Show when={showCreate()}>
|
||||
<section class="card role-form-section">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<form onSubmit={handleCreate}>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Name *</label>
|
||||
<input
|
||||
|
|
@ -250,12 +250,12 @@ export default function DesignationPage() {
|
|||
</div>
|
||||
</div>
|
||||
<Show when={createError()}>
|
||||
<p class="error-box" style="margin-top:10px">{createError()}</p>
|
||||
<p class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-top:10px">{createError()}</p>
|
||||
</Show>
|
||||
<div class="actions" style="margin-top:16px">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
onClick={() => {
|
||||
setShowCreate(false);
|
||||
setCreateError('');
|
||||
|
|
@ -265,7 +265,7 @@ export default function DesignationPage() {
|
|||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn navy"
|
||||
class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
|
||||
disabled={creating() || (departments() ?? []).length === 0}
|
||||
>
|
||||
{creating() ? 'Saving...' : 'Save'}
|
||||
|
|
@ -293,19 +293,19 @@ export default function DesignationPage() {
|
|||
|
||||
{/* Action error */}
|
||||
<Show when={actionError()}>
|
||||
<div class="error-box" style="margin-bottom:12px">{actionError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
|
||||
</Show>
|
||||
|
||||
{/* Table */}
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Department</th>
|
||||
<th>Description</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -339,15 +339,15 @@ export default function DesignationPage() {
|
|||
<td style="color:#475569">{deptDisplay(item)}</td>
|
||||
<td style="color:#475569">{item.description || '—'}</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
onClick={() => startEdit(item)}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
class="btn danger"
|
||||
class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
|
||||
disabled={deleting() === item.id}
|
||||
onClick={() => handleDelete(item.id, item.name)}
|
||||
>
|
||||
|
|
@ -360,7 +360,7 @@ export default function DesignationPage() {
|
|||
<Show when={editingId() === item.id}>
|
||||
<tr>
|
||||
<td colspan="4" style="background:#f8fafc;padding:16px">
|
||||
<div class="field-grid-2" style="margin-bottom:10px">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2" style="margin-bottom:10px">
|
||||
<div class="field">
|
||||
<label>Name *</label>
|
||||
<input
|
||||
|
|
@ -394,14 +394,14 @@ export default function DesignationPage() {
|
|||
</div>
|
||||
</div>
|
||||
<Show when={editError()}>
|
||||
<p class="error-box" style="margin-bottom:8px">{editError()}</p>
|
||||
<p class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:8px">{editError()}</p>
|
||||
</Show>
|
||||
<div class="actions">
|
||||
<button class="btn" type="button" onClick={cancelEdit}>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={cancelEdit}>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="btn navy"
|
||||
class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
|
||||
type="button"
|
||||
disabled={saving()}
|
||||
onClick={() => handleUpdate(item.id)}
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export default function DevelopersPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Developers Management</h1>
|
||||
<p class="page-subtitle">Manage all developer accounts on the platform.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Developers Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all developer accounts on the platform.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -64,15 +64,15 @@ export default function DevelopersPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Registered</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -93,22 +93,22 @@ export default function DevelopersPage() {
|
|||
<td style="color:#475569">{item.email}</td>
|
||||
<td>
|
||||
{item.status?.toUpperCase() === 'ACTIVE' && (
|
||||
<span class="status-chip active">ACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'INACTIVE' && (
|
||||
<span class="status-chip">INACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'PENDING' && (
|
||||
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
)}
|
||||
{!item.status && <span class="status-chip">—</span>}
|
||||
{!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">—</span>}
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/users/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -146,10 +146,10 @@ export default function DiscountPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Discount Management</h1>
|
||||
<p class="page-subtitle">Automatic discounts applied before coupons</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Discount Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Automatic discounts applied before coupons</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -172,9 +172,9 @@ export default function DiscountPage() {
|
|||
</div>
|
||||
|
||||
<Show when={activeTab() === 'list'}>
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
|
|
@ -183,7 +183,7 @@ export default function DiscountPage() {
|
|||
<th>Type</th>
|
||||
<th>Value</th>
|
||||
<th>Status</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -206,14 +206,14 @@ export default function DiscountPage() {
|
|||
<td style="color:#475569">{item.type}</td>
|
||||
<td style="color:#475569">{item.type === 'PERCENT' ? `${item.value}%` : `₹${item.value}`}</td>
|
||||
<td>
|
||||
<span class={`status-chip ${item.is_active ? 'active' : ''}`}>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${item.is_active ? 'active' : ''}`}>
|
||||
{item.is_active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
disabled={toggling() === item.id}
|
||||
onClick={() => handleToggle(item)}
|
||||
>
|
||||
|
|
@ -232,10 +232,10 @@ export default function DiscountPage() {
|
|||
</Show>
|
||||
|
||||
<Show when={activeTab() === 'create'}>
|
||||
<section class="card" style="max-width:520px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:520px">
|
||||
<h2 style="margin:0 0 20px;font-size:16px;font-weight:700">{form().id ? 'Edit Discount' : 'Create Discount'}</h2>
|
||||
<Show when={formError()}>
|
||||
<div class="error-box" style="margin-bottom:12px">{formError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{formError()}</div>
|
||||
</Show>
|
||||
<form onSubmit={handleSave} style="display:flex;flex-direction:column;gap:14px">
|
||||
<div class="field">
|
||||
|
|
@ -301,7 +301,7 @@ export default function DiscountPage() {
|
|||
</select>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Type</label>
|
||||
<select
|
||||
|
|
@ -324,11 +324,11 @@ export default function DiscountPage() {
|
|||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn navy" type="submit" disabled={saving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Saving...' : (form().id ? 'Update Discount' : 'Save Discount')}
|
||||
</button>
|
||||
<Show when={form().id}>
|
||||
<button type="button" class="btn" onClick={resetForm}>Cancel Edit</button>
|
||||
<button type="button" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={resetForm}>Cancel Edit</button>
|
||||
</Show>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -174,9 +174,9 @@ export default function EmployeesPage(props: EmployeesPageProps = {}) {
|
|||
return (
|
||||
<AdminShell>
|
||||
{/* Header */}
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Internal User Management</h1>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Internal User Management</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -207,19 +207,19 @@ export default function EmployeesPage(props: EmployeesPageProps = {}) {
|
|||
{/* ===== LIST VIEW ===== */}
|
||||
<Show when={view() === 'list'}>
|
||||
<Show when={actionError()}>
|
||||
<div class="error-box" style="margin-bottom:12px">{actionError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
|
||||
</Show>
|
||||
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Status</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -252,24 +252,24 @@ export default function EmployeesPage(props: EmployeesPageProps = {}) {
|
|||
<td style="color:#475569">{item.email}</td>
|
||||
<td style="color:#475569">{roleName(item)}</td>
|
||||
<td>
|
||||
<span class={`status-chip${isActive(item) ? ' active' : ''}`}>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600${isActive(item) ? ' active' : ''}`}>
|
||||
{isActive(item) ? 'ACTIVE' : 'INACTIVE'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/employees/${item.id}/edit`}>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/employees/${item.id}/edit`}>
|
||||
Edit
|
||||
</A>
|
||||
<button
|
||||
class={isActive(item) ? 'btn danger' : 'btn navy'}
|
||||
class={isActive(item) ? 'inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors' : 'inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors'}
|
||||
disabled={toggling() === item.id}
|
||||
onClick={() => handleToggleStatus(item.id, isActive(item))}
|
||||
>
|
||||
{toggling() === item.id ? '...' : isActive(item) ? 'Deactivate' : 'Activate'}
|
||||
</button>
|
||||
<button
|
||||
class="btn danger"
|
||||
class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
|
||||
disabled={deleting() === item.id}
|
||||
onClick={() => handleDelete(item.id, employeeName(item))}
|
||||
>
|
||||
|
|
@ -289,9 +289,9 @@ export default function EmployeesPage(props: EmployeesPageProps = {}) {
|
|||
|
||||
{/* ===== CREATE VIEW ===== */}
|
||||
<Show when={view() === 'create'}>
|
||||
<section class="card role-form-section">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<form onSubmit={handleCreate}>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Full Name *</label>
|
||||
<input
|
||||
|
|
@ -339,12 +339,12 @@ export default function EmployeesPage(props: EmployeesPageProps = {}) {
|
|||
</div>
|
||||
</div>
|
||||
<Show when={createError()}>
|
||||
<p class="error-box" style="margin-top:10px">{createError()}</p>
|
||||
<p class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-top:10px">{createError()}</p>
|
||||
</Show>
|
||||
<div class="actions" style="margin-top:16px">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
onClick={() => {
|
||||
resetCreateForm();
|
||||
setView('list');
|
||||
|
|
@ -352,7 +352,7 @@ export default function EmployeesPage(props: EmployeesPageProps = {}) {
|
|||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn navy" disabled={creating()}>
|
||||
<button type="submit" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={creating()}>
|
||||
{creating() ? 'Creating...' : 'Create Internal User'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -77,28 +77,28 @@ export default function EditEmployeePage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Edit Employee</h1>
|
||||
<p class="page-subtitle">Update internal employee profile and role assignment.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Edit Employee</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Update internal employee profile and role assignment.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/employees">Back to Employees</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/employees">Back to Employees</A>
|
||||
</div>
|
||||
|
||||
<Show when={error()}>
|
||||
<div class="error-box">{error()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
|
||||
</Show>
|
||||
|
||||
<Show when={employee.loading}>
|
||||
<div class="card"><p class="notice">Loading employee...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading employee...</p></div>
|
||||
</Show>
|
||||
<Show when={!employee.loading && !employee()}>
|
||||
<div class="card"><p class="notice">Employee not found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Employee not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={employee()}>
|
||||
<form class="card" onSubmit={submit}>
|
||||
<div class="field-grid-2">
|
||||
<form class="rounded-xl border border-gray-200 bg-white shadow-sm" onSubmit={submit}>
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Full Name</label>
|
||||
<input value={name()} onInput={(e) => setName(e.currentTarget.value)} />
|
||||
|
|
@ -120,8 +120,8 @@ export default function EditEmployeePage() {
|
|||
</div>
|
||||
</div>
|
||||
<div class="actions" style="justify-content:flex-end">
|
||||
<A class="btn" href="/admin/employees">Cancel</A>
|
||||
<button class="btn navy" type="submit" disabled={saving()}>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/employees">Cancel</A>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Saving...' : 'Save Changes'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ function renderModuleContent(module: Module | null) {
|
|||
<textarea rows={4} placeholder="Describe the request" style="width:100%;border:1px solid #cbd5e1;border-radius:12px;padding:10px 12px;font-size:13px;outline:none" />
|
||||
</div>
|
||||
<div style="display:flex;justify-content:flex-end;margin-top:12px">
|
||||
<button class="btn navy" type="button">Submit</button>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="button">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -454,36 +454,36 @@ export default function ExternalDashboardManagementPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">External Dashboard Management</h1>
|
||||
<p class="page-subtitle">Open one external dashboard at a time from the list below and edit it using simple tabs.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">External Dashboard Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Open one external dashboard at a time from the list below and edit it using simple tabs.</p>
|
||||
</div>
|
||||
<a class="btn" href="/admin">Back to Dashboard</a>
|
||||
<a class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin">Back to Dashboard</a>
|
||||
</div>
|
||||
|
||||
<div class="admin-link-tabs" style="margin-bottom:14px">
|
||||
<a class="admin-link-tab active" href="#builder">View Dashboards</a>
|
||||
<a class="admin-link-tab" href="#builder">Open Builder</a>
|
||||
<div class="hidden" style="margin-bottom:14px">
|
||||
<a class="hidden" href="#builder">View Dashboards</a>
|
||||
<a class="hidden" href="#builder">Open Builder</a>
|
||||
</div>
|
||||
|
||||
<div id="builder" />
|
||||
|
||||
{/* ---------- List View ---------- */}
|
||||
<Show when={!selected()}>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="page-title" style="font-size:20px">External Dashboard List</h2>
|
||||
<p class="page-subtitle">Choose one external role dashboard to open in the builder, or create a new one.</p>
|
||||
<h2 class="text-2xl font-bold text-gray-900" style="font-size:20px">External Dashboard List</h2>
|
||||
<p class="mt-1 text-sm text-gray-500">Choose one external role dashboard to open in the builder, or create a new one.</p>
|
||||
</div>
|
||||
<button class="btn primary" onClick={createDashboard} disabled={creating()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={createDashboard} disabled={creating()}>
|
||||
{creating() ? 'Creating...' : 'Create External Dashboard'}
|
||||
</button>
|
||||
</div>
|
||||
<Show when={error()}><div class="error-box">{error()}</div></Show>
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table list-table-soft-head">
|
||||
<Show when={error()}><div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div></Show>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm list-table-soft-head">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Role</th>
|
||||
|
|
@ -491,7 +491,7 @@ export default function ExternalDashboardManagementPage() {
|
|||
<th>Status</th>
|
||||
<th>Version</th>
|
||||
<th>Modules</th>
|
||||
<th class="align-right">Action</th>
|
||||
<th class="text-right">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -506,12 +506,12 @@ export default function ExternalDashboardManagementPage() {
|
|||
<tr>
|
||||
<td style="font-weight:600;color:#0f172a">{d.roleKey || 'No role selected'}</td>
|
||||
<td style="color:#475569">{d.title}</td>
|
||||
<td><span class={`status-chip ${d.status === 'published' ? 'active' : ''}`}>{d.status}</span></td>
|
||||
<td><span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${d.status === 'published' ? 'active' : ''}`}>{d.status}</span></td>
|
||||
<td style="color:#64748b">v{d.version}</td>
|
||||
<td style="color:#64748b">{d.modules.length} pages</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<button class="btn" onClick={() => void openDashboard(d.id)}>View Builder</button>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => void openDashboard(d.id)}>View Builder</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -531,14 +531,14 @@ export default function ExternalDashboardManagementPage() {
|
|||
<p>Edit menu labels, page names, and opening page for this role dashboard.</p>
|
||||
</div>
|
||||
<div class="builder-header-actions">
|
||||
<button class="btn" onClick={() => setSelectedId('')}>Back to List</button>
|
||||
<button class="btn primary" onClick={saveSelected} disabled={saving()}>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => setSelectedId('')}>Back to List</button>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={saveSelected} disabled={saving()}>
|
||||
{saving() ? 'Saving...' : 'Save Dashboard'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={error()}><div class="error-box">{error()}</div></Show>
|
||||
<Show when={error()}><div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div></Show>
|
||||
|
||||
{/* Tab bar */}
|
||||
<div class="builder-tab-bar">
|
||||
|
|
@ -551,7 +551,7 @@ export default function ExternalDashboardManagementPage() {
|
|||
|
||||
{/* Overview */}
|
||||
<Show when={activeTab() === 'overview'}>
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="field">
|
||||
<label>Role</label>
|
||||
<select
|
||||
|
|
@ -592,7 +592,7 @@ export default function ExternalDashboardManagementPage() {
|
|||
<div class="builder-section">
|
||||
<div class="sub-card-header">
|
||||
<h4>Menu Items</h4>
|
||||
<button class="btn orange" onClick={addSidebarItem}>Add Menu Item</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={addSidebarItem}>Add Menu Item</button>
|
||||
</div>
|
||||
<For each={selected()!.sidebar}>
|
||||
{(item, idx) => (
|
||||
|
|
@ -624,7 +624,7 @@ export default function ExternalDashboardManagementPage() {
|
|||
/>
|
||||
Show
|
||||
</label>
|
||||
<button class="btn danger" onClick={() => removeSidebarItem(item.key)}>Remove</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeSidebarItem(item.key)}>Remove</button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
|
|
@ -639,7 +639,7 @@ export default function ExternalDashboardManagementPage() {
|
|||
<div class="builder-section">
|
||||
<div class="sub-card-header">
|
||||
<h4>Pages</h4>
|
||||
<button class="btn orange" onClick={addModule}>Add Page</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={addModule}>Add Page</button>
|
||||
</div>
|
||||
<For each={selected()!.modules}>
|
||||
{(module) => (
|
||||
|
|
@ -675,7 +675,7 @@ export default function ExternalDashboardManagementPage() {
|
|||
<textarea rows={2} value={module.summary} onInput={(e) => update({ modules: selected()!.modules.map((m) => m.key === module.key ? { ...m, summary: e.currentTarget.value } : m) })} />
|
||||
</div>
|
||||
<div style="display:flex;justify-content:flex-end">
|
||||
<button class="btn danger" onClick={() => removeModule(module.key)}>Remove Page</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeModule(module.key)}>Remove Page</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -719,7 +719,7 @@ export default function ExternalDashboardManagementPage() {
|
|||
>
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px">
|
||||
<span class="meta-chip">Role: {livePreviewRoleKey()}</span>
|
||||
<a class="btn" href={livePreviewUrl()} target="_blank" rel="noreferrer">Open Full Page Preview</a>
|
||||
<a class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={livePreviewUrl()} target="_blank" rel="noreferrer">Open Full Page Preview</a>
|
||||
</div>
|
||||
<iframe
|
||||
src={livePreviewUrl()}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default function ExternalRoleManagementAliasPage() {
|
|||
onMount(() => navigate('/admin/runtime-roles', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card"><p class="notice">Redirecting to external role management...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to external role management...</p></div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@ import AdminShell from '~/components/AdminShell';
|
|||
export default function AdjustCreditAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/credit', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to credit management...</p></div></AdminShell>;
|
||||
return <AdminShell><div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to credit management...</p></div></AdminShell>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@ import AdminShell from '~/components/AdminShell';
|
|||
export default function ReconcileAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/ledger', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to ledger management...</p></div></AdminShell>;
|
||||
return <AdminShell><div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to ledger management...</p></div></AdminShell>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export default function FitnessTrainersPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Fitness Trainer Management</h1>
|
||||
<p class="page-subtitle">Manage all fitness trainer accounts on the platform.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Fitness Trainer Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all fitness trainer accounts on the platform.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -64,15 +64,15 @@ export default function FitnessTrainersPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Registered</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -93,22 +93,22 @@ export default function FitnessTrainersPage() {
|
|||
<td style="color:#475569">{item.email}</td>
|
||||
<td>
|
||||
{item.status?.toUpperCase() === 'ACTIVE' && (
|
||||
<span class="status-chip active">ACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'INACTIVE' && (
|
||||
<span class="status-chip">INACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'PENDING' && (
|
||||
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
)}
|
||||
{!item.status && <span class="status-chip">—</span>}
|
||||
{!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">—</span>}
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/users/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export default function GraphicDesignersPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Graphic Designer Management</h1>
|
||||
<p class="page-subtitle">Manage all graphic designer accounts on the platform.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Graphic Designer Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all graphic designer accounts on the platform.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -64,15 +64,15 @@ export default function GraphicDesignersPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Registered</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -93,22 +93,22 @@ export default function GraphicDesignersPage() {
|
|||
<td style="color:#475569">{item.email}</td>
|
||||
<td>
|
||||
{item.status?.toUpperCase() === 'ACTIVE' && (
|
||||
<span class="status-chip active">ACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'INACTIVE' && (
|
||||
<span class="status-chip">INACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'PENDING' && (
|
||||
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
)}
|
||||
{!item.status && <span class="status-chip">—</span>}
|
||||
{!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">—</span>}
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/users/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default function HelpAliasPage() {
|
|||
onMount(() => navigate('/admin/support', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card"><p class="notice">Redirecting to support management...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to support management...</p></div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ export default function HelpArticlePage() {
|
|||
const params = useParams();
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Help Article</h1>
|
||||
<p class="page-subtitle">Legacy help article route preserved for migration compatibility.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Help Article</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Legacy help article route preserved for migration compatibility.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/support">Back to Support</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/support">Back to Support</A>
|
||||
</div>
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<p class="notice" style="margin:0">
|
||||
Article ID: <strong>{params.id}</strong>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ export default function HelpSupportBridgePage() {
|
|||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card">
|
||||
<h1 class="page-title">Support Bridge</h1>
|
||||
<p class="page-subtitle" style="margin-top:8px">
|
||||
<h1 class="text-2xl font-bold text-gray-900">Support Bridge</h1>
|
||||
<p class="mt-1 text-sm text-gray-500" style="margin-top:8px">
|
||||
This legacy help bridge now routes through the unified Support Management module.
|
||||
</p>
|
||||
</div>
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="actions">
|
||||
<A class="btn navy" href="/admin/support">Open Support Management</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/support">Open Support Management</A>
|
||||
</div>
|
||||
</section>
|
||||
</AdminShell>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,165 @@
|
|||
import { A } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
import {
|
||||
Users, Building2, Briefcase, Clock, Ticket, Receipt,
|
||||
Package, Megaphone, Shield, UserCog, FormInput, LayoutDashboard,
|
||||
BadgeCheck, ChevronRight, TrendingUp,
|
||||
} from 'lucide-solid';
|
||||
import type { Component } from 'solid-js';
|
||||
|
||||
export default function AdminIndex() {
|
||||
type KpiCard = {
|
||||
label: string;
|
||||
value: string;
|
||||
href: string;
|
||||
icon: Component<any>;
|
||||
bg: string;
|
||||
fg: string;
|
||||
};
|
||||
|
||||
type ControlLink = {
|
||||
label: string;
|
||||
href: string;
|
||||
desc: string;
|
||||
icon: Component<any>;
|
||||
accent: string;
|
||||
};
|
||||
|
||||
const KPI: KpiCard[] = [
|
||||
{ label: 'Total Users', value: '—', href: '/admin/users', icon: Users, bg: 'bg-blue-50', fg: 'text-blue-600' },
|
||||
{ label: 'Companies', value: '—', href: '/admin/company', icon: Building2, bg: 'bg-violet-50', fg: 'text-violet-600' },
|
||||
{ label: 'Open Jobs', value: '—', href: '/admin/jobs', icon: Briefcase, bg: 'bg-amber-50', fg: 'text-amber-600' },
|
||||
{ label: 'Pending Approvals', value: '—', href: '/admin/approval', icon: Clock, bg: 'bg-orange-50', fg: 'text-orange-600' },
|
||||
{ label: 'Open Tickets', value: '—', href: '/admin/support', icon: Ticket, bg: 'bg-rose-50', fg: 'text-rose-600' },
|
||||
{ label: 'Invoices', value: '—', href: '/admin/invoice', icon: Receipt, bg: 'bg-teal-50', fg: 'text-teal-600' },
|
||||
{ label: 'Active Orders', value: '—', href: '/admin/order', icon: Package, bg: 'bg-sky-50', fg: 'text-sky-600' },
|
||||
{ label: 'Active Leads', value: '—', href: '/admin/leads', icon: Megaphone, bg: 'bg-pink-50', fg: 'text-pink-600' },
|
||||
];
|
||||
|
||||
const CONTROLS: ControlLink[] = [
|
||||
{
|
||||
label: 'Internal Roles',
|
||||
href: '/admin/roles',
|
||||
desc: 'Define permissions and access levels for internal staff roles.',
|
||||
icon: Shield,
|
||||
accent: 'bg-blue-50 text-blue-600',
|
||||
},
|
||||
{
|
||||
label: 'External Roles',
|
||||
href: '/admin/runtime-roles',
|
||||
desc: 'Configure modules, credit rules, and capabilities per external role.',
|
||||
icon: UserCog,
|
||||
accent: 'bg-violet-50 text-violet-600',
|
||||
},
|
||||
{
|
||||
label: 'Onboarding Flows',
|
||||
href: '/admin/onboarding-schemas',
|
||||
desc: 'Build schema-driven onboarding forms for each external role.',
|
||||
icon: FormInput,
|
||||
accent: 'bg-amber-50 text-amber-600',
|
||||
},
|
||||
{
|
||||
label: 'External Dashboards',
|
||||
href: '/admin/external-dashboard-management',
|
||||
desc: 'Configure the runtimeConfig, sidebar, and widgets per external role.',
|
||||
icon: LayoutDashboard,
|
||||
accent: 'bg-teal-50 text-teal-600',
|
||||
},
|
||||
{
|
||||
label: 'Internal Dashboards',
|
||||
href: '/admin/internal-dashboard-management',
|
||||
desc: 'Design home widgets and KPI panels for internal staff dashboards.',
|
||||
icon: LayoutDashboard,
|
||||
accent: 'bg-orange-50 text-orange-600',
|
||||
},
|
||||
{
|
||||
label: 'Approval Queue',
|
||||
href: '/admin/approval',
|
||||
desc: 'Review, approve, or reject pending platform action requests.',
|
||||
icon: BadgeCheck,
|
||||
accent: 'bg-rose-50 text-rose-600',
|
||||
},
|
||||
];
|
||||
|
||||
export default function AdminDashboard() {
|
||||
return (
|
||||
<AdminShell>
|
||||
<h1 class="page-title">Admin Builder</h1>
|
||||
<p class="page-subtitle">Pick what you want to configure.</p>
|
||||
<section class="card">
|
||||
<div class="actions">
|
||||
<A class="btn primary" href="/admin/runtime-roles/new">Create Role</A>
|
||||
<A class="btn" href="/admin/runtime-roles">Manage Roles</A>
|
||||
<div class="space-y-8">
|
||||
|
||||
{/* ── Page header ── */}
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-[22px] font-bold tracking-tight text-gray-900">Platform Overview</h1>
|
||||
<p class="mt-0.5 text-sm text-gray-400">Real-time snapshot of the Nxtgauge platform.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 rounded-xl border border-gray-200 bg-white px-3 py-1.5 shadow-sm">
|
||||
<TrendingUp class="h-4 w-4 text-emerald-500" />
|
||||
<span class="text-xs font-medium text-gray-500">Live data</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<A class="btn primary" href="/admin/role-ui-configs/new">Create Dashboard</A>
|
||||
<A class="btn" href="/admin/role-ui-configs">Manage Dashboards</A>
|
||||
|
||||
{/* ── KPI grid ── */}
|
||||
<div class="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||
{KPI.map((kpi) => {
|
||||
const Icon = kpi.icon;
|
||||
return (
|
||||
<A
|
||||
href={kpi.href}
|
||||
class="group relative overflow-hidden rounded-2xl border border-gray-100 bg-white p-5 shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:border-gray-200 hover:shadow-md"
|
||||
>
|
||||
{/* Subtle background circle */}
|
||||
<div class="absolute -right-4 -top-4 h-20 w-20 rounded-full bg-gray-50 opacity-60" />
|
||||
|
||||
<div class="relative flex flex-col gap-4">
|
||||
{/* Icon */}
|
||||
<div class={`flex h-10 w-10 items-center justify-center rounded-xl ${kpi.bg}`}>
|
||||
<Icon class={`h-5 w-5 ${kpi.fg}`} />
|
||||
</div>
|
||||
|
||||
{/* Metric */}
|
||||
<div>
|
||||
<p class="text-[28px] font-bold leading-none tracking-tight text-gray-900">{kpi.value}</p>
|
||||
<p class="mt-1.5 text-[13px] font-medium text-gray-400">{kpi.label}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hover arrow */}
|
||||
<ChevronRight class="absolute bottom-4 right-4 h-4 w-4 text-gray-300 opacity-0 transition-opacity group-hover:opacity-100" />
|
||||
</A>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<A class="btn primary" href="/admin/onboarding-schemas/new">Create Onboarding</A>
|
||||
<A class="btn" href="/admin/onboarding-schemas">Manage Onboarding</A>
|
||||
|
||||
{/* ── Control Plane ── */}
|
||||
<div>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h2 class="text-[15px] font-semibold text-gray-700">Control Plane</h2>
|
||||
<span class="rounded-full border border-gray-200 bg-white px-2.5 py-0.5 text-[11px] font-medium text-gray-400 shadow-sm">
|
||||
{CONTROLS.length} modules
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{CONTROLS.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<A
|
||||
href={item.href}
|
||||
class="group flex items-start gap-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:border-orange-100 hover:shadow-md"
|
||||
>
|
||||
<div class={`mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl ${item.accent}`}>
|
||||
<Icon class="h-4 w-4" />
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-[14px] font-semibold text-gray-800 group-hover:text-[#fd6216] transition-colors">{item.label}</p>
|
||||
<p class="mt-0.5 text-[12px] leading-relaxed text-gray-400">{item.desc}</p>
|
||||
</div>
|
||||
<ChevronRight class="mt-1 h-4 w-4 shrink-0 text-gray-300 transition-transform group-hover:translate-x-0.5 group-hover:text-orange-400" />
|
||||
</A>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -413,35 +413,35 @@ export default function InternalDashboardManagementPage() {
|
|||
// ---------- List view ----------
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Internal Dashboard Management</h1>
|
||||
<p class="page-subtitle">Open one internal dashboard at a time from the list below and edit it using simple tabs.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Internal Dashboard Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Open one internal dashboard at a time from the list below and edit it using simple tabs.</p>
|
||||
</div>
|
||||
<a class="btn" href="/admin">Back to Dashboard</a>
|
||||
<a class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin">Back to Dashboard</a>
|
||||
</div>
|
||||
|
||||
<div class="admin-link-tabs" style="margin-bottom:14px">
|
||||
<a class="admin-link-tab active" href="#builder">View Dashboards</a>
|
||||
<a class="admin-link-tab" href="#builder">Open Builder</a>
|
||||
<div class="hidden" style="margin-bottom:14px">
|
||||
<a class="hidden" href="#builder">View Dashboards</a>
|
||||
<a class="hidden" href="#builder">Open Builder</a>
|
||||
</div>
|
||||
|
||||
<div id="builder" />
|
||||
|
||||
<Show when={!selected()}>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="page-title" style="font-size:20px">Internal Dashboard List</h2>
|
||||
<p class="page-subtitle">Choose one internal dashboard to open in the builder, or create a new dashboard for an internal role.</p>
|
||||
<h2 class="text-2xl font-bold text-gray-900" style="font-size:20px">Internal Dashboard List</h2>
|
||||
<p class="mt-1 text-sm text-gray-500">Choose one internal dashboard to open in the builder, or create a new dashboard for an internal role.</p>
|
||||
</div>
|
||||
<button class="btn navy" onClick={createDashboard} disabled={creating()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={createDashboard} disabled={creating()}>
|
||||
{creating() ? 'Creating...' : 'Create Internal Dashboard'}
|
||||
</button>
|
||||
</div>
|
||||
<Show when={error()}><div class="error-box">{error()}</div></Show>
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table list-table-soft-head">
|
||||
<Show when={error()}><div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div></Show>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm list-table-soft-head">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Role</th>
|
||||
|
|
@ -449,7 +449,7 @@ export default function InternalDashboardManagementPage() {
|
|||
<th>Dashboard</th>
|
||||
<th>Status</th>
|
||||
<th>Version</th>
|
||||
<th class="align-right">Action</th>
|
||||
<th class="text-right">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -465,11 +465,11 @@ export default function InternalDashboardManagementPage() {
|
|||
<td style="color:#475569">{d.roleName || 'Not linked'}</td>
|
||||
<td style="color:#475569">{d.roleId || 'Not linked'}</td>
|
||||
<td style="font-weight:600;color:#0f172a">{d.title}</td>
|
||||
<td><span class={`status-chip ${d.status === 'published' ? 'active' : ''}`}>{d.status}</span></td>
|
||||
<td><span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${d.status === 'published' ? 'active' : ''}`}>{d.status}</span></td>
|
||||
<td style="color:#64748b">v{d.version}</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<button class="btn" onClick={() => void openDashboard(d.id)}>View Builder</button>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => void openDashboard(d.id)}>View Builder</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -489,14 +489,14 @@ export default function InternalDashboardManagementPage() {
|
|||
<p>Manage menu items, sections, tabs, form fields, and summary cards from one place.</p>
|
||||
</div>
|
||||
<div class="builder-header-actions">
|
||||
<button class="btn" onClick={() => setSelectedId('')}>Back to List</button>
|
||||
<button class="btn primary" onClick={saveSelected} disabled={saving()}>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => setSelectedId('')}>Back to List</button>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={saveSelected} disabled={saving()}>
|
||||
{saving() ? 'Saving...' : 'Save Dashboard'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={error()}><div class="error-box">{error()}</div></Show>
|
||||
<Show when={error()}><div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div></Show>
|
||||
|
||||
{/* Tab bar */}
|
||||
<div class="builder-tab-bar">
|
||||
|
|
@ -513,7 +513,7 @@ export default function InternalDashboardManagementPage() {
|
|||
|
||||
{/* Overview */}
|
||||
<Show when={activeTab() === 'overview'}>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Team Role</label>
|
||||
<select
|
||||
|
|
@ -548,7 +548,7 @@ export default function InternalDashboardManagementPage() {
|
|||
<div class="builder-section">
|
||||
<div class="sub-card-header">
|
||||
<h4>Menu</h4>
|
||||
<button class="btn orange" onClick={addSidebarItem}>Add Menu Item</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={addSidebarItem}>Add Menu Item</button>
|
||||
</div>
|
||||
<For each={selected()!.sidebar}>
|
||||
{(item, idx) => (
|
||||
|
|
@ -572,7 +572,7 @@ export default function InternalDashboardManagementPage() {
|
|||
/>
|
||||
Show
|
||||
</label>
|
||||
<button class="btn danger" onClick={() => removeSidebarItem(item.key)}>Remove</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeSidebarItem(item.key)}>Remove</button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
|
|
@ -586,7 +586,7 @@ export default function InternalDashboardManagementPage() {
|
|||
<Show when={activeTab() === 'sections'}>
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">
|
||||
<h3 style="margin:0;font-size:15px;font-weight:700">Sections</h3>
|
||||
<button class="btn orange" onClick={addSection}>Add Section</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={addSection}>Add Section</button>
|
||||
</div>
|
||||
<For each={selected()!.sections}>
|
||||
{(section) => (
|
||||
|
|
@ -597,14 +597,14 @@ export default function InternalDashboardManagementPage() {
|
|||
onInput={(e) => updateSection(section.id, { title: e.currentTarget.value })}
|
||||
placeholder="Section title"
|
||||
/>
|
||||
<button class="btn danger" onClick={() => removeSection(section.id)}>Remove</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeSection(section.id)}>Remove</button>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div class="sub-card">
|
||||
<div class="sub-card-header">
|
||||
<h4>Tabs and Form Fields</h4>
|
||||
<button class="btn orange" onClick={() => addTab(section.id)}>Add Tab</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={() => addTab(section.id)}>Add Tab</button>
|
||||
</div>
|
||||
<For each={section.tabs}>
|
||||
{(tab) => (
|
||||
|
|
@ -615,10 +615,10 @@ export default function InternalDashboardManagementPage() {
|
|||
onInput={(e) => updateTab(section.id, tab.id, { title: e.currentTarget.value })}
|
||||
placeholder="Tab title"
|
||||
/>
|
||||
<button class="btn danger" onClick={() => removeTab(section.id, tab.id)}>Remove</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeTab(section.id, tab.id)}>Remove</button>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:flex-end;margin-bottom:8px">
|
||||
<button class="btn orange" onClick={() => addField(section.id, tab.id)}>Add Field</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={() => addField(section.id, tab.id)}>Add Field</button>
|
||||
</div>
|
||||
<For each={tab.fields}>
|
||||
{(field) => (
|
||||
|
|
@ -651,7 +651,7 @@ export default function InternalDashboardManagementPage() {
|
|||
/>
|
||||
Required
|
||||
</label>
|
||||
<button class="btn danger" onClick={() => removeField(section.id, tab.id, field.id)}>Remove</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeField(section.id, tab.id, field.id)}>Remove</button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
|
|
@ -670,7 +670,7 @@ export default function InternalDashboardManagementPage() {
|
|||
<div class="sub-card" style="margin-top:8px">
|
||||
<div class="sub-card-header">
|
||||
<h4>Widgets</h4>
|
||||
<button class="btn orange" onClick={() => addWidget(section.id)}>Add Widget</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors orange" onClick={() => addWidget(section.id)}>Add Widget</button>
|
||||
</div>
|
||||
<For each={section.widgets}>
|
||||
{(widget) => (
|
||||
|
|
@ -679,7 +679,7 @@ export default function InternalDashboardManagementPage() {
|
|||
<input value={widget.metric} onInput={(e) => updateWidget(section.id, widget.id, { metric: e.currentTarget.value })} placeholder="Metric value e.g. 42" />
|
||||
<textarea rows={2} value={widget.description || ''} onInput={(e) => updateWidget(section.id, widget.id, { description: e.currentTarget.value })} placeholder="Widget description" />
|
||||
<div style="display:flex;justify-content:flex-end">
|
||||
<button class="btn danger" style="font-size:12px;padding:5px 10px" onClick={() => removeWidget(section.id, widget.id)}>Remove Widget</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" style="font-size:12px;padding:5px 10px" onClick={() => removeWidget(section.id, widget.id)}>Remove Widget</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -728,7 +728,7 @@ export default function InternalDashboardManagementPage() {
|
|||
<Show when={selected()!.roleId}>
|
||||
<span class="meta-chip">Role ID: {selected()!.roleId}</span>
|
||||
</Show>
|
||||
<a class="btn" href={livePreviewUrl()} target="_blank" rel="noreferrer">Open Full Page Preview</a>
|
||||
<a class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={livePreviewUrl()} target="_blank" rel="noreferrer">Open Full Page Preview</a>
|
||||
</div>
|
||||
<iframe
|
||||
src={livePreviewUrl()}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default function InternalRoleManagementAliasPage() {
|
|||
onMount(() => navigate('/admin/roles', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card"><p class="notice">Redirecting to internal role management...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to internal role management...</p></div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ export default function InvoicePage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Invoice Management</h1>
|
||||
<p class="page-subtitle">View and download all platform invoices.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Invoice Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">View and download all platform invoices.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -49,9 +49,9 @@ export default function InvoicePage() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Invoice #</th>
|
||||
|
|
@ -61,7 +61,7 @@ export default function InvoicePage() {
|
|||
<th>Tax (₹)</th>
|
||||
<th>Status</th>
|
||||
<th>Date</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -83,16 +83,16 @@ export default function InvoicePage() {
|
|||
<td style="color:#475569">₹{item.total != null ? (item.total / 100).toFixed(2) : '—'}</td>
|
||||
<td style="color:#475569">₹{item.tax != null ? (item.tax / 100).toFixed(2) : '—'}</td>
|
||||
<td>
|
||||
<span class={`status-chip ${item.status === 'PAID' || item.status === 'ISSUED' ? 'active' : ''}`}>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${item.status === 'PAID' || item.status === 'ISSUED' ? 'active' : ''}`}>
|
||||
{item.status || '—'}
|
||||
</span>
|
||||
</td>
|
||||
<td style="color:#475569">{item.created_at ? new Date(item.created_at).toLocaleString() : '—'}</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<Show when={item.download_url || item.pdf_url}>
|
||||
<a
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
href={item.download_url || item.pdf_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
|
|
|||
|
|
@ -63,10 +63,10 @@ export default function JobsPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Jobs Management</h1>
|
||||
<p class="page-subtitle">Review live company job postings</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Jobs Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review live company job postings</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -87,9 +87,9 @@ export default function JobsPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
|
|
@ -98,7 +98,7 @@ export default function JobsPage() {
|
|||
<th>Rate</th>
|
||||
<th>Location</th>
|
||||
<th>Status</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -137,10 +137,10 @@ export default function JobsPage() {
|
|||
<span class={statusChipClass(job.status)}>{job.status || '—'}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/jobs/${job.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/jobs/${job.id}`}>View</A>
|
||||
<Show when={job.status === 'PENDING_APPROVAL'}>
|
||||
<A class="btn" href="/admin/approval">Review</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval">Review</A>
|
||||
</Show>
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -51,25 +51,25 @@ export default function JobDetailPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Job Management</h1>
|
||||
<p class="page-subtitle">Review one live backend job in the same detail-first style as other admin modules.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Job Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review one live backend job in the same detail-first style as other admin modules.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/jobs">Back to Jobs</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/jobs">Back to Jobs</A>
|
||||
</div>
|
||||
|
||||
<Show when={job.loading}>
|
||||
<div class="card"><p class="notice">Loading job...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading job...</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={!job.loading && !job()}>
|
||||
<div class="card"><p class="notice">Job not found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Job not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={job()}>
|
||||
<section class="card">
|
||||
<div class="field-grid-2">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<p class="hint">Title</p>
|
||||
<p style="margin:6px 0 0;font-weight:700;color:#0f172a">{job()!.title || '—'}</p>
|
||||
|
|
|
|||
|
|
@ -215,10 +215,10 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Knowledge Base</h1>
|
||||
<p class="page-subtitle">Manage help articles and categories</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Knowledge Base</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage help articles and categories</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -249,22 +249,22 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
|
||||
{/* Categories Tab */}
|
||||
<Show when={tab() === 'categories'}>
|
||||
<div class="page-actions" style="margin-bottom:16px">
|
||||
<div class="mb-6 flex items-start justify-between gap-4" style="margin-bottom:16px">
|
||||
<div />
|
||||
<button class="btn navy" onClick={() => setShowCatForm(!showCatForm())}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={() => setShowCatForm(!showCatForm())}>
|
||||
{showCatForm() ? 'Cancel' : 'Add Category'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Show when={actionError()}>
|
||||
<div class="error-box" style="margin-bottom:12px">{actionError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
|
||||
</Show>
|
||||
|
||||
<Show when={showCatForm()}>
|
||||
<section class="card" style="margin-bottom:16px;max-width:480px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px;max-width:480px">
|
||||
<h2 style="margin:0 0 16px;font-size:15px;font-weight:700">New Category</h2>
|
||||
<Show when={catError()}>
|
||||
<div class="error-box" style="margin-bottom:10px">{catError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:10px">{catError()}</div>
|
||||
</Show>
|
||||
<form onSubmit={handleAddCategory} style="display:flex;flex-direction:column;gap:12px">
|
||||
<div class="field">
|
||||
|
|
@ -300,7 +300,7 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn navy" type="submit" disabled={catSaving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={catSaving()}>
|
||||
{catSaving() ? 'Saving...' : 'Create Category'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -308,15 +308,15 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
</section>
|
||||
</Show>
|
||||
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Slug</th>
|
||||
<th>Article Count</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -338,10 +338,10 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
<td style="color:#475569;font-family:monospace;font-size:13px">{cat.slug}</td>
|
||||
<td style="color:#475569">{cat.article_count ?? '—'}</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<button class="btn" onClick={() => startEditCat(cat)}>Edit</button>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => startEditCat(cat)}>Edit</button>
|
||||
<button
|
||||
class="btn danger"
|
||||
class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
|
||||
disabled={deletingCatId() === cat.id}
|
||||
onClick={() => deleteCategory(cat.id, cat.name)}
|
||||
>
|
||||
|
|
@ -354,7 +354,7 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
<tr>
|
||||
<td colspan="4" style="background:#f8fafc;padding:14px">
|
||||
<Show when={editCatError()}>
|
||||
<div class="error-box" style="margin-bottom:8px">{editCatError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:8px">{editCatError()}</div>
|
||||
</Show>
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end">
|
||||
<div class="field">
|
||||
|
|
@ -376,10 +376,10 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
/>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="btn navy" disabled={editCatSaving()} onClick={() => saveEditCat(cat.id)}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={editCatSaving()} onClick={() => saveEditCat(cat.id)}>
|
||||
{editCatSaving() ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
<button class="btn" onClick={cancelEditCat}>Cancel</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={cancelEditCat}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -398,7 +398,7 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
{/* Articles Tab */}
|
||||
<Show when={tab() === 'articles'}>
|
||||
<Show when={articleActionError()}>
|
||||
<div class="error-box" style="margin-bottom:12px">{articleActionError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{articleActionError()}</div>
|
||||
</Show>
|
||||
|
||||
<div style="margin-bottom:16px">
|
||||
|
|
@ -411,16 +411,16 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Category</th>
|
||||
<th>Status</th>
|
||||
<th>Updated At</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -440,7 +440,7 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
<td style="font-weight:600;color:#0f172a">{article.title}</td>
|
||||
<td style="color:#475569">{article.category || '—'}</td>
|
||||
<td>
|
||||
<span class={`status-chip ${article.status === 'PUBLISHED' ? 'active' : 'draft'}`}>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${article.status === 'PUBLISHED' ? 'active' : 'draft'}`}>
|
||||
{article.status || '—'}
|
||||
</span>
|
||||
</td>
|
||||
|
|
@ -448,10 +448,10 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
{article.updated_at ? new Date(article.updated_at).toLocaleString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/kb/articles/${article.id}/edit`}>Edit</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/kb/articles/${article.id}/edit`}>Edit</A>
|
||||
<button
|
||||
class="btn danger"
|
||||
class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
|
||||
disabled={deletingArticleId() === article.id}
|
||||
onClick={() => deleteArticle(article.id, article.title)}
|
||||
>
|
||||
|
|
@ -471,10 +471,10 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
|
||||
{/* Create Article Tab */}
|
||||
<Show when={tab() === 'create-article'}>
|
||||
<section class="card" style="max-width:640px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:640px">
|
||||
<h2 style="margin:0 0 20px;font-size:16px;font-weight:700">New Article</h2>
|
||||
<Show when={artError()}>
|
||||
<div class="error-box" style="margin-bottom:14px">{artError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:14px">{artError()}</div>
|
||||
</Show>
|
||||
<form onSubmit={handleCreateArticle} style="display:flex;flex-direction:column;gap:14px">
|
||||
<div class="field">
|
||||
|
|
@ -536,7 +536,7 @@ export default function KbPage(props: KbPageProps = {}) {
|
|||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn navy" type="submit" disabled={artSaving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={artSaving()}>
|
||||
{artSaving() ? 'Creating...' : 'Create Article'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -33,30 +33,30 @@ export default function KbArticleDetailPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">KB Article Detail</h1>
|
||||
<p class="page-subtitle">Metadata and safe content preview for this article.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">KB Article Detail</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Metadata and safe content preview for this article.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href="/admin/kb/articles">Back to Articles</A>
|
||||
<A class="btn primary" href={`/admin/kb/articles/${params.id}/edit`}>Edit Article</A>
|
||||
<div class="flex items-center gap-2">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/kb/articles">Back to Articles</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/kb/articles/${params.id}/edit`}>Edit Article</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={article.loading}>
|
||||
<section class="card"><p class="notice">Loading article...</p></section>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading article...</p></section>
|
||||
</Show>
|
||||
|
||||
<Show when={!article.loading && !article()}>
|
||||
<section class="card"><p class="notice">Article not found.</p></section>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Article not found.</p></section>
|
||||
</Show>
|
||||
|
||||
<Show when={article()}>
|
||||
<div class="detail-layout">
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin-bottom:10px">Metadata</h2>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="kv-item"><p class="kv-label">Title</p><p class="kv-value">{article()!.title || '—'}</p></div>
|
||||
<div class="kv-item"><p class="kv-label">Status</p><p class="kv-value">{article()!.status || '—'}</p></div>
|
||||
<div class="kv-item"><p class="kv-label">Slug</p><p class="kv-value">{article()!.slug || '—'}</p></div>
|
||||
|
|
@ -66,7 +66,7 @@ export default function KbArticleDetailPage() {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin-bottom:10px">Content</h2>
|
||||
<pre class="json" style="max-height:720px;white-space:pre-wrap">{article()!.content || article()!.body || 'No content'}</pre>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -75,28 +75,28 @@ export default function KbArticleEditPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Edit KB Article</h1>
|
||||
<p class="page-subtitle">Update article metadata, status, and content.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Edit KB Article</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Update article metadata, status, and content.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href={`/admin/kb/articles/${params.id}`}>Back to Detail</A>
|
||||
<A class="btn" href="/admin/kb/articles">Back to Articles</A>
|
||||
<div class="flex items-center gap-2">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/kb/articles/${params.id}`}>Back to Detail</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/kb/articles">Back to Articles</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={article.loading}>
|
||||
<section class="card"><p class="notice">Loading article...</p></section>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading article...</p></section>
|
||||
</Show>
|
||||
|
||||
<Show when={!article.loading && !article()}>
|
||||
<section class="card"><p class="notice">Article not found.</p></section>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Article not found.</p></section>
|
||||
</Show>
|
||||
|
||||
<Show when={article() && loaded()}>
|
||||
<form class="card" onSubmit={save}>
|
||||
<div class="field-grid-2">
|
||||
<form class="rounded-xl border border-gray-200 bg-white shadow-sm" onSubmit={save}>
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Title</label>
|
||||
<input value={title()} onInput={(e) => setTitle(e.currentTarget.value)} required />
|
||||
|
|
@ -127,7 +127,7 @@ export default function KbArticleEditPage() {
|
|||
</Show>
|
||||
|
||||
<div class="actions" style="justify-content:flex-end">
|
||||
<button class="btn primary" type="submit" disabled={saving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Saving...' : 'Save Article'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -49,10 +49,10 @@ export default function LeadsPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Leads Management</h1>
|
||||
<p class="page-subtitle">View all requirements and lead requests from customers.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Leads Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">View all requirements and lead requests from customers.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -92,9 +92,9 @@ export default function LeadsPage() {
|
|||
</Show>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
|
|
@ -102,7 +102,7 @@ export default function LeadsPage() {
|
|||
<th>Budget</th>
|
||||
<th>Location</th>
|
||||
<th>Status</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -133,13 +133,13 @@ export default function LeadsPage() {
|
|||
</td>
|
||||
<td style="color:#475569">{item.location || '—'}</td>
|
||||
<td>
|
||||
<span class={`status-chip ${(item.status === 'ACTIVE' || item.status === 'OPEN') ? 'active' : ''}`}>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${(item.status === 'ACTIVE' || item.status === 'OPEN') ? 'active' : ''}`}>
|
||||
{item.status || '—'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/leads/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/leads/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -73,24 +73,24 @@ export default function LeadDetailPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Lead Detail</h1>
|
||||
<p class="page-subtitle">Review one lead and its linked requirement identifiers.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Lead Detail</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review one lead and its linked requirement identifiers.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/leads">Back to Leads</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/leads">Back to Leads</A>
|
||||
</div>
|
||||
|
||||
<Show when={bundle.loading}>
|
||||
<div class="card"><p class="notice">Loading lead...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading lead...</p></div>
|
||||
</Show>
|
||||
<Show when={!bundle.loading && !lead()}>
|
||||
<div class="card"><p class="notice">Lead not found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Lead not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={lead()}>
|
||||
<section class="card" style="padding:20px">
|
||||
<div class="field-grid-2">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:20px">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="kv-item">
|
||||
<p class="kv-label">Lead ID</p>
|
||||
<p class="kv-value">{lead()!.id}</p>
|
||||
|
|
@ -128,7 +128,7 @@ export default function LeadDetailPage() {
|
|||
</div>
|
||||
<div class="actions">
|
||||
<Show when={requirementId()}>
|
||||
<A class="btn" href={`/admin/jobs/${requirementId()}`}>Open Linked Requirement/Job</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/jobs/${requirementId()}`}>Open Linked Requirement/Job</A>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
|
|
@ -139,7 +139,7 @@ export default function LeadDetailPage() {
|
|||
</div>
|
||||
<p style="margin:0;font-size:17px;font-weight:700;color:#111827">{requirement()!.title || 'Untitled Requirement'}</p>
|
||||
<p class="notice" style="margin-top:8px">{requirement()!.description || 'No description available.'}</p>
|
||||
<div class="field-grid-2" style="margin-top:12px">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2" style="margin-top:12px">
|
||||
<div class="kv-item">
|
||||
<p class="kv-label">Customer</p>
|
||||
<p class="kv-value">{requirement()!.customerName || 'Customer'}</p>
|
||||
|
|
|
|||
|
|
@ -56,16 +56,16 @@ export default function LedgerPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Ledger Management</h1>
|
||||
<p class="page-subtitle">Platform financial ledger</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Ledger Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Platform financial ledger</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export default function MakeupArtistPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Makeup Artist Management</h1>
|
||||
<p class="page-subtitle">Manage all makeup artist accounts on the platform.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Makeup Artist Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all makeup artist accounts on the platform.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -64,15 +64,15 @@ export default function MakeupArtistPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Registered</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -93,22 +93,22 @@ export default function MakeupArtistPage() {
|
|||
<td style="color:#475569">{item.email}</td>
|
||||
<td>
|
||||
{item.status?.toUpperCase() === 'ACTIVE' && (
|
||||
<span class="status-chip active">ACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'INACTIVE' && (
|
||||
<span class="status-chip">INACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'PENDING' && (
|
||||
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
)}
|
||||
{!item.status && <span class="status-chip">—</span>}
|
||||
{!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">—</span>}
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/users/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -107,32 +107,32 @@ export default function ModulesPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Module Registry</h1>
|
||||
<p class="page-subtitle">Manage internal module definitions and activation state.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Module Registry</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage internal module definitions and activation state.</p>
|
||||
</div>
|
||||
<button class="btn primary" onClick={() => openModal()}>Add Module</button>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={() => openModal()}>Add Module</button>
|
||||
</div>
|
||||
|
||||
<Show when={error() && !isModalOpen()}>
|
||||
<div class="card"><p class="error-note">{error()}</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="error-note">{error()}</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={modules.loading}>
|
||||
<div class="card"><p class="notice">Loading modules...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading modules...</p></div>
|
||||
</Show>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Key</th>
|
||||
<th>Description</th>
|
||||
<th>Status</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -150,10 +150,10 @@ export default function ModulesPage() {
|
|||
{item.isActive ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-right">
|
||||
<div class="table-actions">
|
||||
<button class="btn" onClick={() => openModal(item)}>Edit</button>
|
||||
<button class="btn danger" onClick={() => removeModule(item.id)}>Delete</button>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => openModal(item)}>Edit</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeModule(item.id)}>Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -211,8 +211,8 @@ export default function ModulesPage() {
|
|||
</Show>
|
||||
|
||||
<div class="actions" style="justify-content:flex-end">
|
||||
<button type="button" class="btn" onClick={closeModal}>Cancel</button>
|
||||
<button type="submit" class="btn primary" disabled={submitting()}>
|
||||
<button type="button" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={closeModal}>Cancel</button>
|
||||
<button type="submit" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={submitting()}>
|
||||
{editing() ? 'Save Changes' : 'Create'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -83,13 +83,13 @@ export default function NotificationsPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Notifications</h1>
|
||||
<p class="page-subtitle">Approval outcomes and action-required updates</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Notifications</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Approval outcomes and action-required updates</p>
|
||||
</div>
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
onClick={handleMarkAllRead}
|
||||
disabled={markingAllRead()}
|
||||
>
|
||||
|
|
@ -115,16 +115,16 @@ export default function NotificationsPage() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Message</th>
|
||||
<th>Event Type</th>
|
||||
<th>Created At</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -148,10 +148,10 @@ export default function NotificationsPage() {
|
|||
{item.created_at ? new Date(item.created_at).toLocaleString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<Show when={item.read_at === null}>
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
disabled={markingId() === item.id}
|
||||
onClick={() => handleMarkRead(item.id)}
|
||||
>
|
||||
|
|
@ -175,7 +175,7 @@ export default function NotificationsPage() {
|
|||
<Show when={cursor() && !loading()}>
|
||||
<div style="margin-top:12px">
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
onClick={() => load(cursor(), false)}
|
||||
>
|
||||
Load More
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default function OnboardingManagementAliasPage() {
|
|||
onMount(() => navigate('/admin/onboarding-schemas', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card"><p class="notice">Redirecting to onboarding management...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to onboarding management...</p></div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,34 +135,34 @@ export default function OnboardingSchemaDetailPage() {
|
|||
<AdminShell>
|
||||
<OnboardingManagementTabs />
|
||||
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Onboarding Management</h1>
|
||||
<p class="page-subtitle">Open one onboarding form at a time, check if it is published, then update the role, questions, steps, and final success message.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Onboarding Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Open one onboarding form at a time, check if it is published, then update the role, questions, steps, and final success message.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<button class="btn" type="button" disabled={saving()} onClick={() => void persist(true)}>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" disabled={saving()} onClick={() => void persist(true)}>
|
||||
Save Active Version
|
||||
</button>
|
||||
<A class="btn" href="/admin/onboarding-schemas">Back to Onboarding Management</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/onboarding-schemas">Back to Onboarding Management</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={schema.loading}>
|
||||
<div class="card"><p class="notice">Loading onboarding flow...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading onboarding flow...</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={!schema.loading && !schema()}>
|
||||
<div class="card"><p class="notice">Onboarding flow not found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Onboarding flow not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={schema() && loaded()}>
|
||||
<>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-bottom:16px">
|
||||
<div class="card"><p class="kv-label">Status</p><p class="kv-value">{schema()?.is_active ? 'PUBLISHED' : 'DRAFT'}</p></div>
|
||||
<div class="card"><p class="kv-label">Version</p><p class="kv-value">{schema()?.schema_json?.version || 1}</p></div>
|
||||
<div class="card"><p class="kv-label">Steps</p><p class="kv-value">{stepCount()}</p></div>
|
||||
<div class="card"><p class="kv-label">Questions</p><p class="kv-value">{selectedFields().length}</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Status</p><p class="kv-value">{schema()?.is_active ? 'PUBLISHED' : 'DRAFT'}</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Version</p><p class="kv-value">{schema()?.schema_json?.version || 1}</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Steps</p><p class="kv-value">{stepCount()}</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Questions</p><p class="kv-value">{selectedFields().length}</p></div>
|
||||
</div>
|
||||
|
||||
<OnboardingFlowBuilder
|
||||
|
|
|
|||
|
|
@ -93,29 +93,29 @@ export default function OnboardingSchemasPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Onboarding Management</h1>
|
||||
<p class="page-subtitle">Manage onboarding flows, role assignments, and previewable step groups for external users.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Onboarding Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage onboarding flows, role assignments, and previewable step groups for external users.</p>
|
||||
</div>
|
||||
<A class="btn navy" href="/admin/onboarding-schemas/new">Create Onboarding Flow</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/onboarding-schemas/new">Create Onboarding Flow</A>
|
||||
</div>
|
||||
|
||||
<OnboardingManagementTabs />
|
||||
|
||||
<Show when={deleteError()}>
|
||||
<div class="error-box">{deleteError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{deleteError()}</div>
|
||||
</Show>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0">
|
||||
<h2 style="margin:0;font-size:17px;font-weight:700">Onboarding Flows</h2>
|
||||
<Show when={!schemas.loading}>
|
||||
<span style="font-size:13px;color:#64748b">{schemas()?.length || 0} flows</span>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Flow</th>
|
||||
|
|
@ -123,7 +123,7 @@ export default function OnboardingSchemasPage() {
|
|||
<th>Steps</th>
|
||||
<th>Version</th>
|
||||
<th>Status</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -144,13 +144,13 @@ export default function OnboardingSchemasPage() {
|
|||
<td style="color:#475569">{schema.stepCount}</td>
|
||||
<td style="color:#475569">v{schema.version}</td>
|
||||
<td>
|
||||
<span class={`status-chip ${schema.status === 'PUBLISHED' ? 'active' : ''}`}>{schema.status}</span>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${schema.status === 'PUBLISHED' ? 'active' : ''}`}>{schema.status}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="action-icon-btn" href={`/admin/onboarding-schemas/${schema.roleId || schema.id}`} title="Open Flow">👁</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/onboarding-schemas/${schema.roleId || schema.id}`} title="Open Flow">👁</A>
|
||||
<button
|
||||
class="action-icon-btn danger"
|
||||
class="rounded p-1.5 text-red-500 hover:bg-red-50 hover:text-red-700 text-sm"
|
||||
disabled={deleting() === schema.id}
|
||||
onClick={() => handleDelete(schema.id, schema.title)}
|
||||
title="Delete Flow"
|
||||
|
|
|
|||
|
|
@ -107,18 +107,18 @@ export default function NewOnboardingSchemaPage() {
|
|||
<AdminShell>
|
||||
<OnboardingManagementTabs />
|
||||
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Create Onboarding Flow</h1>
|
||||
<p class="page-subtitle">Create one onboarding form at a time. Pick the role, choose the questions, set the steps, and write the final success message.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Create Onboarding Flow</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Create one onboarding form at a time. Pick the role, choose the questions, set the steps, and write the final success message.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/onboarding-schemas">Back to Onboarding Management</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/onboarding-schemas">Back to Onboarding Management</A>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-bottom:16px">
|
||||
<div class="card"><p class="kv-label">Role</p><p class="kv-value">{roleKey().replace(/_/g, ' ').toUpperCase()}</p></div>
|
||||
<div class="card"><p class="kv-label">Steps</p><p class="kv-value">{stepCount()}</p></div>
|
||||
<div class="card"><p class="kv-label">Questions</p><p class="kv-value">{selectedFields().length}</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Role</p><p class="kv-value">{roleKey().replace(/_/g, ' ').toUpperCase()}</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Steps</p><p class="kv-value">{stepCount()}</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Questions</p><p class="kv-value">{selectedFields().length}</p></div>
|
||||
</div>
|
||||
|
||||
<OnboardingFlowBuilder
|
||||
|
|
|
|||
|
|
@ -70,10 +70,10 @@ export default function OrderPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Order Management</h1>
|
||||
<p class="page-subtitle">TraceCoin package purchase orders</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Order Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">TraceCoin package purchase orders</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -87,9 +87,9 @@ export default function OrderPage() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order #</th>
|
||||
|
|
@ -124,7 +124,7 @@ export default function OrderPage() {
|
|||
<td style="color:#475569">{formatAmount(item)}</td>
|
||||
<td>
|
||||
<span
|
||||
class="status-chip"
|
||||
class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"
|
||||
style={`${statusStyle(item.status)};padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;border:1px solid`}
|
||||
>
|
||||
{item.status || '—'}
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export default function PhotographerPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Photographer Management</h1>
|
||||
<p class="page-subtitle">Manage all photographer accounts on the platform.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Photographer Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all photographer accounts on the platform.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -64,15 +64,15 @@ export default function PhotographerPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Registered</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -93,22 +93,22 @@ export default function PhotographerPage() {
|
|||
<td style="color:#475569">{item.email}</td>
|
||||
<td>
|
||||
{item.status?.toUpperCase() === 'ACTIVE' && (
|
||||
<span class="status-chip active">ACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'INACTIVE' && (
|
||||
<span class="status-chip">INACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'PENDING' && (
|
||||
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
)}
|
||||
{!item.status && <span class="status-chip">—</span>}
|
||||
{!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">—</span>}
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/photographer/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/photographer/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -50,27 +50,27 @@ export default function PhotographerDetailPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Photographer Detail</h1>
|
||||
<p class="page-subtitle">View profile snapshot and account metadata for one photographer.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Photographer Detail</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">View profile snapshot and account metadata for one photographer.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href="/admin/photographer">Back to Photographer List</A>
|
||||
<A class="btn" href={`/admin/users/details/${params.id}`}>Open User Detail</A>
|
||||
<div class="flex items-center gap-2">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/photographer">Back to Photographer List</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/details/${params.id}`}>Open User Detail</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={profile.loading}>
|
||||
<div class="card"><p class="notice">Loading photographer...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading photographer...</p></div>
|
||||
</Show>
|
||||
<Show when={!profile.loading && !profile()}>
|
||||
<div class="card"><p class="notice">Photographer not found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Photographer not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={profile()}>
|
||||
<section class="card">
|
||||
<div class="field-grid-2">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="kv-item">
|
||||
<p class="kv-label">Name</p>
|
||||
<p class="kv-value">{name()}</p>
|
||||
|
|
|
|||
|
|
@ -150,10 +150,10 @@ export default function PricingPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Pricing Management</h1>
|
||||
<p class="page-subtitle">Create and manage TraceCoin packages</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Pricing Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Create and manage TraceCoin packages</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -177,9 +177,9 @@ export default function PricingPage() {
|
|||
|
||||
{/* Packages list tab */}
|
||||
<Show when={view() === 'packages'}>
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
|
|
@ -188,7 +188,7 @@ export default function PricingPage() {
|
|||
<th>Price (₹)</th>
|
||||
<th>Bonus (%)</th>
|
||||
<th>Status</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -212,15 +212,15 @@ export default function PricingPage() {
|
|||
<td style="color:#475569">₹{(pkg.price_inr / 100).toFixed(2)}</td>
|
||||
<td style="color:#475569">{pkg.bonus_percentage != null ? `${pkg.bonus_percentage}%` : '—'}</td>
|
||||
<td>
|
||||
<span class={`status-chip ${pkg.is_active ? 'active' : ''}`}>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${pkg.is_active ? 'active' : ''}`}>
|
||||
{pkg.is_active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<button class="btn" onClick={() => startEdit(pkg)}>Edit</button>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => startEdit(pkg)}>Edit</button>
|
||||
<button
|
||||
class={pkg.is_active ? 'btn danger' : 'btn navy'}
|
||||
class={pkg.is_active ? 'inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors' : 'inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors'}
|
||||
disabled={togglingId() === pkg.id}
|
||||
onClick={() => toggleActive(pkg)}
|
||||
>
|
||||
|
|
@ -233,7 +233,7 @@ export default function PricingPage() {
|
|||
<tr>
|
||||
<td colspan="7" style="background:#f8fafc;padding:16px">
|
||||
<Show when={editError()}>
|
||||
<div class="error-box" style="margin-bottom:10px">{editError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:10px">{editError()}</div>
|
||||
</Show>
|
||||
<div style="display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end">
|
||||
<div class="field">
|
||||
|
|
@ -264,10 +264,10 @@ export default function PricingPage() {
|
|||
/>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="btn navy" disabled={editSaving()} onClick={() => saveEdit(pkg.id)}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={editSaving()} onClick={() => saveEdit(pkg.id)}>
|
||||
{editSaving() ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
<button class="btn" onClick={cancelEdit}>Cancel</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={cancelEdit}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -285,10 +285,10 @@ export default function PricingPage() {
|
|||
|
||||
{/* Create Package tab */}
|
||||
<Show when={view() === 'create'}>
|
||||
<section class="card" style="max-width:480px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:480px">
|
||||
<h2 style="margin:0 0 20px;font-size:16px;font-weight:700">New Package</h2>
|
||||
<Show when={cError()}>
|
||||
<div class="error-box" style="margin-bottom:14px">{cError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:14px">{cError()}</div>
|
||||
</Show>
|
||||
<form onSubmit={handleCreate} style="display:flex;flex-direction:column;gap:14px">
|
||||
<div class="field">
|
||||
|
|
@ -349,7 +349,7 @@ export default function PricingPage() {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn navy" type="submit" disabled={cSaving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={cSaving()}>
|
||||
{cSaving() ? 'Creating...' : 'Create Package'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@ export default function ProfileAliasPage() {
|
|||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
onMount(() => navigate(`/admin/users/details/${params.id}`, { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to user profile detail...</p></div></AdminShell>;
|
||||
return <AdminShell><div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to user profile detail...</p></div></AdminShell>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,14 +46,14 @@ export default function ReportPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Report Management</h1>
|
||||
<p class="page-subtitle">View platform analytics and generate reports.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Report Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">View platform analytics and generate reports.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="margin-bottom:16px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
|
||||
<h2 style="margin:0 0 16px;font-size:16px;font-weight:700">Date Range</h2>
|
||||
<form onSubmit={handleLoad} style="display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap">
|
||||
<div>
|
||||
|
|
@ -76,40 +76,40 @@ export default function ReportPage() {
|
|||
style="padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px"
|
||||
/>
|
||||
</div>
|
||||
<button class="btn navy" type="submit" disabled={loading()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={loading()}>
|
||||
{loading() ? 'Loading...' : 'Load Report'}
|
||||
</button>
|
||||
</form>
|
||||
<Show when={error()}>
|
||||
<div class="error-box" style="margin-top:12px">{error()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-top:12px">{error()}</div>
|
||||
</Show>
|
||||
</section>
|
||||
|
||||
<Show when={userReport() || revenueReport()}>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;margin-bottom:16px">
|
||||
<div class="card" style="text-align:center">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="text-align:center">
|
||||
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Total Users</p>
|
||||
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{userReport()?.total_users ?? '—'}</p>
|
||||
</div>
|
||||
<div class="card" style="text-align:center">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="text-align:center">
|
||||
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">New Users</p>
|
||||
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{userReport()?.new_users ?? '—'}</p>
|
||||
</div>
|
||||
<div class="card" style="text-align:center">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="text-align:center">
|
||||
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Active Users</p>
|
||||
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{userReport()?.active_users ?? '—'}</p>
|
||||
</div>
|
||||
<div class="card" style="text-align:center">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="text-align:center">
|
||||
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Total Revenue</p>
|
||||
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">
|
||||
{revenueReport()?.total_revenue != null ? `₹${(revenueReport()!.total_revenue! / 100).toFixed(2)}` : '—'}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card" style="text-align:center">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="text-align:center">
|
||||
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">Total Orders</p>
|
||||
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{revenueReport()?.total_orders ?? '—'}</p>
|
||||
</div>
|
||||
<div class="card" style="text-align:center">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="text-align:center">
|
||||
<p style="margin:0 0 8px;font-size:13px;color:#64748b;font-weight:600">TraceCoins Sold</p>
|
||||
<p style="margin:0;font-size:28px;font-weight:700;color:#0f172a">{revenueReport()?.total_tracecoins_sold ?? '—'}</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -40,25 +40,25 @@ export default function RequirementDetailPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Requirement Request</h1>
|
||||
<p class="page-subtitle">Review full requirement request details before approval action.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Requirement Request</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review full requirement request details before approval action.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/approval">Back to Approval Management</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval">Back to Approval Management</A>
|
||||
</div>
|
||||
|
||||
<Show when={requirement.loading}>
|
||||
<div class="card"><p class="notice">Loading requirement...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading requirement...</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={!requirement.loading && !requirement()}>
|
||||
<div class="card"><p class="notice">Requirement not found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Requirement not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={requirement()}>
|
||||
<section class="card">
|
||||
<div class="field-grid-2">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<p class="hint">Title</p>
|
||||
<p style="margin:6px 0 0;font-weight:700;color:#0f172a">{requirement()!.title || '—'}</p>
|
||||
|
|
|
|||
|
|
@ -77,18 +77,18 @@ export default function ResponsesPage() {
|
|||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card">
|
||||
<h1 class="page-title">Responses</h1>
|
||||
<p class="page-subtitle">Track professional responses and move them through shortlist, accept, or reject states.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Responses</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Track professional responses and move them through shortlist, accept, or reject states.</p>
|
||||
</div>
|
||||
|
||||
<Show when={payload.loading}>
|
||||
<div class="card"><p class="notice">Loading responses...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading responses...</p></div>
|
||||
</Show>
|
||||
<Show when={error()}>
|
||||
<div class="card"><p class="error-note">{error()}</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="error-note">{error()}</p></div>
|
||||
</Show>
|
||||
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<Show when={!payload.loading && responses().length === 0} fallback={
|
||||
<div class="list-grid" style="grid-template-columns:1fr;gap:12px">
|
||||
<For each={responses()}>
|
||||
|
|
@ -106,11 +106,11 @@ export default function ResponsesPage() {
|
|||
</div>
|
||||
<div class="actions">
|
||||
<Show when={row.status === 'SUBMITTED'}>
|
||||
<button class="btn" disabled={busyId() === row.id} onClick={() => transition(row.id, 'SHORTLISTED')}>Shortlist</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" disabled={busyId() === row.id} onClick={() => transition(row.id, 'SHORTLISTED')}>Shortlist</button>
|
||||
</Show>
|
||||
<Show when={row.status === 'SUBMITTED' || row.status === 'SHORTLISTED'}>
|
||||
<button class="btn primary" disabled={busyId() === row.id} onClick={() => transition(row.id, 'ACCEPTED')}>Accept</button>
|
||||
<button class="btn danger" disabled={busyId() === row.id} onClick={() => transition(row.id, 'REJECTED')}>Reject</button>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={busyId() === row.id} onClick={() => transition(row.id, 'ACCEPTED')}>Accept</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" disabled={busyId() === row.id} onClick={() => transition(row.id, 'REJECTED')}>Reject</button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -120,10 +120,10 @@ export default function ReviewPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Review Management</h1>
|
||||
<p class="page-subtitle">Moderate platform reviews</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Review Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Moderate platform reviews</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -155,9 +155,9 @@ export default function ReviewPage() {
|
|||
style="padding:8px 12px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;min-width:320px"
|
||||
/>
|
||||
</div>
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Reviewer</th>
|
||||
|
|
@ -165,7 +165,7 @@ export default function ReviewPage() {
|
|||
<th>Rating</th>
|
||||
<th>Title</th>
|
||||
<th>Status</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -196,17 +196,17 @@ export default function ReviewPage() {
|
|||
</td>
|
||||
<td style="color:#475569">{item.title || '—'}</td>
|
||||
<td>
|
||||
<span class={`status-chip ${isPublished ? 'active' : ''}`}
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${isPublished ? 'active' : ''}`}
|
||||
style={isPublished ? '' : 'background:#f1f5f9;color:#475569;border-color:#e2e8f0'}
|
||||
>
|
||||
{isPublished ? 'Published' : 'Hidden'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<Show when={isPublished}>
|
||||
<button
|
||||
class="btn"
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
disabled={toggling() === item.id}
|
||||
onClick={() => handleUpdateStatus(item, 'HIDDEN')}
|
||||
>
|
||||
|
|
@ -215,7 +215,7 @@ export default function ReviewPage() {
|
|||
</Show>
|
||||
<Show when={!isPublished}>
|
||||
<button
|
||||
class="btn navy"
|
||||
class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
|
||||
disabled={toggling() === item.id}
|
||||
onClick={() => handleUpdateStatus(item, 'PUBLISHED')}
|
||||
>
|
||||
|
|
@ -236,10 +236,10 @@ export default function ReviewPage() {
|
|||
</Show>
|
||||
|
||||
<Show when={activeTab() === 'create'}>
|
||||
<section class="card" style="max-width:520px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:520px">
|
||||
<h2 style="margin:0 0 20px;font-size:16px;font-weight:700">Create Review</h2>
|
||||
<Show when={formError()}>
|
||||
<div class="error-box" style="margin-bottom:12px">{formError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{formError()}</div>
|
||||
</Show>
|
||||
<form onSubmit={handleSave} style="display:flex;flex-direction:column;gap:14px">
|
||||
<div class="field">
|
||||
|
|
@ -306,7 +306,7 @@ export default function ReviewPage() {
|
|||
/>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn navy" type="submit" disabled={saving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Saving...' : 'Save Review'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@ import AdminShell from '~/components/AdminShell';
|
|||
export default function RoleModulesAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/role-ui-configs', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to role module configuration...</p></div></AdminShell>;
|
||||
return <AdminShell><div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to role module configuration...</p></div></AdminShell>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default function EditRoleUiConfigRedirectPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<p class="notice">Redirecting to External Dashboard Management...</p>
|
||||
</section>
|
||||
</AdminShell>
|
||||
|
|
|
|||
|
|
@ -77,17 +77,17 @@ export default function RoleUiConfigsViewPage() {
|
|||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card">
|
||||
<h1 class="page-title">External Dashboard Management</h1>
|
||||
<p class="page-subtitle">Read-only view of the currently published external dashboard and runtime role configuration.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">External Dashboard Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Read-only view of the currently published external dashboard and runtime role configuration.</p>
|
||||
</div>
|
||||
|
||||
<ExternalRoleTabs roleKey={roleKey()} />
|
||||
|
||||
<div style="display:grid;grid-template-columns:420px minmax(0,1fr);gap:16px">
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="list-header">
|
||||
<h2 style="margin:0">External Dashboard Roles</h2>
|
||||
<A class="btn" href="/admin/roles">Open Roles</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/roles">Open Roles</A>
|
||||
</div>
|
||||
<p class="notice" style="margin-top:2px">
|
||||
Select a role to inspect its published dashboard modules, onboarding assignment, and permissions.
|
||||
|
|
@ -124,31 +124,31 @@ export default function RoleUiConfigsViewPage() {
|
|||
</section>
|
||||
|
||||
<section style="display:flex;flex-direction:column;gap:12px">
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="list-header">
|
||||
<div>
|
||||
<h2 style="margin:0">Dashboard Detail</h2>
|
||||
<p class="notice" style="margin-top:3px">This page is the safe inspector for published external dashboard and role configuration.</p>
|
||||
</div>
|
||||
<Show when={selected()}>
|
||||
<A class="btn" href={`/admin/runtime-roles/${encodeURIComponent(selected()!.roleKey)}`}>Open Role Detail</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/runtime-roles/${encodeURIComponent(selected()!.roleKey)}`}>Open Role Detail</A>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={selected.loading}>
|
||||
<div class="card"><p class="notice">Loading config detail...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading config detail...</p></div>
|
||||
</Show>
|
||||
<Show when={!selected.loading && !selected() && roleKey()}>
|
||||
<div class="card"><p class="notice">No runtime role found for "{roleKey()}".</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">No runtime role found for "{roleKey()}".</p></div>
|
||||
</Show>
|
||||
<Show when={!selected.loading && !selected() && !roleKey()}>
|
||||
<div class="card"><p class="notice">Select an external role to view its current dashboard configuration.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Select an external role to view its current dashboard configuration.</p></div>
|
||||
</Show>
|
||||
<Show when={selected()}>
|
||||
<div style="display:flex;flex-direction:column;gap:12px">
|
||||
<section class="card">
|
||||
<div class="field-grid-2">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="kv-item"><p class="kv-label">Role Key</p><p class="kv-value">{selected()!.roleKey}</p></div>
|
||||
<div class="kv-item"><p class="kv-label">Display Name</p><p class="kv-value">{selected()!.displayName}</p></div>
|
||||
<div class="kv-item"><p class="kv-label">Vertical</p><p class="kv-value">{selected()!.vertical || '—'}</p></div>
|
||||
|
|
@ -174,7 +174,7 @@ export default function RoleUiConfigsViewPage() {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin:0 0 8px">Enabled Modules</h2>
|
||||
<Show when={selected()!.enabledModules.length > 0} fallback={<p class="notice">No modules configured.</p>}>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:8px">
|
||||
|
|
@ -183,14 +183,14 @@ export default function RoleUiConfigsViewPage() {
|
|||
</Show>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin:0 0 8px">Permission Matrix</h2>
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Module</th>
|
||||
{permissionActions.map((action) => <th class="align-right">{action}</th>)}
|
||||
{permissionActions.map((action) => <th class="text-right">{action}</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -198,7 +198,7 @@ export default function RoleUiConfigsViewPage() {
|
|||
<tr>
|
||||
<td>{moduleKey}</td>
|
||||
{permissionActions.map((action) => (
|
||||
<td class="align-right">
|
||||
<td class="text-right">
|
||||
<input type="checkbox" checked={(selected()!.permissions[moduleKey] || []).includes(action)} disabled />
|
||||
</td>
|
||||
))}
|
||||
|
|
@ -209,12 +209,12 @@ export default function RoleUiConfigsViewPage() {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin:0 0 8px">Feature Limits</h2>
|
||||
<pre class="json">{JSON.stringify(selected()!.featureLimits || {}, null, 2)}</pre>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin:0 0 8px">Published Runtime Config</h2>
|
||||
<pre class="json">{JSON.stringify(selected(), null, 2)}</pre>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default function CreateRoleUiConfigRedirectPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<p class="notice">Redirecting to External Dashboard Management...</p>
|
||||
</section>
|
||||
</AdminShell>
|
||||
|
|
|
|||
|
|
@ -112,38 +112,38 @@ export default function EditInternalRolePage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Edit Internal Role</h1>
|
||||
<p class="page-subtitle">Update role name, access areas, and permissions.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Edit Internal Role</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Update role name, access areas, and permissions.</p>
|
||||
</div>
|
||||
<A class="btn" href={`/admin/roles/${params.id}`}>Back to Role</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/roles/${params.id}`}>Back to Role</A>
|
||||
</div>
|
||||
|
||||
<nav class="admin-link-tabs" aria-label="Role Management Navigation">
|
||||
<A class="admin-link-tab active" href="/admin/roles">Internal Roles</A>
|
||||
<A class="admin-link-tab" href="/admin/runtime-roles">External Runtime Roles</A>
|
||||
<A class="admin-link-tab" href="/admin/onboarding-schemas">Onboarding Schemas</A>
|
||||
<nav class="hidden" aria-label="Role Management Navigation">
|
||||
<A class="hidden" href="/admin/roles">Internal Roles</A>
|
||||
<A class="hidden" href="/admin/runtime-roles">External Runtime Roles</A>
|
||||
<A class="hidden" href="/admin/onboarding-schemas">Onboarding Schemas</A>
|
||||
</nav>
|
||||
|
||||
<Show when={data.loading}>
|
||||
<div class="card"><p class="notice">Loading role details...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading role details...</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={data.error}>
|
||||
<div class="error-box">Failed to load role. Check that the backend is running.</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">Failed to load role. Check that the backend is running.</div>
|
||||
</Show>
|
||||
|
||||
<Show when={error()}>
|
||||
<div class="error-box">{error()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
|
||||
</Show>
|
||||
|
||||
<Show when={!data.loading && data()}>
|
||||
{/* Role Details */}
|
||||
<div class="role-form-section">
|
||||
<div class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<h3>Role Basics</h3>
|
||||
<p>Update the role name and description.</p>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Role Name <span style="color:#ef4444">*</span></label>
|
||||
<input
|
||||
|
|
@ -164,14 +164,14 @@ export default function EditInternalRolePage() {
|
|||
</div>
|
||||
|
||||
{/* Module Access */}
|
||||
<div class="role-form-section">
|
||||
<div class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<h3>Area Access</h3>
|
||||
<p>Select which areas this role can access.</p>
|
||||
<div class="module-picker">
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
{allModules().map((mod) => (
|
||||
<button
|
||||
type="button"
|
||||
class={`module-chip ${assignedModules().includes(mod) ? 'selected' : ''}`}
|
||||
class={`flex cursor-pointer items-center gap-1.5 rounded-full border border-gray-200 bg-white px-3 py-1.5 text-xs font-medium text-gray-600 hover:border-gray-300 ${assignedModules().includes(mod) ? 'selected' : ''}`}
|
||||
onClick={() => toggleModule(mod)}
|
||||
>
|
||||
<span style={`width:14px;height:14px;border-radius:3px;border:2px solid ${assignedModules().includes(mod) ? '#c2410c' : '#cbd5e1'};background:${assignedModules().includes(mod) ? '#c2410c' : '#fff'};flex-shrink:0;display:inline-block`} />
|
||||
|
|
@ -183,11 +183,11 @@ export default function EditInternalRolePage() {
|
|||
|
||||
{/* Permission Table */}
|
||||
<Show when={assignedModules().length > 0}>
|
||||
<div class="role-form-section">
|
||||
<div class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<h3>Permissions</h3>
|
||||
<p>Choose what this role can do in each selected area.</p>
|
||||
<div class="table-wrap">
|
||||
<table class="perm-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:45%">Area</th>
|
||||
|
|
@ -228,7 +228,7 @@ export default function EditInternalRolePage() {
|
|||
{/* Save */}
|
||||
<div style="display:flex;justify-content:flex-end;margin-top:8px">
|
||||
<button
|
||||
class="btn navy"
|
||||
class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
|
||||
onClick={handleSave}
|
||||
disabled={saving() || !roleName().trim()}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -43,39 +43,39 @@ export default function RoleDetailPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Role Details</h1>
|
||||
<p class="page-subtitle">View role information and assigned permissions.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Role Details</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">View role information and assigned permissions.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href="/admin/roles">Back to List</A>
|
||||
<div class="flex items-center gap-2">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/roles">Back to List</A>
|
||||
<Show when={data()?.role}>
|
||||
<A class="btn navy" href={`/admin/roles/${params.id}/edit`}>Edit Role</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/roles/${params.id}/edit`}>Edit Role</A>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="admin-link-tabs" aria-label="Role Management Navigation">
|
||||
<A class="admin-link-tab active" href="/admin/roles">Internal Roles</A>
|
||||
<A class="admin-link-tab" href="/admin/runtime-roles">External Runtime Roles</A>
|
||||
<A class="admin-link-tab" href="/admin/onboarding-schemas">Onboarding Schemas</A>
|
||||
<nav class="hidden" aria-label="Role Management Navigation">
|
||||
<A class="hidden" href="/admin/roles">Internal Roles</A>
|
||||
<A class="hidden" href="/admin/runtime-roles">External Runtime Roles</A>
|
||||
<A class="hidden" href="/admin/onboarding-schemas">Onboarding Schemas</A>
|
||||
</nav>
|
||||
|
||||
<Show when={data.loading}>
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<p class="notice">Loading role details...</p>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={data.error}>
|
||||
<div class="error-box">Failed to load role. Check that the backend is running.</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">Failed to load role. Check that the backend is running.</div>
|
||||
</Show>
|
||||
|
||||
<Show when={data()?.role}>
|
||||
{/* Role Info */}
|
||||
<div class="role-detail-card">
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Role Name</label>
|
||||
<input class="role-field-readonly" value={data()!.role.name} readOnly disabled />
|
||||
|
|
@ -88,9 +88,9 @@ export default function RoleDetailPage() {
|
|||
</div>
|
||||
|
||||
{/* Permission Matrix */}
|
||||
<div class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="perm-table">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:45%">Name of the module</th>
|
||||
|
|
|
|||
|
|
@ -93,29 +93,29 @@ export default function CreateInternalRolePage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Create Internal Role</h1>
|
||||
<p class="page-subtitle">Create a new internal role and choose what it can access.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Create Internal Role</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Create a new internal role and choose what it can access.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/roles">Back to Roles</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/roles">Back to Roles</A>
|
||||
</div>
|
||||
|
||||
<nav class="admin-link-tabs" aria-label="Role Management Navigation">
|
||||
<A class="admin-link-tab active" href="/admin/roles">Internal Roles</A>
|
||||
<A class="admin-link-tab" href="/admin/runtime-roles">External Runtime Roles</A>
|
||||
<A class="admin-link-tab" href="/admin/onboarding-schemas">Onboarding Schemas</A>
|
||||
<nav class="hidden" aria-label="Role Management Navigation">
|
||||
<A class="hidden" href="/admin/roles">Internal Roles</A>
|
||||
<A class="hidden" href="/admin/runtime-roles">External Runtime Roles</A>
|
||||
<A class="hidden" href="/admin/onboarding-schemas">Onboarding Schemas</A>
|
||||
</nav>
|
||||
|
||||
<Show when={error()}>
|
||||
<div class="error-box">{error()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
|
||||
</Show>
|
||||
|
||||
{/* Role Details */}
|
||||
<div class="role-form-section">
|
||||
<div class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<h3>Role Basics</h3>
|
||||
<p>Start by giving this role a clear name.</p>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Role Name <span style="color:#ef4444">*</span></label>
|
||||
<input
|
||||
|
|
@ -136,18 +136,18 @@ export default function CreateInternalRolePage() {
|
|||
</div>
|
||||
|
||||
{/* Module Access */}
|
||||
<div class="role-form-section">
|
||||
<div class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<h3>Area Access</h3>
|
||||
<p>Select which areas this role can access. You can set permissions for selected areas below.</p>
|
||||
<Show when={permissions.loading}>
|
||||
<p class="notice">Loading available areas...</p>
|
||||
</Show>
|
||||
<Show when={!permissions.loading && allModules().length > 0}>
|
||||
<div class="module-picker">
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
{allModules().map((mod) => (
|
||||
<button
|
||||
type="button"
|
||||
class={`module-chip ${assignedModules().includes(mod) ? 'selected' : ''}`}
|
||||
class={`flex cursor-pointer items-center gap-1.5 rounded-full border border-gray-200 bg-white px-3 py-1.5 text-xs font-medium text-gray-600 hover:border-gray-300 ${assignedModules().includes(mod) ? 'selected' : ''}`}
|
||||
onClick={() => toggleModule(mod)}
|
||||
>
|
||||
<span style={`width:14px;height:14px;border-radius:3px;border:2px solid ${assignedModules().includes(mod) ? '#c2410c' : '#cbd5e1'};background:${assignedModules().includes(mod) ? '#c2410c' : '#fff'};flex-shrink:0;display:inline-block`} />
|
||||
|
|
@ -163,11 +163,11 @@ export default function CreateInternalRolePage() {
|
|||
|
||||
{/* Permission Table */}
|
||||
<Show when={assignedModules().length > 0}>
|
||||
<div class="role-form-section">
|
||||
<div class="mb-6 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<h3>Permissions</h3>
|
||||
<p>Choose what this role can do in each selected area.</p>
|
||||
<div class="table-wrap">
|
||||
<table class="perm-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:45%">Area</th>
|
||||
|
|
@ -208,7 +208,7 @@ export default function CreateInternalRolePage() {
|
|||
{/* Save */}
|
||||
<div style="display:flex;justify-content:flex-end;margin-top:8px">
|
||||
<button
|
||||
class="btn navy"
|
||||
class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
|
||||
onClick={handleSave}
|
||||
disabled={saving() || !roleName().trim()}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -50,32 +50,32 @@ export default function InternalRolesPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Internal Role Management</h1>
|
||||
<p class="page-subtitle">Manage internal employee roles and permissions from one clean list.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Internal Role Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage internal employee roles and permissions from one clean list.</p>
|
||||
</div>
|
||||
<A class="btn navy" href="/admin/roles/create">Create Internal Role</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/roles/create">Create Internal Role</A>
|
||||
</div>
|
||||
|
||||
<nav class="admin-link-tabs" aria-label="Role Management Navigation">
|
||||
<A class="admin-link-tab active" href="/admin/roles">Internal Roles</A>
|
||||
<A class="admin-link-tab" href="/admin/runtime-roles">External Runtime Roles</A>
|
||||
<A class="admin-link-tab" href="/admin/onboarding-schemas">Onboarding Schemas</A>
|
||||
<nav class="hidden" aria-label="Role Management Navigation">
|
||||
<A class="hidden" href="/admin/roles">Internal Roles</A>
|
||||
<A class="hidden" href="/admin/runtime-roles">External Runtime Roles</A>
|
||||
<A class="hidden" href="/admin/onboarding-schemas">Onboarding Schemas</A>
|
||||
</nav>
|
||||
|
||||
<Show when={deleteError()}>
|
||||
<div class="error-box">{deleteError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{deleteError()}</div>
|
||||
</Show>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -105,11 +105,11 @@ export default function InternalRolesPage() {
|
|||
</td>
|
||||
<td style="color:#475569;">{role.description || 'No description added yet.'}</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="action-icon-btn" href={`/admin/roles/${role.id}`} title="View Role">👁</A>
|
||||
<A class="action-icon-btn" href={`/admin/roles/${role.id}/edit`} title="Edit Role">✎</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/roles/${role.id}`} title="View Role">👁</A>
|
||||
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/roles/${role.id}/edit`} title="Edit Role">✎</A>
|
||||
<button
|
||||
class="action-icon-btn danger"
|
||||
class="rounded p-1.5 text-red-500 hover:bg-red-50 hover:text-red-700 text-sm"
|
||||
disabled={deleting() === role.id}
|
||||
onClick={() => handleDelete(role.id, role.name)}
|
||||
title="Delete Role"
|
||||
|
|
|
|||
|
|
@ -36,27 +36,27 @@ export default function RoleTemplatesPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Role Templates</h1>
|
||||
<p class="page-subtitle">Starter role presets for faster internal role creation and cloning.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Role Templates</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Starter role presets for faster internal role creation and cloning.</p>
|
||||
</div>
|
||||
<A class="btn navy" href="/admin/roles/create">Create Internal Role</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/roles/create">Create Internal Role</A>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #e2e8f0">
|
||||
<h2 style="margin:0;font-size:16px">Available Templates</h2>
|
||||
<span style="font-size:12px;color:#64748b">{count()} template{count() !== 1 ? 's' : ''}</span>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Code</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -74,9 +74,9 @@ export default function RoleTemplatesPage() {
|
|||
<td style="color:#475569">{item.description || '—'}</td>
|
||||
<td style="color:#64748b;font-family:ui-monospace,SFMono-Regular,Menlo,monospace">{item.code || '—'}</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/roles/${item.id}`}>View</A>
|
||||
<A class="btn" href="/admin/roles/create">Use As Base</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/roles/${item.id}`}>View</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/roles/create">Use As Base</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -152,26 +152,26 @@ export default function EditExternalRolePage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">External Role Management</h1>
|
||||
<p class="page-subtitle">
|
||||
<h1 class="text-2xl font-bold text-gray-900">External Role Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Update this external role with simple settings: pages, permissions, onboarding form, approvals, and limits.
|
||||
</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href={`/admin/role-ui-configs?roleKey=${encodeURIComponent(roleKey())}`}>Open Inspector</A>
|
||||
<A class="btn" href="/admin/runtime-roles">Back to External Roles</A>
|
||||
<div class="flex items-center gap-2">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/role-ui-configs?roleKey=${encodeURIComponent(roleKey())}`}>Open Inspector</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/runtime-roles">Back to External Roles</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ExternalRoleTabs roleKey={roleKey()} />
|
||||
|
||||
<Show when={error()}>
|
||||
<div class="error-box">{error()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
|
||||
</Show>
|
||||
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin-bottom:6px">How Saving Works</h2>
|
||||
<p class="notice" style="margin:0">
|
||||
Saving here updates the live role settings used by the app.
|
||||
|
|
@ -179,11 +179,11 @@ export default function EditExternalRolePage() {
|
|||
</div>
|
||||
|
||||
<Show when={data.loading}>
|
||||
<div class="card"><p class="notice">Loading external role...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading external role...</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={!data.loading && !data()}>
|
||||
<div class="card"><p class="notice">Role "{roleKey()}" not found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Role "{roleKey()}" not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={!data.loading && data()}>
|
||||
|
|
|
|||
|
|
@ -77,20 +77,20 @@ export default function RuntimeRolesPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">External Role Management</h1>
|
||||
<p class="page-subtitle">Manage canonical external runtime roles, enabled modules, onboarding assignment, approval gates, and default runtime destinations from one place.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">External Role Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage canonical external runtime roles, enabled modules, onboarding assignment, approval gates, and default runtime destinations from one place.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href="/admin/role-ui-configs">Inspector</A>
|
||||
<A class="btn navy" href="/admin/runtime-roles/new">Create External Role</A>
|
||||
<div class="flex items-center gap-2">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/role-ui-configs">Inspector</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/runtime-roles/new">Create External Role</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ExternalRoleTabs />
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0">
|
||||
<div>
|
||||
<h2 style="margin:0;font-size:17px;font-weight:700">Published External Roles</h2>
|
||||
|
|
@ -100,8 +100,8 @@ export default function RuntimeRolesPage() {
|
|||
<span style="font-size:13px;color:#64748b">{roles()?.length || 0} roles</span>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Role</th>
|
||||
|
|
@ -109,7 +109,7 @@ export default function RuntimeRolesPage() {
|
|||
<th>Modules</th>
|
||||
<th>Schema</th>
|
||||
<th>Status</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -124,7 +124,7 @@ export default function RuntimeRolesPage() {
|
|||
</Show>
|
||||
<Show when={!roles.loading && !roles.error && (roles()?.length ?? 0) > 0}>
|
||||
{roles()!.map((role) => (
|
||||
<tr class={selectedRoleKey() === role.roleKey.toLowerCase() ? 'row-selected' : ''}>
|
||||
<tr class={selectedRoleKey() === role.roleKey.toLowerCase() ? 'bg-orange-50' : ''}>
|
||||
<td>
|
||||
<div>
|
||||
<p style="margin:0;font-weight:600;color:#0f172a">{role.displayName}</p>
|
||||
|
|
@ -135,14 +135,14 @@ export default function RuntimeRolesPage() {
|
|||
<td style="color:#475569">{role.enabledModules.length}</td>
|
||||
<td style="color:#475569;font-size:12px">{role.onboardingSchemaId || '—'}</td>
|
||||
<td>
|
||||
<span class={`status-chip ${role.isActive ? 'active' : ''}`}>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${role.isActive ? 'active' : ''}`}>
|
||||
{role.isActive ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="action-icon-btn" href={`/admin/runtime-roles/${encodeURIComponent(role.roleKey)}`} target="_blank" rel="noreferrer" title="View External Role">👁</A>
|
||||
<A class="action-icon-btn" href={`/admin/runtime-roles/${encodeURIComponent(role.roleKey)}`} title="Edit External Role">✎</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/runtime-roles/${encodeURIComponent(role.roleKey)}`} target="_blank" rel="noreferrer" title="View External Role">👁</A>
|
||||
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/runtime-roles/${encodeURIComponent(role.roleKey)}`} title="Edit External Role">✎</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -84,20 +84,20 @@ export default function CreateExternalRolePage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Create External Role</h1>
|
||||
<p class="page-subtitle">
|
||||
<h1 class="text-2xl font-bold text-gray-900">Create External Role</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Create a new external role and choose what it can access in the app.
|
||||
</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/runtime-roles">Back to External Roles</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/runtime-roles">Back to External Roles</A>
|
||||
</div>
|
||||
|
||||
<ExternalRoleTabs />
|
||||
|
||||
<Show when={error()}>
|
||||
<div class="error-box">{error()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
|
||||
</Show>
|
||||
|
||||
<ExternalRoleForm
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@ import AdminShell from '~/components/AdminShell';
|
|||
export default function SettingsAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/internal-dashboard-management', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to settings/configuration...</p></div></AdminShell>;
|
||||
return <AdminShell><div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to settings/configuration...</p></div></AdminShell>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export default function SocialMediaManagersPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Social Media Manager Management</h1>
|
||||
<p class="page-subtitle">Manage all social media manager accounts on the platform.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Social Media Manager Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all social media manager accounts on the platform.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -64,15 +64,15 @@ export default function SocialMediaManagersPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Registered</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -93,22 +93,22 @@ export default function SocialMediaManagersPage() {
|
|||
<td style="color:#475569">{item.email}</td>
|
||||
<td>
|
||||
{item.status?.toUpperCase() === 'ACTIVE' && (
|
||||
<span class="status-chip active">ACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'INACTIVE' && (
|
||||
<span class="status-chip">INACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'PENDING' && (
|
||||
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
)}
|
||||
{!item.status && <span class="status-chip">—</span>}
|
||||
{!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">—</span>}
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/users/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -158,10 +158,10 @@ export default function SupportPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Support Management</h1>
|
||||
<p class="page-subtitle">Handle platform issues and customer queries</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Support Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Handle platform issues and customer queries</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -211,9 +211,9 @@ export default function SupportPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Issue</th>
|
||||
|
|
@ -222,7 +222,7 @@ export default function SupportPage() {
|
|||
<th>Status</th>
|
||||
<th>Requester</th>
|
||||
<th>Updated At</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -260,8 +260,8 @@ export default function SupportPage() {
|
|||
{item.updatedAt ? new Date(item.updatedAt).toLocaleString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/support/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/support/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -277,7 +277,7 @@ export default function SupportPage() {
|
|||
|
||||
{/* Create Case Tab */}
|
||||
<Show when={activeTab() === 'create'}>
|
||||
<section class="card" style="max-width:600px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:600px">
|
||||
<h2 style="margin:0 0 6px;font-size:16px;font-weight:700;color:#1e293b">Create Support Case</h2>
|
||||
<p style="margin:0 0 20px;font-size:13px;color:#64748b">
|
||||
Create an internal support record for platform issues, customer concerns, or compensation-related reviews.
|
||||
|
|
@ -288,7 +288,7 @@ export default function SupportPage() {
|
|||
</div>
|
||||
</Show>
|
||||
<Show when={createError()}>
|
||||
<div class="error-box" style="margin-bottom:14px">{createError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:14px">{createError()}</div>
|
||||
</Show>
|
||||
<form onSubmit={handleCreate} style="display:flex;flex-direction:column;gap:14px">
|
||||
<div class="field">
|
||||
|
|
@ -311,7 +311,7 @@ export default function SupportPage() {
|
|||
style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;resize:vertical;box-sizing:border-box"
|
||||
/>
|
||||
</div>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Type</label>
|
||||
<select
|
||||
|
|
@ -337,7 +337,7 @@ export default function SupportPage() {
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-grid-2">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:4px">Requester Name</label>
|
||||
<input
|
||||
|
|
@ -358,7 +358,7 @@ export default function SupportPage() {
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn navy" type="submit" disabled={createLoading()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={createLoading()}>
|
||||
{createLoading() ? 'Creating...' : 'Create Support Case'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -64,21 +64,21 @@ export default function TaxPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Tax Management</h1>
|
||||
<p class="page-subtitle">Configure tax rates for platform transactions.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Tax Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Configure tax rates for platform transactions.</p>
|
||||
</div>
|
||||
<button class="btn navy" onClick={() => setShowForm(!showForm())}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={() => setShowForm(!showForm())}>
|
||||
{showForm() ? 'Cancel' : 'Add Tax'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Show when={showForm()}>
|
||||
<section class="card" style="margin-bottom:16px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
|
||||
<h2 style="margin:0 0 16px;font-size:16px;font-weight:700">New Tax</h2>
|
||||
<Show when={formError()}>
|
||||
<div class="error-box" style="margin-bottom:12px">{formError()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{formError()}</div>
|
||||
</Show>
|
||||
<form onSubmit={handleSave} style="display:flex;flex-direction:column;gap:12px;max-width:400px">
|
||||
<div>
|
||||
|
|
@ -114,7 +114,7 @@ export default function TaxPage() {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn navy" type="submit" disabled={saving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Saving...' : 'Save Tax'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -122,16 +122,16 @@ export default function TaxPage() {
|
|||
</section>
|
||||
</Show>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Rate (%)</th>
|
||||
<th>Description</th>
|
||||
<th>Status</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -151,15 +151,15 @@ export default function TaxPage() {
|
|||
<td style="color:#475569">{item.rate}%</td>
|
||||
<td style="color:#475569">{item.description || '—'}</td>
|
||||
<td>
|
||||
<span class={`status-chip ${item.is_active !== false ? 'active' : ''}`}>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${item.is_active !== false ? 'active' : ''}`}>
|
||||
{item.is_active !== false ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<a class="btn" href={`/admin/tax/${item.id}/edit`}>Edit</a>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<a class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/tax/${item.id}/edit`}>Edit</a>
|
||||
<button
|
||||
class="btn danger"
|
||||
class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
|
||||
disabled={deleting() === item.id}
|
||||
onClick={() => handleDelete(item.id, item.name)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export default function TutorsPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Tutors Management</h1>
|
||||
<p class="page-subtitle">Manage all tutor accounts on the platform.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Tutors Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all tutor accounts on the platform.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -64,15 +64,15 @@ export default function TutorsPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Registered</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -93,22 +93,22 @@ export default function TutorsPage() {
|
|||
<td style="color:#475569">{item.email}</td>
|
||||
<td>
|
||||
{item.status?.toUpperCase() === 'ACTIVE' && (
|
||||
<span class="status-chip active">ACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'INACTIVE' && (
|
||||
<span class="status-chip">INACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'PENDING' && (
|
||||
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
)}
|
||||
{!item.status && <span class="status-chip">—</span>}
|
||||
{!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">—</span>}
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/users/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -53,12 +53,12 @@ async function fetchUsers(): Promise<User[]> {
|
|||
|
||||
function StatusBadge(props: { status: string }) {
|
||||
if (props.status === 'ACTIVE') {
|
||||
return <span class="status-chip active">ACTIVE</span>;
|
||||
return <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>;
|
||||
}
|
||||
if (props.status === 'PENDING') {
|
||||
return <span class="status-chip" style="background:#f59e0b;color:#fff">PENDING</span>;
|
||||
return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#f59e0b;color:#fff">PENDING</span>;
|
||||
}
|
||||
return <span class="status-chip">INACTIVE</span>;
|
||||
return <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>;
|
||||
}
|
||||
|
||||
export default function UsersPage() {
|
||||
|
|
@ -107,10 +107,10 @@ export default function UsersPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">External User Management</h1>
|
||||
<p class="page-subtitle">Manage all external platform users.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">External User Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all external platform users.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ export default function UsersPage() {
|
|||
|
||||
{/* Filters */}
|
||||
<Show when={activeTab() === 'list'}>
|
||||
<div class="card" style="margin-bottom:16px;display:flex;gap:12px;flex-wrap:wrap;align-items:center;">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px;display:flex;gap:12px;flex-wrap:wrap;align-items:center;">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by name or email..."
|
||||
|
|
@ -180,9 +180,9 @@ export default function UsersPage() {
|
|||
</Show>
|
||||
|
||||
<Show when={activeTab() === 'list'}>
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User ID</th>
|
||||
|
|
@ -191,7 +191,7 @@ export default function UsersPage() {
|
|||
<th>Registration Role</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -221,11 +221,11 @@ export default function UsersPage() {
|
|||
: '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="action-icon-btn" href={`/admin/users/details/${item.id}`} title="Open Detail Page">↗</A>
|
||||
<button class="action-icon-btn" type="button" onClick={() => onView(item)} title="Quick View">👁</button>
|
||||
<A class="action-icon-btn" href={`/admin/users/${item.id}/edit`} title="Edit User">✎</A>
|
||||
<button class="action-icon-btn danger" type="button" title="Delete User">🗑</button>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/users/details/${item.id}`} title="Open Detail Page">↗</A>
|
||||
<button class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" type="button" onClick={() => onView(item)} title="Quick View">👁</button>
|
||||
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/users/${item.id}/edit`} title="Edit User">✎</A>
|
||||
<button class="rounded p-1.5 text-red-500 hover:bg-red-50 hover:text-red-700 text-sm" type="button" title="Delete User">🗑</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -236,11 +236,11 @@ export default function UsersPage() {
|
|||
</table>
|
||||
</div>
|
||||
<div class="admin-pagination">
|
||||
<button class="btn" type="button" disabled={currentPage() <= 1} onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" disabled={currentPage() <= 1} onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}>
|
||||
Previous
|
||||
</button>
|
||||
<span>Page {currentPage()} of {totalPages()}</span>
|
||||
<button class="btn" type="button" disabled={currentPage() >= totalPages()} onClick={() => setCurrentPage((p) => Math.min(totalPages(), p + 1))}>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" disabled={currentPage() >= totalPages()} onClick={() => setCurrentPage((p) => Math.min(totalPages(), p + 1))}>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -248,11 +248,11 @@ export default function UsersPage() {
|
|||
</Show>
|
||||
|
||||
<Show when={activeTab() === 'view'}>
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<Show when={selectedUser()} fallback={<p class="notice">Select a user from list to view details.</p>}>
|
||||
<div class="list-header">
|
||||
<h2>User Details</h2>
|
||||
<button class="btn" type="button" onClick={() => setActiveTab('list')}>Back To List</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={() => setActiveTab('list')}>Back To List</button>
|
||||
</div>
|
||||
<div class="grid" style="margin-top:0">
|
||||
<div class="list-item">
|
||||
|
|
@ -268,8 +268,8 @@ export default function UsersPage() {
|
|||
<p><strong>Created:</strong> {(selectedUser()!.created_at || selectedUser()!.createdAt) ? new Date((selectedUser()!.created_at || selectedUser()!.createdAt)!).toLocaleString() : '—'}</p>
|
||||
<p><strong>Role ID:</strong> {selectedUser()!.roleId || '—'}</p>
|
||||
<div class="actions">
|
||||
<A class="btn navy" href={`/admin/users/${selectedUser()!.id}/edit`}>Edit User</A>
|
||||
<button class="btn danger" type="button">Deactivate</button>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/users/${selectedUser()!.id}/edit`}>Edit User</A>
|
||||
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" type="button">Deactivate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -114,32 +114,32 @@ export default function EditUserPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Edit User</h1>
|
||||
<p class="page-subtitle">Update user profile, role assignment, and account status.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Edit User</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Update user profile, role assignment, and account status.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href={`/admin/users/details/${params.id}`}>View Details</A>
|
||||
<A class="btn" href="/admin/users">Back to Users</A>
|
||||
<div class="flex items-center gap-2">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/details/${params.id}`}>View Details</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/users">Back to Users</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={error()}>
|
||||
<div class="error-box">{error()}</div>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
|
||||
</Show>
|
||||
|
||||
<Show when={user.loading}>
|
||||
<div class="card"><p class="notice">Loading user...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading user...</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={!user.loading && !user()}>
|
||||
<div class="card"><p class="notice">User not found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">User not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={user()}>
|
||||
<section class="card" style="max-width:900px">
|
||||
<div class="field-grid-2">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:900px">
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Full Name</label>
|
||||
<input value={name()} onInput={(e) => setName(e.currentTarget.value)} />
|
||||
|
|
@ -170,8 +170,8 @@ export default function EditUserPage() {
|
|||
</div>
|
||||
|
||||
<div class="actions" style="justify-content:flex-end">
|
||||
<button class="btn" type="button" onClick={() => navigate('/admin/users')}>Cancel</button>
|
||||
<button class="btn primary" type="button" onClick={save} disabled={submitting()}>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={() => navigate('/admin/users')}>Cancel</button>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="button" onClick={save} disabled={submitting()}>
|
||||
{submitting() ? 'Saving...' : 'Save Changes'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -161,27 +161,27 @@ export default function UserDetailPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">{roleTitleLabel() ? `${roleTitleLabel()} Profile` : 'User Details'}</h1>
|
||||
<p class="page-subtitle">Review account profile and role registration data with approval status.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">{roleTitleLabel() ? `${roleTitleLabel()} Profile` : 'User Details'}</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review account profile and role registration data with approval status.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href="/admin/users">Back to Users</A>
|
||||
<A class="btn navy" href={`/admin/users/${params.id}/edit`}>Edit User</A>
|
||||
<div class="flex items-center gap-2">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/users">Back to Users</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/users/${params.id}/edit`}>Edit User</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={bundle.loading}>
|
||||
<div class="card"><p class="notice">Loading user...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading user...</p></div>
|
||||
</Show>
|
||||
<Show when={!bundle.loading && !user()}>
|
||||
<div class="card"><p class="notice">User not found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">User not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={user()}>
|
||||
<>
|
||||
<section class="card" style="padding:22px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:22px">
|
||||
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap">
|
||||
<div style="display:flex;gap:14px;align-items:center;min-width:260px">
|
||||
<div class="identity-avatar">
|
||||
|
|
@ -213,7 +213,7 @@ export default function UserDetailPage() {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card" style="margin-top:16px;padding:0;overflow:hidden">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-top:16px;padding:0;overflow:hidden">
|
||||
<div style="padding:16px 18px;border-bottom:1px solid #e2e8f0;background:#f8fafc">
|
||||
<h2 style="margin:0;font-size:18px">{roleTitleLabel() ? `${roleTitleLabel()} Registration Data` : 'Registered Roles & Profiles'}</h2>
|
||||
</div>
|
||||
|
|
@ -222,8 +222,8 @@ export default function UserDetailPage() {
|
|||
<p class="notice">This user has no registered role profiles yet.</p>
|
||||
</div>
|
||||
}>
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<Show when={!roleFilter()}>
|
||||
|
|
@ -233,7 +233,7 @@ export default function UserDetailPage() {
|
|||
<th>Verification</th>
|
||||
<th>Submitted</th>
|
||||
<th>Last Updated</th>
|
||||
<th class="align-right">Action</th>
|
||||
<th class="text-right">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -251,8 +251,8 @@ export default function UserDetailPage() {
|
|||
</td>
|
||||
<td>{formatDate(item.submittedAt)}</td>
|
||||
<td>{formatDate(item.updatedAt)}</td>
|
||||
<td class="align-right">
|
||||
<A class={`btn ${item.isApproval ? '' : 'orange'}`} href={item.isApproval ? `/admin/approval/${item.id}` : `/admin/profile/${item.publicId || item.id}`}>
|
||||
<td class="text-right">
|
||||
<A class={`inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors ${item.isApproval ? '' : 'orange'}`} href={item.isApproval ? `/admin/approval/${item.id}` : `/admin/profile/${item.publicId || item.id}`}>
|
||||
{item.isApproval ? 'View Request' : 'Open Profile'}
|
||||
</A>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -42,17 +42,17 @@ export default function VerificationStatusPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Verification Status</h1>
|
||||
<p class="page-subtitle">Track request status states and open a specific record for follow-up.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Verification Status</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Track request status states and open a specific record for follow-up.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/approval">Open Approval Center</A>
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval">Open Approval Center</A>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
|
|
@ -60,7 +60,7 @@ export default function VerificationStatusPage() {
|
|||
<th>Requester</th>
|
||||
<th>Status</th>
|
||||
<th>Submitted</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -80,12 +80,12 @@ export default function VerificationStatusPage() {
|
|||
<div style="font-weight:600;color:#0f172a">{item.requesterName}</div>
|
||||
<div style="font-size:12px;color:#64748b">{item.requesterEmail}</div>
|
||||
</td>
|
||||
<td><span class="status-chip">{item.status}</span></td>
|
||||
<td><span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">{item.status}</span></td>
|
||||
<td style="color:#475569">{item.createdAt ? new Date(item.createdAt).toLocaleString() : '—'}</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="action-icon-btn" href={`/admin/verification-status/${item.id}`} title="Open Status Detail">↗</A>
|
||||
<A class="action-icon-btn" href={`/admin/approval/${item.id}`} title="Open Approval Detail">👁</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/verification-status/${item.id}`} title="Open Status Detail">↗</A>
|
||||
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/approval/${item.id}`} title="Open Approval Detail">👁</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -40,41 +40,41 @@ export default function VerificationStatusDetailPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Verification Status Detail</h1>
|
||||
<p class="page-subtitle">Open one verification status request and jump into approval review when needed.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Verification Status Detail</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Open one verification status request and jump into approval review when needed.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href="/admin/verification-status">Back to Status List</A>
|
||||
<A class="btn navy" href={`/admin/approval/${params.id}`}>Open Approval Detail</A>
|
||||
<div class="flex items-center gap-2">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/verification-status">Back to Status List</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/approval/${params.id}`}>Open Approval Detail</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={detail.loading}>
|
||||
<div class="card"><p class="notice">Loading verification status...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading verification status...</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={!detail.loading && !detail()}>
|
||||
<div class="card"><p class="notice">Verification status record not found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Verification status record not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={detail()}>
|
||||
<div class="grid" style="margin-top:0">
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin-bottom:8px">Request</h2>
|
||||
<p class="notice" style="margin:0"><strong>ID:</strong> {detail()!.id}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Type:</strong> {type()}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Status:</strong> {status()}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Submitted:</strong> {submitted() ? new Date(submitted()).toLocaleString() : '—'}</p>
|
||||
</section>
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin-bottom:8px">Requester</h2>
|
||||
<p class="notice" style="margin:0"><strong>Name:</strong> {requester()}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Email:</strong> {email()}</p>
|
||||
</section>
|
||||
</div>
|
||||
<section class="card" style="margin-top:16px">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-top:16px">
|
||||
<h2 style="margin-bottom:10px">Request Reason Payload</h2>
|
||||
<pre class="json">{detail()!.requestReason || 'No requestReason payload found.'}</pre>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -5,18 +5,18 @@ export default function VerificationManagementPage() {
|
|||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card">
|
||||
<h1 class="page-title">Approval Management</h1>
|
||||
<p class="page-subtitle" style="margin-top:8px">
|
||||
<h1 class="text-2xl font-bold text-gray-900">Approval Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500" style="margin-top:8px">
|
||||
Admin review now lives under Approval Management. Verification remains a user-facing status concept.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<p class="notice" style="margin:0">
|
||||
Use Approval Management to review companies, customers, candidates, and professional submissions.
|
||||
</p>
|
||||
<div class="actions">
|
||||
<A class="btn navy" href="/admin/approval">Open Approval Management</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/approval">Open Approval Management</A>
|
||||
</div>
|
||||
</section>
|
||||
</AdminShell>
|
||||
|
|
|
|||
|
|
@ -37,26 +37,26 @@ export default function VerificationDetailPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Verification Review</h1>
|
||||
<p class="page-subtitle">Review submission context, documents, and verification decision state.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Verification Review</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review submission context, documents, and verification decision state.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href="/admin/verification">Back to Verification</A>
|
||||
<A class="btn navy" href={`/admin/approval/${params.id}`}>Open Approval Detail</A>
|
||||
<div class="flex items-center gap-2">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/verification">Back to Verification</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/approval/${params.id}`}>Open Approval Detail</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={approval.loading}>
|
||||
<div class="card"><p class="notice">Loading verification detail...</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading verification detail...</p></div>
|
||||
</Show>
|
||||
<Show when={!approval.loading && !approval()}>
|
||||
<div class="card"><p class="notice">Verification request not found.</p></div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Verification request not found.</p></div>
|
||||
</Show>
|
||||
<Show when={approval()}>
|
||||
<div class="grid" style="margin-top:0">
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin-bottom:8px">Summary</h2>
|
||||
<p class="notice" style="margin:0"><strong>Approval ID:</strong> {approval()!.id}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Type:</strong> {type()}</p>
|
||||
|
|
@ -64,13 +64,13 @@ export default function VerificationDetailPage() {
|
|||
<p class="notice" style="margin:8px 0 0"><strong>Requester:</strong> {requester()}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Email:</strong> {email()}</p>
|
||||
</section>
|
||||
<section class="card">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<h2 style="margin-bottom:8px">Remark Snapshot</h2>
|
||||
<p class="notice" style="margin:0">
|
||||
This route mirrors the Next.js verification detail entry point and delegates action workflow to Approval Management.
|
||||
</p>
|
||||
<div class="actions">
|
||||
<A class="btn primary" href={`/admin/approval/${params.id}`}>Review & Take Action</A>
|
||||
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/approval/${params.id}`}>Review & Take Action</A>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export default function VideoEditorsPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-actions">
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="page-title">Video Editor Management</h1>
|
||||
<p class="page-subtitle">Manage all video editor accounts on the platform.</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Video Editor Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage all video editor accounts on the platform.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div style="display:flex;gap:12px;padding:16px;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -64,15 +64,15 @@ export default function VideoEditorsPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Registered</th>
|
||||
<th class="align-right">Actions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -93,22 +93,22 @@ export default function VideoEditorsPage() {
|
|||
<td style="color:#475569">{item.email}</td>
|
||||
<td>
|
||||
{item.status?.toUpperCase() === 'ACTIVE' && (
|
||||
<span class="status-chip active">ACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700">ACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'INACTIVE' && (
|
||||
<span class="status-chip">INACTIVE</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">INACTIVE</span>
|
||||
)}
|
||||
{item.status?.toUpperCase() === 'PENDING' && (
|
||||
<span class="status-chip" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600" style="background:#fff7ed;color:#c2410c;border-color:#fed7aa;">PENDING</span>
|
||||
)}
|
||||
{!item.status && <span class="status-chip">—</span>}
|
||||
{!item.status && <span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">—</span>}
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
{item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/users/${item.id}`}>View</A>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@ import AdminShell from '~/components/AdminShell';
|
|||
export default function WorkspaceAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to dashboard workspace...</p></div></AdminShell>;
|
||||
return <AdminShell><div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Redirecting to dashboard workspace...</p></div></AdminShell>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default function WorkspaceMenuAliasPage() {
|
|||
onMount(() => navigate('/admin', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card">
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<p class="notice">Redirecting workspace menu <strong>{params.menuId}</strong> to dashboard...</p>
|
||||
</div>
|
||||
</AdminShell>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
import { defineConfig } from '@solidjs/start/config';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
export default defineConfig({});
|
||||
export default defineConfig({
|
||||
ssr: false,
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue