Replace Tailwind with Bootstrap for improved UI styling
This commit is contained in:
Generated
+308
-3
@@ -8,13 +8,16 @@
|
||||
"name": "hospitality-web",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@tanstack/react-query": "^5.90.10",
|
||||
"axios": "^1.13.2",
|
||||
"bootstrap": "^5.3.8",
|
||||
"date-fns": "^4.1.0",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"lucide-react": "^0.554.0",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "^19.2.0",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.9.6"
|
||||
},
|
||||
@@ -271,6 +274,15 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||
@@ -1058,6 +1070,85 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/ssr": {
|
||||
"version": "3.9.10",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz",
|
||||
"integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/hooks": {
|
||||
"version": "0.4.16",
|
||||
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
|
||||
"integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/ui": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz",
|
||||
"integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@react-aria/ssr": "^3.5.0",
|
||||
"@restart/hooks": "^0.5.0",
|
||||
"@types/warning": "^3.0.3",
|
||||
"dequal": "^2.0.3",
|
||||
"dom-helpers": "^5.2.0",
|
||||
"uncontrollable": "^8.0.4",
|
||||
"warning": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.14.0",
|
||||
"react-dom": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/ui/node_modules/@restart/hooks": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz",
|
||||
"integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/ui/node_modules/uncontrollable": {
|
||||
"version": "8.0.4",
|
||||
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz",
|
||||
"integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.47",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz",
|
||||
@@ -1373,6 +1464,15 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
||||
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.90.10",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.10.tgz",
|
||||
@@ -1468,6 +1568,12 @@
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/qrcode.react": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/qrcode.react/-/qrcode.react-1.0.5.tgz",
|
||||
@@ -1482,7 +1588,6 @@
|
||||
"version": "19.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz",
|
||||
"integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
@@ -1498,6 +1603,21 @@
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/warning": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
|
||||
"integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz",
|
||||
@@ -1925,6 +2045,25 @@
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.3.8",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
|
||||
"integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.8"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
@@ -2044,6 +2183,12 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -2118,7 +2263,6 @@
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
@@ -2165,6 +2309,25 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -2880,6 +3043,15 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -2924,7 +3096,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
@@ -3034,6 +3205,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
@@ -3170,6 +3353,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -3319,6 +3511,30 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types-extra": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
|
||||
"integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-is": "^16.3.2",
|
||||
"warning": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
@@ -3374,6 +3590,37 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-bootstrap": {
|
||||
"version": "2.10.10",
|
||||
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz",
|
||||
"integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.7",
|
||||
"@restart/hooks": "^0.4.9",
|
||||
"@restart/ui": "^1.9.4",
|
||||
"@types/prop-types": "^15.7.12",
|
||||
"@types/react-transition-group": "^4.4.6",
|
||||
"classnames": "^2.3.2",
|
||||
"dom-helpers": "^5.2.1",
|
||||
"invariant": "^2.2.4",
|
||||
"prop-types": "^15.8.1",
|
||||
"prop-types-extra": "^1.1.0",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"uncontrollable": "^7.2.1",
|
||||
"warning": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.14.8",
|
||||
"react": ">=16.14.0",
|
||||
"react-dom": ">=16.14.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||
@@ -3386,6 +3633,18 @@
|
||||
"react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
|
||||
@@ -3434,6 +3693,22 @@
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.6.0",
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
@@ -3683,6 +3958,12 @@
|
||||
"typescript": ">=4.8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@@ -3734,6 +4015,21 @@
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uncontrollable": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
|
||||
"integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.6.3",
|
||||
"@types/react": ">=16.9.11",
|
||||
"invariant": "^2.2.4",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
@@ -3888,6 +4184,15 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -10,13 +10,16 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@tanstack/react-query": "^5.90.10",
|
||||
"axios": "^1.13.2",
|
||||
"bootstrap": "^5.3.8",
|
||||
"date-fns": "^4.1.0",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"lucide-react": "^0.554.0",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "^19.2.0",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.9.6"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
|
||||
import { Navbar, Nav, Container } from 'react-bootstrap';
|
||||
import EventsPage from './pages/admin/EventsPage';
|
||||
import EventDetailPage from './pages/admin/EventDetailPage';
|
||||
import GroupDetailPage from './pages/admin/GroupDetailPage';
|
||||
@@ -12,26 +13,21 @@ function App() {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<nav className="bg-white shadow-sm border-b">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between h-16">
|
||||
<div className="flex space-x-8">
|
||||
<Link to="/" className="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-900">
|
||||
Admin
|
||||
</Link>
|
||||
<Link to="/staff" className="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-500 hover:text-gray-900">
|
||||
Staff Scanner
|
||||
</Link>
|
||||
<Link to="/guest" className="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-500 hover:text-gray-900">
|
||||
Guest View
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<Navbar bg="primary" variant="dark" expand="lg" className="mb-4">
|
||||
<Container>
|
||||
<Navbar.Brand as={Link} to="/">Hospitality System</Navbar.Brand>
|
||||
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||
<Navbar.Collapse id="basic-navbar-nav">
|
||||
<Nav className="ms-auto">
|
||||
<Nav.Link as={Link} to="/">Admin</Nav.Link>
|
||||
<Nav.Link as={Link} to="/staff">Staff Scanner</Nav.Link>
|
||||
<Nav.Link as={Link} to="/guest">Guest View</Nav.Link>
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
</Container>
|
||||
</Navbar>
|
||||
|
||||
<main>
|
||||
<Container>
|
||||
<Routes>
|
||||
<Route path="/" element={<EventsPage />} />
|
||||
<Route path="/events/:id" element={<EventDetailPage />} />
|
||||
@@ -39,8 +35,7 @@ function App() {
|
||||
<Route path="/staff" element={<ScannerPage />} />
|
||||
<Route path="/guest" element={<GuestQrPage />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
</Container>
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
/* Custom styles */
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button, Card, Row, Col, Form, Modal } from 'react-bootstrap';
|
||||
import { useEvents, useCreateEvent } from '../../hooks/useEvents';
|
||||
import type { CreateEventRequest } from '../../lib/types';
|
||||
|
||||
@@ -7,7 +8,7 @@ export default function EventsPage() {
|
||||
const navigate = useNavigate();
|
||||
const { data: events, isLoading } = useEvents();
|
||||
const createEvent = useCreateEvent();
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [formData, setFormData] = useState<CreateEventRequest>({
|
||||
name: '',
|
||||
startDate: '',
|
||||
@@ -19,7 +20,7 @@ export default function EventsPage() {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const result = await createEvent.mutateAsync(formData);
|
||||
setShowForm(false);
|
||||
setShowModal(false);
|
||||
setFormData({ name: '', startDate: '', endDate: '', location: '' });
|
||||
navigate(`/events/${result.id}`);
|
||||
} catch (error) {
|
||||
@@ -27,96 +28,102 @@ export default function EventsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) return <div className="p-8">Loading...</div>;
|
||||
if (isLoading) return <div className="text-center py-5"><div className="spinner-border" role="status"><span className="visually-hidden">Loading...</span></div></div>;
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">Events</h1>
|
||||
<button
|
||||
onClick={() => setShowForm(!showForm)}
|
||||
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"
|
||||
>
|
||||
{showForm ? 'Cancel' : 'Create Event'}
|
||||
</button>
|
||||
<>
|
||||
<div className="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Events</h1>
|
||||
<Button variant="primary" onClick={() => setShowModal(true)}>
|
||||
Create Event
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{showForm && (
|
||||
<div className="bg-white p-6 rounded-lg shadow mb-8">
|
||||
<h2 className="text-xl font-semibold mb-4">Create New Event</h2>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Event Name</label>
|
||||
<input
|
||||
<Row className="g-4">
|
||||
{events?.map((event) => (
|
||||
<Col key={event.id} md={6} lg={4}>
|
||||
<Card
|
||||
className="h-100 shadow-sm"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => navigate(`/events/${event.id}`)}
|
||||
>
|
||||
<Card.Body>
|
||||
<Card.Title>{event.name}</Card.Title>
|
||||
<Card.Text className="text-muted">{event.location}</Card.Text>
|
||||
<Card.Text>
|
||||
<small className="text-muted">
|
||||
{new Date(event.startDate).toLocaleDateString()}
|
||||
</small>
|
||||
</Card.Text>
|
||||
<div className="d-flex gap-3 mt-3">
|
||||
<span className="badge bg-primary">{event.groupCount || 0} groups</span>
|
||||
<span className="badge bg-success">{event.productCount || 0} products</span>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
<Modal show={showModal} onHide={() => setShowModal(false)}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Create New Event</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Event Name</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
required
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Start Date</label>
|
||||
<input
|
||||
</Form.Group>
|
||||
<Row>
|
||||
<Col md={6}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Start Date</Form.Label>
|
||||
<Form.Control
|
||||
type="datetime-local"
|
||||
required
|
||||
value={formData.startDate}
|
||||
onChange={(e) => setFormData({ ...formData, startDate: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">End Date</label>
|
||||
<input
|
||||
</Form.Group>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>End Date</Form.Label>
|
||||
<Form.Control
|
||||
type="datetime-local"
|
||||
required
|
||||
value={formData.endDate}
|
||||
onChange={(e) => setFormData({ ...formData, endDate: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Location</label>
|
||||
<input
|
||||
</Form.Group>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Location</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
required
|
||||
value={formData.location}
|
||||
onChange={(e) => setFormData({ ...formData, location: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={createEvent.isPending}
|
||||
className="w-full bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
</Form.Group>
|
||||
<div className="d-flex justify-content-end gap-2">
|
||||
<Button variant="secondary" onClick={() => setShowModal(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" type="submit" disabled={createEvent.isPending}>
|
||||
{createEvent.isPending ? 'Creating...' : 'Create Event'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{events?.map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
onClick={() => navigate(`/events/${event.id}`)}
|
||||
className="bg-white p-6 rounded-lg shadow hover:shadow-lg transition-shadow cursor-pointer"
|
||||
>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">{event.name}</h3>
|
||||
<p className="text-gray-600 mb-4">{event.location}</p>
|
||||
<div className="text-sm text-gray-500">
|
||||
<p>{new Date(event.startDate).toLocaleDateString()}</p>
|
||||
<div className="flex gap-4 mt-2">
|
||||
<span>{event.groupCount || 0} groups</span>
|
||||
<span>{event.productCount || 0} products</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { Card, Form, Button, ProgressBar, ListGroup } from 'react-bootstrap';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { usePersonByQrCode } from '../../hooks/useQrCode';
|
||||
|
||||
@@ -13,79 +14,79 @@ export default function GuestQrPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto px-4 py-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-8 text-center">Guest View</h1>
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-md-8 col-lg-6">
|
||||
<h1 className="text-center mb-4">Guest View</h1>
|
||||
|
||||
{!showQr ? (
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Enter Your QR Code
|
||||
</label>
|
||||
<input
|
||||
<Card className="shadow-sm">
|
||||
<Card.Body>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Enter Your QR Code</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
size="lg"
|
||||
value={qrCode}
|
||||
onChange={(e) => setQrCode(e.target.value)}
|
||||
placeholder="Your QR code (GUID)"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg text-lg"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-blue-600 text-white px-4 py-3 rounded-lg hover:bg-blue-700 font-medium"
|
||||
>
|
||||
</Form.Group>
|
||||
<Button variant="primary" type="submit" className="w-100" size="lg">
|
||||
Show My QR Code
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="bg-white p-8 rounded-lg shadow text-center">
|
||||
<Card className="shadow text-center">
|
||||
<Card.Body className="p-4">
|
||||
{person && (
|
||||
<>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-6">{person.name}</h2>
|
||||
<h2 className="mb-4">{person.name}</h2>
|
||||
|
||||
<div className="flex justify-center mb-8">
|
||||
<div className="bg-white p-6 rounded-lg border-4 border-gray-200">
|
||||
<div className="d-flex justify-content-center mb-4">
|
||||
<div className="p-4 bg-white border border-3 rounded">
|
||||
<QRCodeSVG value={qrCode} size={256} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-semibold mb-4">Your Quotas</h3>
|
||||
<div className="space-y-3">
|
||||
<h4 className="mb-3">Your Quotas</h4>
|
||||
<ListGroup className="text-start">
|
||||
{person.quotas?.map((quota) => (
|
||||
<div key={quota.productId} className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-left">
|
||||
<h4 className="font-semibold">{quota.productName}</h4>
|
||||
<p className="text-sm text-gray-500">{quota.productType}</p>
|
||||
<ListGroup.Item key={quota.productId}>
|
||||
<div className="d-flex justify-content-between align-items-center mb-2">
|
||||
<div>
|
||||
<h6 className="mb-0">{quota.productName}</h6>
|
||||
<small className="text-muted">{quota.productType}</small>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-2xl font-bold text-blue-600">{quota.remainingAmount}</p>
|
||||
<p className="text-xs text-gray-500">of {quota.initialAmount} remaining</p>
|
||||
<div className="text-end">
|
||||
<h5 className="mb-0 text-primary">{quota.remainingAmount}</h5>
|
||||
<small className="text-muted">of {quota.initialAmount} remaining</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className="bg-blue-600 h-2 rounded-full"
|
||||
style={{ width: `${(quota.remainingAmount / quota.initialAmount) * 100}%` }}
|
||||
<ProgressBar
|
||||
now={(quota.remainingAmount / quota.initialAmount) * 100}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
))}
|
||||
</div>
|
||||
</ListGroup>
|
||||
|
||||
<button
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={() => setShowQr(false)}
|
||||
className="mt-6 text-blue-600 hover:text-blue-700"
|
||||
className="mt-3"
|
||||
>
|
||||
Enter Different Code
|
||||
</button>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,101 +1,116 @@
|
||||
import { useState } from 'react';
|
||||
import { Card, Form, Button, Badge, ListGroup, Alert } from 'react-bootstrap';
|
||||
import { usePersonByQrCode, useCreateTransaction } from '../../hooks/useQrCode';
|
||||
|
||||
export default function ScannerPage() {
|
||||
const [qrCode, setQrCode] = useState('');
|
||||
const [selectedProduct, setSelectedProduct] = useState('');
|
||||
const { data: person, isLoading } = usePersonByQrCode(qrCode);
|
||||
const [searchQr, setSearchQr] = useState('');
|
||||
const { data: person, isLoading } = usePersonByQrCode(searchQr);
|
||||
const createTransaction = useCreateTransaction();
|
||||
|
||||
const handleScan = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
// QR code is set, person data will load automatically
|
||||
setSearchQr(qrCode);
|
||||
};
|
||||
|
||||
const handleTransaction = async (productId: string, amount: number) => {
|
||||
try {
|
||||
await createTransaction.mutateAsync({
|
||||
qrCode,
|
||||
qrCode: searchQr,
|
||||
productId,
|
||||
amount,
|
||||
});
|
||||
alert('Transaction recorded!');
|
||||
alert('Transaction recorded successfully!');
|
||||
} catch (error: any) {
|
||||
alert(error.response?.data || 'Failed to record transaction');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto px-4 py-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-8">Staff Scanner</h1>
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-md-8 col-lg-6">
|
||||
<h1 className="mb-4">Staff Scanner</h1>
|
||||
|
||||
<div className="bg-white p-6 rounded-lg shadow mb-6">
|
||||
<form onSubmit={handleScan} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Scan or Enter QR Code
|
||||
</label>
|
||||
<input
|
||||
<Card className="mb-4 shadow-sm">
|
||||
<Card.Body>
|
||||
<Form onSubmit={handleScan}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Scan or Enter QR Code</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
size="lg"
|
||||
value={qrCode}
|
||||
onChange={(e) => setQrCode(e.target.value)}
|
||||
placeholder="Enter QR code (GUID)"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg text-lg focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-blue-600 text-white px-4 py-3 rounded-lg hover:bg-blue-700 font-medium"
|
||||
>
|
||||
</Form.Group>
|
||||
<Button variant="primary" type="submit" className="w-100" size="lg">
|
||||
Lookup Person
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
|
||||
{isLoading && <div className="text-center py-8">Loading...</div>}
|
||||
{isLoading && (
|
||||
<div className="text-center py-5">
|
||||
<div className="spinner-border" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{person && (
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">{person.name}</h2>
|
||||
{person.email && <p className="text-gray-600 mb-4">{person.email}</p>}
|
||||
|
||||
<h3 className="text-lg font-semibold mb-3">Available Quotas</h3>
|
||||
<div className="space-y-3">
|
||||
{person.quotas?.map((quota) => (
|
||||
<div key={quota.productId} className="border border-gray-200 rounded-lg p-4">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<Card className="shadow">
|
||||
<Card.Header className="bg-primary text-white">
|
||||
<h4 className="mb-0">{person.name}</h4>
|
||||
{person.email && <small>{person.email}</small>}
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<h5 className="mb-3">Available Quotas</h5>
|
||||
{person.quotas && person.quotas.length > 0 ? (
|
||||
<ListGroup>
|
||||
{person.quotas.map((quota) => (
|
||||
<ListGroup.Item key={quota.productId}>
|
||||
<div className="d-flex justify-content-between align-items-center mb-2">
|
||||
<div>
|
||||
<h4 className="font-semibold">{quota.productName}</h4>
|
||||
<p className="text-sm text-gray-500">{quota.productType}</p>
|
||||
<h6 className="mb-0">{quota.productName}</h6>
|
||||
<small className="text-muted">{quota.productType}</small>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-2xl font-bold text-blue-600">{quota.remainingAmount}</p>
|
||||
<p className="text-xs text-gray-500">of {quota.initialAmount}</p>
|
||||
<div className="text-end">
|
||||
<h3 className="mb-0 text-primary">{quota.remainingAmount}</h3>
|
||||
<small className="text-muted">of {quota.initialAmount}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-3">
|
||||
<button
|
||||
<div className="d-flex gap-2">
|
||||
<Button
|
||||
variant="success"
|
||||
onClick={() => handleTransaction(quota.productId, 1)}
|
||||
disabled={quota.remainingAmount < 1 || createTransaction.isPending}
|
||||
className="flex-1 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="flex-fill"
|
||||
>
|
||||
Use 1
|
||||
</button>
|
||||
</Button>
|
||||
{quota.remainingAmount >= 2 && (
|
||||
<button
|
||||
<Button
|
||||
variant="success"
|
||||
onClick={() => handleTransaction(quota.productId, 2)}
|
||||
disabled={createTransaction.isPending}
|
||||
className="flex-1 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700"
|
||||
className="flex-fill"
|
||||
>
|
||||
Use 2
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ListGroup>
|
||||
) : (
|
||||
<Alert variant="info">No quotas assigned to this person.</Alert>
|
||||
)}
|
||||
</Card.Body>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
Reference in New Issue
Block a user