diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1c08a1..f895a28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,8 +38,32 @@ jobs: - name: Compilar backend run: npm run build:backend - - name: Verificar frontend com ESLint + - name: Verificar frontend com Oxlint run: npm run lint + - name: Testar frontend + run: npm run test:frontend + - name: Compilar frontend run: npm run build:frontend + + security: + name: Auditoria de segurança + runs-on: ubuntu-24.04 + + steps: + - name: Baixar código + uses: actions/checkout@v7 + + - name: Configurar Node.js + uses: actions/setup-node@v7 + with: + node-version: 22 + cache: npm + + - name: Auditar dependências da raiz + run: npm audit --audit-level=moderate + + - name: Auditar dependências do backend + working-directory: ./backend + run: npm audit --workspaces=false --audit-level=moderate \ No newline at end of file diff --git a/.gitignore b/.gitignore index 61c8742..0f787dc 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,7 @@ lerna-debug.log* # Vite / bundler cache # ───────────────────────────────────────── .vite/ -frontend/app/.vite/ +frontend/.vite/ # ───────────────────────────────────────── # Docker local data diff --git a/backend/package-lock.json b/backend/package-lock.json index 7630e6a..8d1305e 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -16,7 +16,7 @@ "@fastify/swagger": "^9.8.1", "@fastify/swagger-ui": "^6.1.1", "@prisma/adapter-mariadb": "^7.9.1", - "@prisma/client": "^7.9.1", + "@prisma/client": "^7.10.1", "bcrypt": "^6.0.0", "dotenv": "^17.4.2", "fastify": "^5.11.2", @@ -66,6 +66,7 @@ "version": "1.11.3", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -969,12 +970,13 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", - "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", "cpu": [ "arm64" ], + "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -986,16 +988,17 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.3" + "@img/sharp-libvips-darwin-arm64": "1.3.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", - "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", "cpu": [ "x64" ], + "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -1007,19 +1010,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.3" + "@img/sharp-libvips-darwin-x64": "1.3.4" } }, "node_modules/@img/sharp-freebsd-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", - "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "license": "Apache-2.0", "optional": true, "os": [ "freebsd" ], "dependencies": { - "@img/sharp-wasm32": "0.35.4" + "@img/sharp-wasm32": "0.35.5" }, "engines": { "node": ">=20.9.0" @@ -1029,12 +1033,13 @@ } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", - "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", "cpu": [ "arm64" ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" @@ -1044,12 +1049,13 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", - "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", "cpu": [ "x64" ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" @@ -1059,12 +1065,16 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", - "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", "cpu": [ "arm" ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -1074,12 +1084,16 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", - "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -1089,12 +1103,16 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", - "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -1104,12 +1122,16 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", - "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", "cpu": [ "riscv64" ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -1119,12 +1141,16 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", - "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -1134,12 +1160,16 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", - "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -1149,12 +1179,16 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", - "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -1164,12 +1198,16 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", - "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -1179,12 +1217,16 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", - "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", "cpu": [ "arm" ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -1196,16 +1238,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.3" + "@img/sharp-libvips-linux-arm": "1.3.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", - "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -1217,16 +1263,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.3" + "@img/sharp-libvips-linux-arm64": "1.3.4" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", - "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -1238,16 +1288,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.3" + "@img/sharp-libvips-linux-ppc64": "1.3.4" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", - "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", "cpu": [ "riscv64" ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -1259,16 +1313,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.3" + "@img/sharp-libvips-linux-riscv64": "1.3.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", - "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -1280,16 +1338,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.3" + "@img/sharp-libvips-linux-s390x": "1.3.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", - "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -1301,16 +1363,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.3" + "@img/sharp-libvips-linux-x64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", - "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -1322,16 +1388,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", - "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -1343,13 +1413,14 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.3" + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", - "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { "@emnapi/runtime": "^1.11.3" @@ -1362,15 +1433,16 @@ } }, "node_modules/@img/sharp-webcontainers-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", - "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", "cpu": [ "wasm32" ], + "license": "Apache-2.0", "optional": true, "dependencies": { - "@img/sharp-wasm32": "0.35.4" + "@img/sharp-wasm32": "0.35.5" }, "engines": { "node": ">=20.9.0" @@ -1380,12 +1452,13 @@ } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", - "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", "cpu": [ "arm64" ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -1398,12 +1471,13 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", - "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", "cpu": [ "ia32" ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -1416,12 +1490,13 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", - "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", "cpu": [ "x64" ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -1447,20 +1522,22 @@ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" }, "node_modules/@prisma/adapter-mariadb": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/adapter-mariadb/-/adapter-mariadb-7.9.1.tgz", - "integrity": "sha512-4aZUbIg2av7XFSQyM9cQeAp9YP9HiPr8H7ehe2rDo375IZXILEzQrNXZ/zLetgRQ2ojlaUl/brArFdOVS3n1xA==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/adapter-mariadb/-/adapter-mariadb-7.10.1.tgz", + "integrity": "sha512-wB7ZU1NpPlONqzMsCHMyXsNUwjNr2w72PAp3KimJVMCyXzrCmzhxdbxGgWFgT6N9RX+xnujYiWUN3AQF85TIQA==", + "license": "Apache-2.0", "dependencies": { - "@prisma/driver-adapter-utils": "7.9.1", - "mariadb": "3.4.5" + "@prisma/driver-adapter-utils": "7.10.1", + "mariadb": "3.5.4" } }, "node_modules/@prisma/client": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.9.1.tgz", - "integrity": "sha512-+xgrh2EhJVF79wC0yX5G4PI1Rdcm7Qn/nekNQ+t/O153wtNggruHal+fXHSa0QE+Tp/Cw5wvxeCEhZZ59xGm8Q==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.10.1.tgz", + "integrity": "sha512-5tgAbFGh//zVOC6WPNVgziOErJHYkUE+wmqCwbIyq6IrTXUYtv9y2iBFGh/Owo99Mx0+cr0j1FOrxkYBMuyhaQ==", + "license": "Apache-2.0", "dependencies": { - "@prisma/client-runtime-utils": "7.9.1" + "@prisma/client-runtime-utils": "7.10.1" }, "engines": { "node": "^20.19 || ^22.12 || >=24.0" @@ -1479,26 +1556,29 @@ } }, "node_modules/@prisma/client-runtime-utils": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.9.1.tgz", - "integrity": "sha512-mVIBGYdO5CFmK0HvjxrtfIyQQcPdb88pSCeVQriVQPVZyDovIWblpHfOgcS8QO187j3QF0ePArH8qPhp0AU2vg==" + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.10.1.tgz", + "integrity": "sha512-AzoATVIgmO3lcZfM443+9SI7+cyLMW2+Psw4op8WQHpHvHCX//CKxv8fwhiL0yGWF8DjdQrFewxrZo5McBdB8g==", + "license": "Apache-2.0" }, "node_modules/@prisma/config": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.9.1.tgz", - "integrity": "sha512-4znKhxTmXmuPye9Z6pbIyYb5VZlkZ05qG1L6Dr4g+7oTwc6V50Bs9XirFBDdjWt+H/AabMn9aUnxBcvj8z05aA==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.10.1.tgz", + "integrity": "sha512-+0swF5ELUa3P/9cIR26+gpYjjCbGE0qvEBZhV4zMDkYsrtHjZ884MQbOnWFWXj600N0sSJNnHbtBqe12yDngWg==", "devOptional": true, + "license": "Apache-2.0", "dependencies": { "c12": "3.3.4", - "deepmerge-ts": "7.1.5", + "deepmerge-ts": "8.0.2", "effect": "3.20.0", "empathic": "2.0.0" } }, "node_modules/@prisma/debug": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.9.1.tgz", - "integrity": "sha512-/cpVZ4itxtcgB8GHBvZtcmuEjq+lWsLrRJxFMbwZrT1RIdtuKmUm7PPGo/wzfbYpBrk+9WmmBE8CHJw2rybKDQ==" + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.10.1.tgz", + "integrity": "sha512-eFCAkGZ13D/qPg48JBlG5IWi9cSBBOC7oG3abDPcAkYioDWlIjh0YJJLK2J7qo5S+xWWD8I6egAN/8KbCaiMCA==", + "license": "Apache-2.0" }, "node_modules/@prisma/dev": { "version": "0.24.17", @@ -1524,59 +1604,65 @@ } }, "node_modules/@prisma/driver-adapter-utils": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.9.1.tgz", - "integrity": "sha512-vmHehG7nn/heW32DXXpp13DxxAxVVe6n250oEt3dOL2E/4bt3olktKZN0mzSuxMMronyMSkbeW2uCOn3F4g8RQ==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.10.1.tgz", + "integrity": "sha512-DJ4c6xvVnm3s9ZUyql9nCUicADSqyvH+CjXRCM9L6m5zJxCNmebCElG6X4irICq3rosBwlX+S19Dxq/4IrZk3A==", + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.9.1" + "@prisma/debug": "7.10.1" } }, "node_modules/@prisma/engines": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.9.1.tgz", - "integrity": "sha512-UprXSMNXx2NF5ow4pqaQtE8OuBz6K78B0wc0tn2L28G5r933iWp1DR9Do2qWrsNvvFIP3x6mpEWnQtckMO0Uhg==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.10.1.tgz", + "integrity": "sha512-R6/ghv1Bu36FhCdQLFof1pNtyoWoeDzJLYeTKjgqb4eGtOAJx7ZqJtcm4pzkahLfZ9rYz8AkmsMDwkyiYbXPCQ==", "devOptional": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.9.1", - "@prisma/engines-version": "7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad", - "@prisma/fetch-engine": "7.9.1", - "@prisma/get-platform": "7.9.1" + "@prisma/debug": "7.10.1", + "@prisma/engines-version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8", + "@prisma/fetch-engine": "7.10.1", + "@prisma/get-platform": "7.10.1" } }, "node_modules/@prisma/engines-version": { - "version": "7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad.tgz", - "integrity": "sha512-2BsPPFksz3CQUXG6af3rVCtJKg6+JJGJTtfgu2fU8DdXhOfkBjulCq8mwybCd6ge0/jhZq2kOtLAbmUDMyI1nA==", - "devOptional": true + "version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8.tgz", + "integrity": "sha512-XjXmwjN2NE340RpXoXv8ZF8aNAvr0FE+7gNfkKeW6vyjXs7F8uxvEbWQoVrhioXDQoIsnNmv7r+wxvzfdEZaWA==", + "devOptional": true, + "license": "Apache-2.0" }, "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.9.1.tgz", - "integrity": "sha512-PK8R60YZRQvYxBrGG9i7l2/rFyzy+2MuI1dKtmtrCqPH8YpiJx/MfiC7LRzX5786rZDEv7BngcjfIJW4/9ADuw==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.1.tgz", + "integrity": "sha512-iVnghHne5vcaobBjPVCMEjh5r/1UCj394EIGTUclasFRatpR/MQwcM2veLv5kgoFSyYP54nch/lNFCQRYcDxvg==", "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.9.1" + "@prisma/debug": "7.10.1" } }, "node_modules/@prisma/fetch-engine": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.9.1.tgz", - "integrity": "sha512-9DwxrNTeT25Orbu9CWh0CZvVlyY1lmscpbaeLZcOnuR7zcuFrt91YSmmOfIm7zJ08YOZ6mVzURKwLoMwEBcK8w==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.10.1.tgz", + "integrity": "sha512-qZezeaExLWG2ptbHOrMxdiWluKHgqEclTdJbvY6pk6+Wg7EiVkeq5U3cXz9ti8nd0bngNvsDgledLHVRg5LF1w==", "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.9.1", - "@prisma/engines-version": "7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad", - "@prisma/get-platform": "7.9.1" + "@prisma/debug": "7.10.1", + "@prisma/engines-version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8", + "@prisma/get-platform": "7.10.1" } }, "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.9.1.tgz", - "integrity": "sha512-PK8R60YZRQvYxBrGG9i7l2/rFyzy+2MuI1dKtmtrCqPH8YpiJx/MfiC7LRzX5786rZDEv7BngcjfIJW4/9ADuw==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.1.tgz", + "integrity": "sha512-iVnghHne5vcaobBjPVCMEjh5r/1UCj394EIGTUclasFRatpR/MQwcM2veLv5kgoFSyYP54nch/lNFCQRYcDxvg==", "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.9.1" + "@prisma/debug": "7.10.1" } }, "node_modules/@prisma/get-platform": { @@ -1809,7 +1895,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "devOptional": true + "devOptional": true, + "license": "MIT" }, "node_modules/@types/bcrypt": { "version": "6.0.0", @@ -1931,7 +2018,6 @@ "version": "26.1.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", - "dev": true, "dependencies": { "undici-types": "~8.3.0" } @@ -1961,7 +2047,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "aix" @@ -1977,7 +2062,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -1993,7 +2077,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -2009,7 +2092,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -2025,7 +2107,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -2041,7 +2122,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2057,7 +2137,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2073,7 +2152,6 @@ "cpu": [ "loong64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2089,7 +2167,6 @@ "cpu": [ "mips64el" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2105,7 +2182,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2121,7 +2197,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2137,7 +2212,6 @@ "cpu": [ "s390x" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2153,7 +2227,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2169,7 +2242,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "netbsd" @@ -2185,7 +2257,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "netbsd" @@ -2201,7 +2272,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "openbsd" @@ -2217,7 +2287,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "openbsd" @@ -2233,7 +2302,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "sunos" @@ -2249,7 +2317,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -2265,7 +2332,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -2558,6 +2624,7 @@ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", "devOptional": true, + "license": "MIT", "engines": { "node": ">= 6.0.0" } @@ -2595,9 +2662,10 @@ "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==" }, "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, @@ -2610,6 +2678,7 @@ "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", "devOptional": true, + "license": "MIT", "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.4", @@ -2646,6 +2715,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "devOptional": true, + "license": "MIT", "dependencies": { "readdirp": "^5.0.0" }, @@ -2692,7 +2762,8 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "devOptional": true + "devOptional": true, + "license": "MIT" }, "node_modules/content-disposition": { "version": "2.0.1", @@ -2896,19 +2967,31 @@ "dev": true }, "node_modules/deepmerge-ts": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", - "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-8.0.2.tgz", + "integrity": "sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==", "devOptional": true, + "funding": [ + { + "type": "ko-fi", + "url": "https://ko-fi.com/rebeccastevens" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/deepmerge-ts" + } + ], + "license": "BSD-3-Clause", "engines": { - "node": ">=16.0.0" + "node": ">=16.9.0" } }, "node_modules/defu": { "version": "6.1.7", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", - "devOptional": true + "devOptional": true, + "license": "MIT" }, "node_modules/delaunator": { "version": "5.1.0", @@ -2923,6 +3006,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", "engines": { "node": ">=0.10" } @@ -2947,7 +3031,8 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "devOptional": true + "devOptional": true, + "license": "MIT" }, "node_modules/detect-libc": { "version": "2.1.2", @@ -2986,6 +3071,7 @@ "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz", "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==", "devOptional": true, + "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" @@ -3007,6 +3093,7 @@ "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", "devOptional": true, + "license": "MIT", "engines": { "node": ">=14" } @@ -3226,10 +3313,11 @@ } }, "node_modules/exsolve": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", - "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", - "devOptional": true + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.3.tgz", + "integrity": "sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==", + "devOptional": true, + "license": "MIT" }, "node_modules/fast-check": { "version": "3.23.2", @@ -3246,6 +3334,7 @@ "url": "https://opencollective.com/fast-check" } ], + "license": "MIT", "dependencies": { "pure-rand": "^6.1.0" }, @@ -3308,9 +3397,9 @@ } }, "node_modules/fast-json-stringify/node_modules/ajv/node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", "funding": [ { "type": "github", @@ -3320,12 +3409,13 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/fast-json-stringify/node_modules/fast-uri": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.2.tgz", - "integrity": "sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.2.3.tgz", + "integrity": "sha512-I1PXwP5eAhlNlcdpjt9CgB59op2ZvZfxw+BkqGFVIUOgNpCVTcTqp3HhTX7brxj5vsip+nSY4nTvff+eIA76hg==", "funding": [ { "type": "github", @@ -3335,7 +3425,8 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/fast-json-stringify/node_modules/json-schema-traverse": { "version": "1.0.0", @@ -3343,9 +3434,10 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/fast-jwt": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/fast-jwt/-/fast-jwt-6.3.2.tgz", - "integrity": "sha512-JTQImpkXVvj+eq7tJImtsHRt1K6ngloEzIx62Qbf9x4tEM2P2EqGpYJSeUoPf/kMn78rImSHfhf08KShR8PauA==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/fast-jwt/-/fast-jwt-6.3.5.tgz", + "integrity": "sha512-H05ACsqy2i91TwJeLLHcTgQu6cmcmwdyvfIavjtv3ig+sQZRPsSiksXR3QFPvdk0aXmJdKIePE0OsvO4ndt4zw==", + "license": "Apache-2.0", "dependencies": { "@lukeed/ms": "^2.0.2", "asn1.js": "^5.4.1", @@ -3372,9 +3464,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", "funding": [ { "type": "github", @@ -3384,7 +3476,8 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/fastfall": { "version": "1.5.1", @@ -3398,9 +3491,9 @@ } }, "node_modules/fastify": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.11.2.tgz", - "integrity": "sha512-i/eJG7nXR9OkbFgoX4jFiPOHoRq0rXqDACVAXELh5Fdg6BFBErIVZ8MyibGCwup9Go1pYrxZJ0ogIub8vVdEcQ==", + "version": "5.12.7", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.12.7.tgz", + "integrity": "sha512-v4pEPsTRPuqlDIYiQFB+BHjVN4SdHX9u0ABk0ZDUKbbdY1vsNryUd1j4Z+jQEZajvYIVk047Kimva9/AJ79GYw==", "funding": [ { "type": "github", @@ -3411,6 +3504,7 @@ "url": "https://opencollective.com/fastify" } ], + "license": "MIT", "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", @@ -3422,7 +3516,7 @@ "find-my-way": "^9.6.0", "light-my-request": "^6.0.0", "pino": "^9.14.0 || ^10.1.0", - "process-warning": "^5.0.0", + "process-warning": "^5.1.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", @@ -3565,6 +3659,7 @@ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "devOptional": true, + "license": "MIT", "dependencies": { "is-property": "^1.0.2" } @@ -3588,6 +3683,7 @@ "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz", "integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==", "devOptional": true, + "license": "MIT", "bin": { "giget": "dist/cli.mjs" } @@ -3661,7 +3757,7 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "devOptional": true, + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -3746,7 +3842,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", - "devOptional": true + "devOptional": true, + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", @@ -3897,18 +3994,24 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "devOptional": true + "devOptional": true, + "license": "Apache-2.0" }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/lru.min": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", - "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.5.tgz", + "integrity": "sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==", "devOptional": true, + "license": "MIT", "engines": { "bun": ">=1.0.0", "deno": ">=1.30.0", @@ -3920,44 +4023,21 @@ } }, "node_modules/mariadb": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz", - "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.5.4.tgz", + "integrity": "sha512-3m0vdfgRkjle5/9hHl4R7rlVvnDA7QfuFSNkdIAQNROWKOS0J3b9IQK9HaZSCGEVKiDUbFXFiINfC+cmR5P/aA==", + "license": "LGPL-2.1-or-later", "dependencies": { "@types/geojson": "^7946.0.16", - "@types/node": "^24.0.13", + "@types/node": ">=20", "denque": "^2.1.0", - "iconv-lite": "^0.6.3", - "lru-cache": "^10.4.3" + "iconv-lite": "^0.7.2", + "lru-cache": "^11.5.0" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/mariadb/node_modules/@types/node": { - "version": "24.13.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", - "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", - "dependencies": { - "undici-types": "~7.18.0" + "node": ">= 20.0.0" } }, - "node_modules/mariadb/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mariadb/node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==" - }, "node_modules/mime": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", @@ -4010,23 +4090,25 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/mysql2": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", - "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", + "version": "3.24.4", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.4.tgz", + "integrity": "sha512-A2olluVlj0mvgyIRRISMEzXc51m+21mRtcMVjJyIpt2GG98+XrC9m9HzsqcMsX2LcnfccJvY5NB22g8fENBnOA==", "devOptional": true, + "license": "MIT", "dependencies": { - "aws-ssl-profiles": "^1.1.1", - "denque": "^2.1.0", + "aws-ssl-profiles": "^1.1.2", "generate-function": "^2.3.1", - "iconv-lite": "^0.7.0", - "long": "^5.2.1", - "lru.min": "^1.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" + "iconv-lite": "^0.7.3", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.5.1" }, "engines": { "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" } }, "node_modules/named-placeholders": { @@ -4034,6 +4116,7 @@ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", "devOptional": true, + "license": "MIT", "dependencies": { "lru.min": "^1.1.0" }, @@ -4071,10 +4154,11 @@ "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==" }, "node_modules/ohash": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "devOptional": true + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "devOptional": true, + "license": "MIT" }, "node_modules/on-exit-leak-free": { "version": "2.1.2", @@ -4176,14 +4260,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -4194,7 +4270,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", - "devOptional": true + "devOptional": true, + "license": "MIT" }, "node_modules/pino": { "version": "10.3.1", @@ -4231,16 +4308,24 @@ "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==" }, "node_modules/pkg-types": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", - "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.3.tgz", + "integrity": "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==", "devOptional": true, + "license": "MIT", "dependencies": { - "confbox": "^0.2.4", - "exsolve": "^1.0.8", + "confbox": "^0.3.1", + "exsolve": "^1.1.1", "pathe": "^2.0.3" } }, + "node_modules/pkg-types/node_modules/confbox": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", @@ -4287,17 +4372,18 @@ } }, "node_modules/prisma": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.9.1.tgz", - "integrity": "sha512-aPqePoZIqwlAchbgbFDO/wHqGB+7H1nj9gaM+OsL9h77S5S3TnLd9BgD3LnoeDikULo7cl2HSUrEyQ55Z7DYbg==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.10.1.tgz", + "integrity": "sha512-GoVeErHGGtsSjsflQ1r2BA3RKTXtxNraQJz8OLkLhoLr7OgCscdx4qw4WMM4HnERYc+a1cq2VnMNbep+vOjNcQ==", "devOptional": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/config": "7.9.1", + "@prisma/config": "7.10.1", "@prisma/dev": "0.24.17", - "@prisma/engines": "7.9.1", + "@prisma/engines": "7.10.1", "@prisma/studio-core": "0.33.0", - "mysql2": "3.15.3", + "mysql2": "3.24.4", "postgres": "3.4.7" }, "bin": { @@ -4374,7 +4460,8 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ] + ], + "license": "MIT" }, "node_modules/qrcode": { "version": "1.5.4", @@ -4398,12 +4485,13 @@ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, "node_modules/rc9": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", - "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.1.0.tgz", + "integrity": "sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==", "devOptional": true, + "license": "MIT", "dependencies": { - "defu": "^6.1.6", + "defu": "^6.1.7", "destr": "^2.0.5" } }, @@ -4435,6 +4523,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "devOptional": true, + "license": "MIT", "engines": { "node": ">= 20.19.0" }, @@ -4604,12 +4693,6 @@ "node": ">=10" } }, - "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", - "devOptional": true - }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -4626,9 +4709,10 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/sharp": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", - "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", + "license": "Apache-2.0", "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", @@ -4641,31 +4725,31 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.4", - "@img/sharp-darwin-x64": "0.35.4", - "@img/sharp-freebsd-wasm32": "0.35.4", - "@img/sharp-libvips-darwin-arm64": "1.3.3", - "@img/sharp-libvips-darwin-x64": "1.3.3", - "@img/sharp-libvips-linux-arm": "1.3.3", - "@img/sharp-libvips-linux-arm64": "1.3.3", - "@img/sharp-libvips-linux-ppc64": "1.3.3", - "@img/sharp-libvips-linux-riscv64": "1.3.3", - "@img/sharp-libvips-linux-s390x": "1.3.3", - "@img/sharp-libvips-linux-x64": "1.3.3", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", - "@img/sharp-libvips-linuxmusl-x64": "1.3.3", - "@img/sharp-linux-arm": "0.35.4", - "@img/sharp-linux-arm64": "0.35.4", - "@img/sharp-linux-ppc64": "0.35.4", - "@img/sharp-linux-riscv64": "0.35.4", - "@img/sharp-linux-s390x": "0.35.4", - "@img/sharp-linux-x64": "0.35.4", - "@img/sharp-linuxmusl-arm64": "0.35.4", - "@img/sharp-linuxmusl-x64": "0.35.4", - "@img/sharp-webcontainers-wasm32": "0.35.4", - "@img/sharp-win32-arm64": "0.35.4", - "@img/sharp-win32-ia32": "0.35.4", - "@img/sharp-win32-x64": "0.35.4" + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" }, "peerDependenciesMeta": { "@types/node": { @@ -4722,13 +4806,20 @@ "node": ">= 10.x" } }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "node_modules/sql-escaper": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.2.tgz", + "integrity": "sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg==", "devOptional": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" } }, "node_modules/statuses": { @@ -4817,6 +4908,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", "optional": true }, "node_modules/tsx": { @@ -4886,8 +4978,7 @@ "node_modules/undici-types": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==" }, "node_modules/uri-js": { "version": "4.4.1", @@ -5085,9 +5176,10 @@ } }, "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/backend/package.json b/backend/package.json index eca11d9..15c5f07 100644 --- a/backend/package.json +++ b/backend/package.json @@ -40,7 +40,7 @@ "@fastify/swagger": "^9.8.1", "@fastify/swagger-ui": "^6.1.1", "@prisma/adapter-mariadb": "^7.9.1", - "@prisma/client": "^7.9.1", + "@prisma/client": "^7.10.1", "bcrypt": "^6.0.0", "dotenv": "^17.4.2", "fastify": "^5.11.2", diff --git a/docker-compose.yml b/docker-compose.yml index 20a2326..7c4a937 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -74,7 +74,7 @@ services: frontend: build: - context: ./frontend/app + context: ./frontend dockerfile: Dockerfile container_name: orderflow-frontend restart: unless-stopped @@ -85,7 +85,7 @@ services: CHOKIDAR_USEPOLLING: "true" volumes: # Mantem o codigo local sincronizado com o servidor de desenvolvimento. - - ./frontend/app:/app + - ./frontend:/app # Evita que o bind mount esconda as dependencias instaladas na imagem. - frontend_node_modules:/app/node_modules depends_on: diff --git a/docs/COMPONENTS.md b/docs/COMPONENTS.md new file mode 100644 index 0000000..27cb5da --- /dev/null +++ b/docs/COMPONENTS.md @@ -0,0 +1,69 @@ +# Componentes MesaFlow + +Componentes React em TypeScript organizados por domínio do design system: + +- `components/ui/buttons`: ações, ícones e quantidade. +- `components/ui/inputs`: campos de texto, textarea e select. +- `components/ui/forms`: checkbox, radio, switch e dropdown. +- `components/ui/navigation`: abas, categorias, breadcrumb e paginação. +- `components/ui/cards`: indicadores estatísticos. +- `components/ui/status`: etiquetas de status. +- `components/ui/feedback`: modal, toast, alerta, tooltip e carregamento. +- `components/ui/icons`: ícones com comportamento próprio. +- `components/layout`: navegação, cabeçalhos e barras compartilhadas. +- `components/domain/cards`: cartões de produto, pedido e mesa. +- `components/domain/restaurant`: carrinho, andamento do pedido e status de mesas. +- `components/domain/notifications`: notificações do atendimento. + +Use o ponto único de importação: + +```tsx +import { Button, ProductCard, Badge } from "./components"; +``` + +Os componentes utilizam os tokens e estilos de `src/styles/main.scss`. Importe essa entrada uma única vez, antes de renderizá-los. As regras compartilhadas ficam em `src/styles/components/`; os estilos específicos permanecem junto às páginas e componentes. + +```tsx +import "./styles/main.scss"; +``` + + +## Onde alterar cada parte + +Os caminhos abaixo são relativos a `frontend/src`. + +| Alteração desejada | Ponto de partida | +| --- | --- | +| Entrada, login e divisão entre admin e cliente | `routers/AppRouter.tsx` | +| Login, cadastro demonstrativo e validação das senhas | `pages/admin/Login/index.tsx` | +| Aparência do login e cadastro | `pages/admin/Login/styles.scss` | +| Rotas administrativas e estados compartilhados | `routers/AdminRoutes.tsx` | +| Endereços de navegação | `routers/paths.ts` | +| Contexto CSS de login, administrativo e cliente | `hooks/useDocumentClass.ts` e estilos de cada tela/layout | +| Rotas e pedidos demonstrativos do cliente | `routers/ClientRoutes.tsx` | +| Itens e ações da barra inferior do cliente | `components/layout/ClientTabBar.tsx` | +| Marcação compartilhada da navegação inferior | `components/layout/BottomNavigation.tsx` | +| Estado e ações de uma página | Diretório `hooks` próximo da página, quando existente | +| Comunicação com o backend | `services/` | +| Cores, fontes, espaçamentos e demais tokens | `styles/themes/`, `styles/base/_tokens.scss`, `styles/abstracts/` | + +O login e o cadastro atuais são demonstrativos; não criam contas nem autenticam no backend. `App.tsx` renderiza `AppRouter`. A entrada `/` abre o login, e os links `/cardapio?mesa=...` abrem diretamente o cliente. Os layouts renderizam `Outlet` e aplicam somente o contexto visual. + +`useDocumentClass` adiciona uma classe ao elemento `html` enquanto a tela está montada e a remove ao sair. Os estilos usam essa classe para controlar o contexto visual e a rolagem elástica. Cada contexto deve ser controlado por uma única instância montada. + +O router administrativo mantém os hooks de mesas, pedidos, produtos e categorias acima das páginas roteadas, preservando esses dados durante a navegação. As páginas são montadas por rota, sem telas ocultas. `useAdminNavigation` foi removido; o histórico do navegador é controlado pelo React Router. O estado demonstrativo administrativo é reiniciado ao recarregar ou sair da área administrativa. + +Ao editar estilos, preserve as classes e a ordem da cascata. Consulte `SCSS-REFACTOR.md` para detalhes sobre tokens, mixins e regras compartilhadas. + +## Admin no celular + +Até 820px, a própria `AdminSidebar` vira uma barra inferior com as cinco seções. As regras ficam em `layouts/adminWorkspace.scss`; o espaço inferior reservado evita que ela cubra o fim das páginas. Acima desse limite, permanece a navegação lateral. + +Na tela de Mesas, a busca principal acompanha o cabeçalho fixo. A segunda busca é ocultada no celular e os filtros de status permitem rolagem horizontal. Os ajustes específicos ficam em `pages/admin/Table/styles.scss`. + +Ao selecionar uma mesa, os detalhes exibem o pedido ativo mais recente associado ao atendimento, seus itens e o total. “Ver pedido” abre o fluxo completo de Pedidos. As etapas existentes continuam sendo solicitar a conta, aguardar pagamento e liberar a mesa. Na última etapa, enquanto existir pedido ativo, a ação leva ao pedido para receber o pagamento; depois do pagamento, volte a Mesas para liberar o atendimento. Se houver outros pedidos ativos, eles também precisam ser finalizados antes de liberar a mesa. + +Esse fluxo continua demonstrativo, com estado em memória. A mudança de responsividade não conecta as operações às APIs nem persiste dados após recarregar a página. + + +O mapa completo das URLs, o formato do QR Code e a configuração de hospedagem estão em [ROUTING.md](ROUTING.md). diff --git a/docs/DESIGN SYSTEM.md b/docs/DESIGN SYSTEM.md new file mode 100644 index 0000000..b9cd9d7 --- /dev/null +++ b/docs/DESIGN SYSTEM.md @@ -0,0 +1,146 @@ +# Design System MesaFlow + +A entrada global é `main.scss`, importada uma vez em `src/main.tsx`, antes de `App`. +Os estilos de páginas e notificações permanecem próximos de seus componentes. +A migração preserva seletores, ordem da cascata, breakpoints, dimensões, animações +e comportamentos. As mudanças visuais se concentram em cores, tipografia, raios e elevação. + +## Estrutura + +```text +src/styles/ +├── abstracts/ +│ ├── _variables.scss # Escalas estáticas internas +│ ├── _functions.scss # Cálculo de espaçamento +│ └── _mixins.scss # Regra de foco +├── base/ +│ ├── _tokens.scss # Escalas públicas, fontes e aliases existentes +│ ├── _reset.scss +│ ├── _fonts.scss # Fontes locais com font-display: swap +│ ├── _typography.scss +│ └── _global.scss +├── components/ +│ ├── _actions.scss +│ ├── _forms.scss +│ ├── _controls.scss +│ ├── _cards.scss +│ ├── _navigation.scss +│ ├── _feedback.scss +│ └── _responsive.scss +├── themes/ +│ ├── _mesaflow.scss # Identidade institucional +│ └── _semantic.scss # Papéis visuais e estados independentes +└── main.scss +``` + +## Tokens e uso + +| Grupo | Tokens principais | +| --- | --- | +| Institucionais | `--brand-primary: #244A3C`, `--brand-accent: #C76D4D`, `--brand-background: #F7F3EA`, `--brand-text: #252927`, `--brand-surface: #FFFEFB` | +| Superfícies | `--background`, `--surface`, `--surface-muted`, `--surface-hover`, `--overlay` | +| Texto e bordas | `--text-primary`, `--text-secondary`, `--text-placeholder`, `--text-inverse`, `--border`, `--border-strong`, `--border-hover` | +| Ações | `--action-primary`, `--action-primary-hover`, `--action-primary-active`, `--on-primary`, `--action-secondary`, `--action-secondary-text`, `--action-secondary-hover`, `--accent`, `--on-accent` | +| Estados | `--success`, `--warning`, `--error`, `--info`, variantes de texto, fundo sutil e borda; `--status-closing` preserva a distinção do fechamento de mesa | +| Interação | `--disabled`, `--disabled-background`, `--disabled-opacity`, `--hover`, `--focus`, `--focus-ring`, `--selected-background`, `--selected-border`, `--control-track` | +| Espaçamento | `--spacing-0` a `--spacing-16`: 0 a 64px em incrementos de 4px | +| Raios | `--radius-small: 6px`, `--radius-medium: 10px`, `--radius-large: 16px`, `--radius-xl: 24px`, `--radius-full: 9999px` | +| Elevação | `--elevation-1` (cards), `--elevation-2` (menus), `--elevation-3` (modais) | +| Tipografia | `--font-display`, `--font-heading`, `--font-body`, `--font-size-xs` a `--font-size-7xl`, pesos de 400 a 800, alturas de linha de título e corpo | + +O catálogo completo de cores está em `themes/_semantic.scss`; as escalas estão em +`base/_tokens.scss` e `abstracts/_variables.scss`. Cores fixas ficam apenas nos temas. +Os gradientes decorativos de alimentos usam `--illustration-*`, separados dos estados. + +Bricolage Grotesque aparece nos títulos de destaque (`h1`); Manrope nos demais títulos, +navegação e controles; DM Sans no corpo e nos textos de leitura. A escala de fontes +é 12, 14, 16, 18, 20, 24, 28, 32, 36, 40 e 48px. Ajustes locais de tamanho e espaçamentos +fora da escala foram mantidos quando já existentes, para evitar um redesign de páginas. +Novas regras devem preferir a escala. As fontes variáveis incluem o subconjunto latino, +com acentos do português, e são empacotadas como WOFF2 locais. + +Aliases existentes continuam disponíveis: `--primary`, `--primary-dark`, `--text`, +`--radius-card`, `--radius-control`, `--shadow-soft`, `--shadow`, `--shadow-large` e +`--space-1` a `--space-6`. Atenção: `--space-5` continua sendo **24px** e `--space-6`, +**32px**. Na nova escala, `--spacing-5` vale **20px** e `--spacing-6`, **24px**. + +## Extensão futura por estabelecimento + +Use CSS Custom Properties para valores que precisam variar em tempo de execução. +Variáveis e funções Sass ficam restritas às escalas estáticas e recursos internos. +Os aliases semânticos são declarados em `:root, [data-theme]` para serem recalculados +no limite do tema, inclusive quando a marca é sobrescrita em um contêiner. + +Um tema futuro pode adicionar, depois da entrada global: + +```scss +[data-theme='restaurante'] { + --brand-primary: #193f57; + --brand-accent: #c8a45c; + --on-primary: #fffefb; + --on-accent: #171a18; +} +``` + +O exemplo é apenas um contrato de extensão. A migração não adiciona estado, +seletor de tema, persistência ou API. +Estados operacionais não derivam das cores institucionais. Ao criar um tema, valide +os pares de contraste; mudar uma cor de marca não garante contraste automaticamente. +Para modais em portais, o escopo do tema deverá incluir também o destino do portal. + +## Arquivos migrados + +Caminhos relativos a `src/`: + +| CSS original | Destino | +| --- | --- | +| `index.css` | `styles/main.scss`, `styles/base/*`, `styles/themes/*` e `styles/abstracts/*` | +| `styles/components.css` | `styles/components/_actions.scss`, `_forms.scss`, `_controls.scss`, `_cards.scss`, `_navigation.scss`, `_feedback.scss`, `_responsive.scss` | +| `components/domain/notifications/style.css` | `components/domain/notifications/style.scss` | +| `pages/admin/Categories/styles.css` | `pages/admin/Categories/styles.scss` | +| `pages/admin/Dashboard/styles.css` | `pages/admin/Dashboard/styles.scss` | +| `pages/admin/Order/styles.css` | `pages/admin/Order/styles.scss` | +| `pages/admin/Products/styles.css` | `pages/admin/Products/styles.scss` | +| `pages/admin/Table/styles.css` | `pages/admin/Table/styles.scss` | +| `pages/client/Cart/styles.css` | `pages/client/Cart/styles.scss` | +| `pages/client/ConfirmOrder/styles.css` | `pages/client/ConfirmOrder/styles.scss` | +| `pages/client/Home/styles.css` | `pages/client/Home/styles.scss` | +| `pages/client/Menu/styles.css` | `pages/client/Menu/styles.scss` | +| `pages/client/OrderSuccess/styles.css` | `pages/client/OrderSuccess/styles.scss` | +| `pages/client/OrderTracking/styles.css` | `pages/client/OrderTracking/styles.scss` | +| `pages/client/ProductDetail/styles.css` | `pages/client/ProductDetail/styles.scss` | +| `pages/client/TableBill/styles.css` | `pages/client/TableBill/styles.scss` | + +## Dependências + +- Desenvolvimento: `sass ^1.106.1`. +- Fontes locais: `@fontsource-variable/bricolage-grotesque`, `@fontsource-variable/manrope` + e `@fontsource-variable/dm-sans`, todas `^5.3.0` (licença OFL-1.1). +- Atualizados os lockfiles npm da raiz e do frontend. O lockfile independente estava + desatualizado em relação ao manifesto; sua sincronização também resolve as versões + já declaradas de Vite e Lucide e suas dependências transitivas. +- Nenhuma biblioteca de componentes adicionada. + +## Validação da migração — 22/10/2026 + +- TypeScript do frontend: `npm exec --workspace frontend -- tsc -b --pretty false`. +- Lint: `npm run lint`; aprovado com dois avisos anteriores em `useProductDetail.ts` + (`react/set-state-in-effect` e `react-hooks/exhaustive-deps`). +- Testes: `npm test`; oito arquivos aprovados pelo runner. Não existe suíte própria + do frontend. A integração com banco requer `RUN_TABLES_DB_TESTS=1` e não foi habilitada. +- Build: `npm run build`, incluindo backend e frontend. O primeiro build encontrou + um Prisma Client local desatualizado; `npm run prisma:generate` resolveu a falha, + sem editar schema, contratos ou código do backend e sem executar migrations. +- Compilação independente das 15 entradas SCSS (global, 13 páginas e notificações). + A auditoria comparou a ordem de 896 seletores locais e 153 seletores compartilhados + com o CSS anterior e preservou as declarações de layout e comportamento. +- Todas as 15 importações locais de estilo resolvidas; nenhum CSS antigo restante + em `src/`, nenhuma cor hexadecimal fora dos temas e nenhuma referência a token indefinido. +- Duplicações: oito grupos de regras idênticas entre Cart, ConfirmOrder e ProductDetail + (`.icon-button`, `.cart-button`, `.cart-button b`, `.bottom-navigation` e quatro regras + de descendentes/estado). Mantidos para preservar a cascata e os pontos de importação; + Menu também possui variantes de alguns desses seletores, com dimensões diferentes. +- Limitação visual: o `App.tsx` já renderizava apenas o título MesaFlow, sem montar as + páginas. Não houve validação em navegador dos fluxos, modais ou responsividade. + A integração das telas e sua revisão visual continuam necessárias antes de liberar + a nova identidade. Rotas, estado, serviços e regras de negócio foram preservados. diff --git a/docs/ROUTING.md b/docs/ROUTING.md new file mode 100644 index 0000000..bd1c541 --- /dev/null +++ b/docs/ROUTING.md @@ -0,0 +1,69 @@ +# Rotas do MesaFlow + +A navegação usa React Router em modo declarativo com `BrowserRouter` e URLs reais, conforme a [documentação de rotas](https://reactrouter.com/start/declarative/routing). `App.tsx` renderiza apenas `AppRouter`. + +## Organização + +- `src/routers/AppRouter.tsx`: entrada, login, divisão admin/cliente e reposicionamento da rolagem entre páginas. +- `src/routers/AdminRoutes.tsx`: páginas administrativas, navegação para pedido por ID e hooks de estado compartilhados. +- `src/routers/ClientRoutes.tsx`: páginas do cliente, parâmetros de produto, contexto da mesa e callbacks de navegação. +- `src/routers/paths.ts`: endereços usados pelos controles de navegação e função que preserva a query string do cliente. +- `src/layouts/AdminWorkspace.tsx` e `ClientWorkspace.tsx`: `Outlet`, classes de contexto e estrutura visual. Não selecionam páginas nem mantêm estado de navegação. + +## Endereços + +| URL | Tela | +| --- | --- | +| `/` | Redireciona para login; com `?mesa=...`, para o cardápio do cliente | +| `/login` | Login e formulário de cadastro demonstrativo | +| `/admin` | Redireciona para dashboard | +| `/admin/dashboard` | Dashboard | +| `/admin/pedidos` | Lista de pedidos | +| `/admin/pedidos/:orderId` | Pedido selecionado; ID desconhecido volta para a lista | +| `/admin/mesas` | Mesas | +| `/admin/cardapio` | Produtos, prévia | +| `/admin/cardapio/produtos` | Todos os produtos | +| `/admin/cardapio/categorias` | Categorias, prévia | +| `/admin/cardapio/categorias/todas` | Todas as categorias | +| `/admin/relatorios` | Relatórios | +| `/cliente` | Boas-vindas do cliente | +| `/cardapio` | Cardápio do cliente e destino do QR Code | +| `/produto/:productId` | Detalhes de produto; ID desconhecido volta para o cardápio | +| `/carrinho` | Carrinho | +| `/confirmar-pedido` | Revisão e confirmação demonstrativa | +| `/acompanhar` | Acompanhamento demonstrativo | +| `/conta` | Conta da mesa | + +Use `?mesa=` em todas as URLs do cliente. Os callbacks do router preservam essa query string, incluindo a codificação do token. O produto é resolvido pelo ID no catálogo atual, permitindo abrir seus detalhes diretamente sem depender de um objeto selecionado na tela anterior. + +## QR Code + +O backend já gera `/cardapio?mesa=` em `table.service.ts`; esse contrato foi mantido, sem modificar o backend. + +A prévia de QR no admin utiliza `table.menuUrl` quando fornecida. Para as mesas demonstrativas sem URL, gera um link `/cardapio?mesa=` na origem atual. A prévia abre o cliente, mas não cria atendimento real. + +O parâmetro `mesa` é transportado na navegação. Esta alteração não resolve tokens em nomes/números de mesa por API, nem implementa envio real de pedidos, autenticação ou pagamentos. O catálogo e os atendimentos continuam demonstrativos. + +O carrinho agora é armazenado por mesa (`mesaflow:cart:`); abrir outro QR cria outro contexto de carrinho. Sem parâmetro de mesa, a chave anterior `mesaflow:cart` continua sendo usada. Dados administrativos permanecem em memória durante a visita ao admin. Nome do cliente e último pedido demonstrativo permanecem em memória durante a visita ao cliente. + +## Histórico, rolagem e publicação + +Os botões navegam pela History API; voltar/avançar do navegador acompanham as URLs. Trocas de tela voltam ao topo. A seleção de card em Pedidos preserva a rolagem realizada pelo próprio card até os detalhes. + +Em produção, o servidor precisa devolver `index.html` para caminhos do frontend que não sejam arquivos existentes. Vite dev/preview já oferecem o fallback de SPA. Em Nginx, a regra equivalente é: + +```nginx +location / { + try_files $uri $uri/ /index.html; +} +``` + +Mantenha as rotas da API separadas desse fallback. Configure a URL pública do frontend no backend para que o QR Code use um endereço acessível pelo celular; `localhost` aponta para o próprio aparelho. + +## Validação + +- `npm --prefix frontend test`: renderiza as definições reais de rotas com `MemoryRouter` e verifica os destinos diretos, produto por ID, categorias e preservação do token. +- `npm --prefix frontend run build`: TypeScript e bundle de produção. +- `npm --prefix frontend run lint` e `npm test`: lint do frontend e testes existentes do backend. + +Os testes de renderização não executam os efeitos nem simulam gestos, foco ou histórico de um navegador real. Validar visualmente o fluxo completo no celular continua necessário. diff --git a/frontend/app/.dockerignore b/frontend/.dockerignore similarity index 100% rename from frontend/app/.dockerignore rename to frontend/.dockerignore diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..4c5cebe --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1 @@ +VITE_API_URL=http://localhost:3333/api/v1 diff --git a/frontend/app/.gitignore b/frontend/.gitignore similarity index 100% rename from frontend/app/.gitignore rename to frontend/.gitignore diff --git a/frontend/app/.oxlintrc.json b/frontend/.oxlintrc.json similarity index 100% rename from frontend/app/.oxlintrc.json rename to frontend/.oxlintrc.json diff --git a/frontend/app/Dockerfile b/frontend/Dockerfile similarity index 54% rename from frontend/app/Dockerfile rename to frontend/Dockerfile index 3fc233a..15265a2 100644 --- a/frontend/app/Dockerfile +++ b/frontend/Dockerfile @@ -4,11 +4,12 @@ WORKDIR /app # Instala dependências primeiro (cache layer) COPY package.json package-lock.json ./ -RUN npm install +RUN npm ci # Copia o restante do código COPY . . EXPOSE 5173 -CMD ["npm", "run", "dev"] +# O volume de node_modules persiste entre builds; sincroniza com o lockfile ao iniciar. +CMD ["sh", "-c", "npm ci && exec npm run dev"] diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..8ddf2a8 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,51 @@ +# Frontend — MesaFlow + +Aplicação React 19 com TypeScript, React Router 7, Vite 8 e Sass. A raiz da aplicação é `frontend/`. + +## Organização + +```text +src/ +├── api/ # Cliente HTTP, URL da API e tratamento de respostas/erros +├── assets/ # Imagens e arquivos importados pelo código, processados pelo Vite +├── components/ # UI reutilizável, componentes de domínio e elementos de layout +├── contexts/ # Estado global: contexto, provider e hook de acesso ao carrinho +├── data/ # Dados estáticos ou mocks compartilhados entre funcionalidades +├── hooks/ # Hooks compartilhados entre páginas e layouts +├── layouts/ # Estruturas visuais das áreas administrativa e cliente +├── pages/ # Páginas e seus componentes, hooks, tipos, mocks e utilitários locais +├── routers/ # Rotas e composição das páginas +├── services/ # Operações de domínio e integrações, usando o cliente HTTP +├── styles/ # Estilos globais, tokens, temas e estilos base +└── utils/ # Funções puras compartilhadas +``` + +`public/` contém arquivos servidos diretamente por URL, sem importação. Use `assets/` para imagens que fazem parte dos módulos. + +Mantenha os nomes das pastas em minúsculas e preserve `contexts/`, já adotado pelo projeto. Código usado por uma única página fica junto dela; promova para uma pasta compartilhada quando houver reutilização real. Evite que componentes compartilhados, services ou utils importem páginas. Imports entre áreas usam `@/`; imports internos da própria funcionalidade podem ser relativos. Tipos usam `import type`. + +O fluxo é: página → hook/context → service → api. Componentes recebem dados e callbacks. Services não navegam nem renderizam mensagens. Utils não acessam rede ou estado React. Os estilos locais continuam próximos dos componentes e páginas. + +O Context atende ao estado global atual. Não há dependência de Redux nem motivo para criar uma pasta vazia; avalie sua adoção quando existirem fluxos globais complexos que justifiquem esse custo. + +## Configuração e comandos + +Copie `.env.example` para `.env` quando precisar configurar a API. `VITE_API_URL` é a URL completa com `/api/v1`, sem barra final. Sem configuração, o cliente usa o protocolo e host da página na porta 3333. Variáveis `VITE_*` são públicas e não devem conter segredos. + +Na raiz do repositório: + +- `npm ci`: instala as dependências do monorepo pelo lockfile da raiz. +- `npm run dev:frontend`: inicia o servidor Vite. +- `npm run build:frontend`: verifica TypeScript e gera o bundle. +- `npm run lint`: executa Oxlint. +- `npm run test:frontend`: executa os testes do frontend. + +O Docker também usa `frontend/` como contexto e origem do bind mount. As dependências declaradas cobrem React/roteamento, ícones, QR Code, fontes, TypeScript, Vite, lint e Sass. Atualizações de versões devem ser avaliadas separadamente; esta revisão preserva as versões existentes. + +## Limites atuais da integração + +A navegação principal do cliente usa `data/catalog.mock.ts`; confirmação de pedidos, acompanhamento e conta ainda têm dados ou ações demonstrativas. O botão de fechamento em `TableBill` confirma apenas na interface. Várias páginas administrativas também usam mocks locais. A existência de services não significa que todas as telas estejam conectadas ao backend. + +O fluxo legado `TableEntry`/`ClientFlow` consulta o cardápio real e desabilita pedidos por QR. O hook legado do carrinho agora exige um atendimento real informado pelo chamador e bloqueia o envio quando não há identificação; o atendimento fixo foi removido. O backend já oferece rotas por token, que exigem integração própria com os dados do cliente. Autenticação e persistência desses fluxos precisam de revisão ao realizar essa integração. + +Se `dist/assets` pertencer ao usuário do container, o build local pode falhar com `EACCES`. Ajuste a propriedade dos arquivos no ambiente. Para validar sem alterar esses arquivos, execute em `frontend/`: `npm run build -- --outDir /tmp/mesaflow-frontend-build`. diff --git a/frontend/app/README.md b/frontend/app/README.md deleted file mode 100644 index cf4f691..0000000 --- a/frontend/app/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Frontend — OrderFlow App - -Interface web desenvolvida com **React** e **Vite**. - -## Stack - -- [React](https://react.dev/) — biblioteca de UI -- [Vite](https://vitejs.dev/) — bundler e dev server -- TypeScript - -## Estrutura - -```text -frontend/app/ -├── public/ # Arquivos estáticos -├── src/ # Código-fonte React -│ ├── assets/ -│ └── ... -├── index.html -├── vite.config.ts -├── tsconfig.json -└── package.json -``` - -## Scripts - -| Comando | Descrição | -|-------------------|----------------------------------| -| `npm run dev` | Inicia o servidor de dev (Vite) | -| `npm run build` | Gera o bundle de produção | -| `npm run preview` | Pré-visualiza o build | - -> Para iniciar frontend e backend juntos, use `npm start` na raiz do monorepo. - -. diff --git a/frontend/app/package-lock.json b/frontend/app/package-lock.json deleted file mode 100644 index 0e21ea2..0000000 --- a/frontend/app/package-lock.json +++ /dev/null @@ -1,1213 +0,0 @@ -{ - "name": "app", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "app", - "version": "0.0.0", - "dependencies": { - "lucide-react": "^1.40.0", - "react": "^19.2.8", - "react-dom": "^19.2.8" - }, - "devDependencies": { - "@types/node": "^24.13.3", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.4", - "oxlint": "^1.75.0", - "typescript": "~6.0.2", - "vite": "^8.2.0" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.143.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz", - "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@oxlint/binding-android-arm-eabi": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.77.0.tgz", - "integrity": "sha512-E06sKWS6PiI6HRxS1wyQg22HvApt01hI7fV+T3wUk3OSbaaP4a3hYGY/MIQDmASqCiRjBdpRQYkgMkqH82cWmQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-android-arm64": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.77.0.tgz", - "integrity": "sha512-NvsKz0KZxTp9cYWPLf+FXaSZwB3oO3peAjtukpOMBgse2vhQSoIIVqeO1yR0lEo/UcdZIDL18uq+kL0LzQ0ytA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-darwin-arm64": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.77.0.tgz", - "integrity": "sha512-bgjTn6nW4bQCFBvSvuHCpDD+sONvmpo4lGI4PxzMt1quBA+xYxhczk6RiCn3GZ9gY8uhaBbwhj9MdKGfu6T9DA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-darwin-x64": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.77.0.tgz", - "integrity": "sha512-aotaIttH1R6j1Rwhx0M0htgeZyGtVQqYNTVEYMN/UcgHPquGA6kmk9OyuDc3a2GKUQBC+3C3GVQCcrRPMYqAFA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-freebsd-x64": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.77.0.tgz", - "integrity": "sha512-nNx/wta7ksRAdYvq+l4AWjXkLxEXHALhENxjj2cYbQAIR4ybaA5L+hCbE63HOmft5czQ6ks+hb8vmEAnn7YGPg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-linux-arm-gnueabihf": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.77.0.tgz", - "integrity": "sha512-tMLLjM7xXtzXisVCzkOTXNCy9bZVId2wteNwjohlFDR/jY6WagpEDA1c1wu4xRc20Hojaxj+V6DSR7gbKxijWA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-linux-arm-musleabihf": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.77.0.tgz", - "integrity": "sha512-MiAFDFaqR0tmHTAyo0YDcZ5hyLREdYw/RQhc2R3cbT+8O3tB+zqPM2th9TTQ+Uo3jn/embS+DO+HyX9ztCPkOQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-linux-arm64-gnu": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.77.0.tgz", - "integrity": "sha512-/xqQ3B16i1T4cyt/9Mn+4CpzhUXoBXp7kVpIwzOXNFLj5JmK1bIjsbSnX296Gg8A/o7oDtKWikFgBx0SLwztkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-linux-arm64-musl": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.77.0.tgz", - "integrity": "sha512-LSbwuRKiNCenPDcbARqAZ5RfBy7gmj7vOvfJRLeCDU3gFtSxWbhv/+VTlaUqzUhNj1gFLHB8h7ALnxa/Az6z6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-linux-ppc64-gnu": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.77.0.tgz", - "integrity": "sha512-QWdcH31mXEUe5Nq1s0CfCpceaKjIo9uZtwDjAuL681g1axf+5x8xrg/eXWaw//4NCxYZ4V4e5Hu5tvdR+pTBlg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-linux-riscv64-gnu": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.77.0.tgz", - "integrity": "sha512-GnOfYgJxbcElOiPZaDFDl406ONddwvOWk2jvAAAEjwAl4GofNoHF+/HHUIBYa6bFCArlcGPi0XjC4cU1pkgF/Q==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-linux-riscv64-musl": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.77.0.tgz", - "integrity": "sha512-AyEMTUCf0xY+hHF+IxqXFQIX0yQOIR8ykpY0lJNOw9xYqOzUX8dyZfRvlG0RfXwuQn2eonf/8NrMmDSZJjdqsA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-linux-s390x-gnu": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.77.0.tgz", - "integrity": "sha512-sPLzEcNvxd/oyVQ5oZo92CiHkFkpBeRop13E/P3TPY+hZfXHKCOWKI70TE2RYwMKFJDc20EMjH16L7NZICtKTw==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-linux-x64-gnu": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.77.0.tgz", - "integrity": "sha512-1Oh2ssH2L7lwyvkdSqaMUfsGfwU2Wfvew+obBUYjRVqhpBcUpwnsPSEr1IzVi9XqkuY10geiLsNKecqaZC34Dw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-linux-x64-musl": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.77.0.tgz", - "integrity": "sha512-0j/2wRgNGO+Qj/M1uu/p57h/hFTTWWcfie0ufkbabeus2s5+/QqkCflnMOwLLN5m2GsNeWp4xdl4cPa4n7QCOQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-openharmony-arm64": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.77.0.tgz", - "integrity": "sha512-BJ/j54qS0usEnyDkLYURMj2iiD9h5Cyy+ppzeMSXBGRXaGRNWnj1Mw14NqWMR5E/PzdgB30OOCCzLzbRoduafw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-win32-arm64-msvc": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.77.0.tgz", - "integrity": "sha512-Yh8w+g2Lpx7StrvtYkoz9JJvXjB9wxgFChFNb85nrXm/wj/XTwGWS1hve9+900HL7llrntYB3YP+y32E3tRqzA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-win32-ia32-msvc": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.77.0.tgz", - "integrity": "sha512-zja5b7+6a7UsRFgAQSrnax5vrzliEyNPLCjfXONu/vTWswaIVZGFajJZptaeRvPE4LghtFdAzVFlexTm7MVTGA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-win32-x64-msvc": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.77.0.tgz", - "integrity": "sha512-+teyvPDZ2RjUvo+SuCqS/UhaJl1QtdW5fWT5NJTV61V5MIuIS90Db9LixmtEGvXixyttiK62P96MSu3UlpviBw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz", - "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz", - "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz", - "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz", - "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz", - "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz", - "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz", - "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz", - "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz", - "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz", - "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz", - "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz", - "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz", - "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz", - "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true - }, - "node_modules/@types/node": { - "version": "24.13.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", - "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", - "dev": true, - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", - "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", - "dev": true, - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", - "dev": true, - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", - "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", - "dev": true, - "dependencies": { - "@rolldown/pluginutils": "^1.0.1" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", - "babel-plugin-react-compiler": "^1.0.0", - "vite": "^8.0.0" - }, - "peerDependenciesMeta": { - "@rolldown/plugin-babel": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - } - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/lightningcss": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", - "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", - "dev": true, - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", - "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", - "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", - "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", - "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", - "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", - "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", - "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", - "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", - "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", - "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/nanoid": { - "version": "3.3.17", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", - "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/oxlint": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.77.0.tgz", - "integrity": "sha512-qnGh8XJHaQ0dprrDXNQZgS0FgjI6v+V3+X8DwmaV++5Aamy6jGKfDdQ1TUvhUxtmKFAbEf4/WeO5QZX+5WSngg==", - "dev": true, - "bin": { - "oxlint": "bin/oxlint" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/sponsors/Boshen" - }, - "optionalDependencies": { - "@oxlint/binding-android-arm-eabi": "1.77.0", - "@oxlint/binding-android-arm64": "1.77.0", - "@oxlint/binding-darwin-arm64": "1.77.0", - "@oxlint/binding-darwin-x64": "1.77.0", - "@oxlint/binding-freebsd-x64": "1.77.0", - "@oxlint/binding-linux-arm-gnueabihf": "1.77.0", - "@oxlint/binding-linux-arm-musleabihf": "1.77.0", - "@oxlint/binding-linux-arm64-gnu": "1.77.0", - "@oxlint/binding-linux-arm64-musl": "1.77.0", - "@oxlint/binding-linux-ppc64-gnu": "1.77.0", - "@oxlint/binding-linux-riscv64-gnu": "1.77.0", - "@oxlint/binding-linux-riscv64-musl": "1.77.0", - "@oxlint/binding-linux-s390x-gnu": "1.77.0", - "@oxlint/binding-linux-x64-gnu": "1.77.0", - "@oxlint/binding-linux-x64-musl": "1.77.0", - "@oxlint/binding-openharmony-arm64": "1.77.0", - "@oxlint/binding-win32-arm64-msvc": "1.77.0", - "@oxlint/binding-win32-ia32-msvc": "1.77.0", - "@oxlint/binding-win32-x64-msvc": "1.77.0" - }, - "peerDependencies": { - "oxlint-tsgolint": ">=7.0.2001", - "vite-plus": "*" - }, - "peerDependenciesMeta": { - "oxlint-tsgolint": { - "optional": true - }, - "vite-plus": { - "optional": true - } - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true - }, - "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.17", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", - "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", - "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.8" - } - }, - "node_modules/lucide-react": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.40.0.tgz", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/rolldown": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz", - "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==", - "dev": true, - "dependencies": { - "@oxc-project/types": "=0.143.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.2.3", - "@rolldown/binding-darwin-arm64": "1.2.3", - "@rolldown/binding-darwin-x64": "1.2.3", - "@rolldown/binding-freebsd-x64": "1.2.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", - "@rolldown/binding-linux-arm64-gnu": "1.2.3", - "@rolldown/binding-linux-arm64-musl": "1.2.3", - "@rolldown/binding-linux-ppc64-gnu": "1.2.3", - "@rolldown/binding-linux-s390x-gnu": "1.2.3", - "@rolldown/binding-linux-x64-gnu": "1.2.3", - "@rolldown/binding-linux-x64-musl": "1.2.3", - "@rolldown/binding-openharmony-arm64": "1.2.3", - "@rolldown/binding-win32-arm64-msvc": "1.2.3", - "@rolldown/binding-win32-x64-msvc": "1.2.3" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true - }, - "node_modules/vite": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", - "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", - "dev": true, - "dependencies": { - "lightningcss": "^1.33.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.25", - "rolldown": "~1.2.1", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - } - } -} diff --git a/frontend/app/public/favicon.svg b/frontend/app/public/favicon.svg deleted file mode 100644 index 6893eb1..0000000 --- a/frontend/app/public/favicon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/app/public/icons.svg b/frontend/app/public/icons.svg deleted file mode 100644 index e952219..0000000 --- a/frontend/app/public/icons.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/app/src/App.tsx b/frontend/app/src/App.tsx deleted file mode 100644 index 015e13b..0000000 --- a/frontend/app/src/App.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { TableEntry } from "@/pages/client/TableEntry"; -import { useEffect, useState } from "react"; -import Categories from "@/pages/admin/categories"; -import Orders from "@/pages/admin/order"; -import Products from "@/pages/admin/products"; -import Tables from "@/pages/admin/table"; -import Dashboard from "@/pages/admin/dashboard"; - -type AdminPage = "client" | "products" | "categories" | "orders" | "tables" | "dashboard"; - -const pageFromPath = (): AdminPage => { - if (window.location.pathname === "/cardapio") return "client"; - if (window.location.pathname.endsWith("/dashboard")) return "dashboard"; - if (window.location.pathname.endsWith("/categories")) return "categories"; - if ( - window.location.pathname.endsWith("/order") || - window.location.pathname.endsWith("/orders") - ) { - return "orders"; - } - if ( - window.location.pathname.endsWith("/table") || - window.location.pathname.endsWith("/tables") - ) { - return "tables"; - } - return "products"; -}; - -function App() { - const [page, setPage] = useState(pageFromPath); - - useEffect(() => { - const handleHistoryChange = () => setPage(pageFromPath()); - window.addEventListener("popstate", handleHistoryChange); - - return () => window.removeEventListener("popstate", handleHistoryChange); - }, []); - - const navigate = (id: string) => { - const destination = - id === "dashboard" - ? { page: "dashboard" as const, path: "/admin/dashboard" } - : id === "categories" - ? { page: "categories" as const, path: "/admin/categories" } - : id === "orders" - ? { page: "orders" as const, path: "/admin/order" } - : id === "tables" - ? { page: "tables" as const, path: "/admin/table" } - : id === "menu" - ? { page: "products" as const, path: "/admin/products" } - : null; - - if (!destination || destination.page === page) return; - - window.history.pushState({}, "", destination.path); - setPage(destination.page); - }; - - if (page === "client") return ; - if (page === "dashboard") return ; - if (page === "categories") return ; - if (page === "orders") return ; - if (page === "tables") return ; - return ; -} - -export default App; diff --git a/frontend/app/src/assets/cardapio-icon.png b/frontend/app/src/assets/cardapio-icon.png deleted file mode 100644 index 3f53ec3..0000000 Binary files a/frontend/app/src/assets/cardapio-icon.png and /dev/null differ diff --git a/frontend/app/src/assets/categorias-icon.png b/frontend/app/src/assets/categorias-icon.png deleted file mode 100644 index 80c8567..0000000 Binary files a/frontend/app/src/assets/categorias-icon.png and /dev/null differ diff --git a/frontend/app/src/assets/dashboard-icon.png b/frontend/app/src/assets/dashboard-icon.png deleted file mode 100644 index 659ebff..0000000 Binary files a/frontend/app/src/assets/dashboard-icon.png and /dev/null differ diff --git a/frontend/app/src/assets/hero.png b/frontend/app/src/assets/hero.png deleted file mode 100644 index 02251f4..0000000 Binary files a/frontend/app/src/assets/hero.png and /dev/null differ diff --git a/frontend/app/src/assets/lixeira-icon.png b/frontend/app/src/assets/lixeira-icon.png deleted file mode 100644 index 5801dfd..0000000 Binary files a/frontend/app/src/assets/lixeira-icon.png and /dev/null differ diff --git a/frontend/app/src/assets/mesas-icon.png b/frontend/app/src/assets/mesas-icon.png deleted file mode 100644 index 3351172..0000000 Binary files a/frontend/app/src/assets/mesas-icon.png and /dev/null differ diff --git a/frontend/app/src/assets/pedidos-icon.png b/frontend/app/src/assets/pedidos-icon.png deleted file mode 100644 index a16d716..0000000 Binary files a/frontend/app/src/assets/pedidos-icon.png and /dev/null differ diff --git a/frontend/app/src/assets/react.svg b/frontend/app/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/frontend/app/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/app/src/assets/relatorios-icon.png b/frontend/app/src/assets/relatorios-icon.png deleted file mode 100644 index aa88b4a..0000000 Binary files a/frontend/app/src/assets/relatorios-icon.png and /dev/null differ diff --git a/frontend/app/src/assets/vite.svg b/frontend/app/src/assets/vite.svg deleted file mode 100644 index 5101b67..0000000 --- a/frontend/app/src/assets/vite.svg +++ /dev/null @@ -1 +0,0 @@ -Vite diff --git a/frontend/app/src/components/README.md b/frontend/app/src/components/README.md deleted file mode 100644 index 2eba943..0000000 --- a/frontend/app/src/components/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Componentes MesaFlow - -Componentes React em TypeScript organizados por domínio do design system: - -- `buttons`: quadro 04 — ações, ícones e quantidade. -- `inputs`: quadro 05 — input, textarea e select. -- `forms`: quadro 06 — controles, menu, abas, breadcrumb, paginação e dica. -- `cards`: quadro 07 — produto, pedido, mesa e estatística. -- `status`: quadro 08 — etiquetas de status. -- `navigation`: quadro 09 — navegação de cliente e administrativo. -- `restaurant`: quadro 10 — carrinho, andamento do pedido e mesas. -- `feedback`: quadro 11 — modal, toast, alerta e estados de carregamento. - -Use o ponto único de importação: - -```tsx -import { Button, ProductCard, Badge } from "./components"; -``` - -Os componentes utilizam os tokens de `global.css` e seus estilos portáveis estão em `src/styles/components.css`. Em uma aplicação React, importe os dois arquivos na entrada antes de renderizá-los: - -```tsx -import "./global.css"; -import "./styles/components.css"; -``` diff --git a/frontend/app/src/components/forms/index.ts b/frontend/app/src/components/forms/index.ts deleted file mode 100644 index bdef2e1..0000000 --- a/frontend/app/src/components/forms/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from "./Checkbox"; -export * from "./Radio"; -export * from "./Switch"; -export * from "./Tabs"; -export * from "./Pagination"; -export * from "./Breadcrumbs"; -export * from "./Dropdown"; -export * from "./Tooltip"; diff --git a/frontend/app/src/components/index.ts b/frontend/app/src/components/index.ts deleted file mode 100644 index 8c31fec..0000000 --- a/frontend/app/src/components/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** Ponto único de importação dos componentes MesaFlow. */ -export * from "./buttons"; -export * from "./inputs"; -export * from "./forms"; -export * from "./cards"; -export * from "./status"; -export * from "./navigation"; -export * from "./restaurant"; -export * from "./feedback"; diff --git a/frontend/app/src/components/navigation/BottomNavigation.tsx b/frontend/app/src/components/navigation/BottomNavigation.tsx deleted file mode 100644 index ce42d83..0000000 --- a/frontend/app/src/components/navigation/BottomNavigation.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { ReactNode } from "react"; -import { classNames } from "../shared/classNames"; - -type NavigationItem = { id: string; label: string; icon?: ReactNode }; -type BottomNavigationProps = { items: NavigationItem[]; activeId: string; onChange: (id: string) => void }; - -export function BottomNavigation({ items, activeId, onChange }: BottomNavigationProps) { - return ; -} diff --git a/frontend/app/src/components/notifications/index.tsx b/frontend/app/src/components/notifications/index.tsx deleted file mode 100644 index 8f7cdec..0000000 --- a/frontend/app/src/components/notifications/index.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { BellIcon } from "lucide-react"; -import { - findClosingRequestedAttendances, - findCancelledAttendances, - type ClosingRequestedAttendance, - type CancelledResquestAttendance, -} from "../../services/attendance.service"; -import { useEffect, useId, useState } from "react"; -import "./style.css"; - -export function Notifications() { - const [closingRequests, setClosingRequests] = useState([]); - const [cancelledRequests, setCancelledRequests] = useState([]); - const [notificationsOpen, setNotificationsOpen] = useState(false); - const panelId = useId(); - - useEffect(() => { - const fetchClosingRequests = async () => { - try { - const requests = await findClosingRequestedAttendances(); - setClosingRequests(requests); - } catch (error) { - console.error("Erro ao buscar solicitações de fechamento:", error); - } - }; - fetchClosingRequests(); - - const interval = setInterval(fetchClosingRequests, 5000); // Atualiza a cada 5 segundos - - return () => clearInterval(interval); // Limpa o intervalo ao desmontar o componente - }, []); - - useEffect(() => { - const fetchCancelledRequests = async () => { - try { - const requests = await findCancelledAttendances(); - setCancelledRequests(requests); - } catch (error) { - console.error("Erro ao buscar solicitações canceladas:", error); - } - }; - fetchCancelledRequests(); - - const interval = setInterval(fetchCancelledRequests, 5000); // Atualiza a cada 5 segundos - - return () => clearInterval(interval); // Limpa o intervalo ao desmontar o componente - }, []); - - const notifications = [...closingRequests, ...cancelledRequests].sort( - (a, b) => new Date(b.closingRequestedAt).getTime() - new Date(a.closingRequestedAt).getTime(), - ); - - return ( -
- - {notificationsOpen && ( -
-

Notificações

- {notifications.length === 0 ? ( -

Nenhuma notificação no momento.

- ) : ( -
    - {notifications.map((request) => ( -
  • - - {request.status === "CANCELLED" ? "Solicitação cancelada" : "Fechamento solicitado"} - - Mesa {request.table.number} - -
  • - ))} -
- )} -
- )} -
- ); -} diff --git a/frontend/app/src/components/notifications/style.css b/frontend/app/src/components/notifications/style.css deleted file mode 100644 index 36ff06f..0000000 --- a/frontend/app/src/components/notifications/style.css +++ /dev/null @@ -1,146 +0,0 @@ -.notifications-container { - position: relative; - flex: 0 0 44px; - color: var(--text); - font-family: var(--font-body); -} - -.notifications-button { - position: relative; - display: grid; - width: 44px; - height: 44px; - padding: 0; - place-items: center; - border: 1px solid var(--border); - border-radius: var(--radius-control); - background: var(--surface); - color: var(--primary-dark); - box-shadow: var(--shadow-soft); - transition: background 150ms ease, border-color 150ms ease; -} - -.notifications-button:hover, -.notifications-button[aria-expanded="true"] { - border-color: var(--primary); - background: #fff3eb; -} - -.notifications-button:focus-visible { - outline: 3px solid rgb(201 76 59 / 24%); - outline-offset: 2px; -} - -.notifications-button svg { - width: 20px; - height: 20px; -} - -.notifications-button .notification-badge { - position: absolute; - top: -6px; - right: -6px; - display: grid; - min-width: 20px; - height: 20px; - padding: 0 5px; - place-items: center; - border: 2px solid var(--surface); - border-radius: var(--radius-full); - background: var(--primary); - color: var(--surface); - font-size: 11px; - font-weight: 700; - font-variant-numeric: tabular-nums; - line-height: 1; -} - -.notifications-panel { - position: absolute; - z-index: 100; - top: calc(100% + 8px); - right: 0; - width: min(360px, calc(100vw - 32px)); - max-height: min(480px, 60dvh); - overflow-y: auto; - overscroll-behavior: contain; - border: 1px solid var(--border); - border-radius: var(--radius-control); - background: var(--surface); - box-shadow: var(--shadow-large); -} - -.notifications-panel > h3 { - margin: 0; - padding: 16px 20px; - border-bottom: 1px solid var(--border); - background: #fff3eb; - color: var(--primary-dark); - font-size: 14px; - font-weight: 700; - line-height: 1.4; -} - -.notifications-panel > ul { - margin: 0; - padding: 0; - list-style: none; -} - -.notifications-panel > ul > li { - display: grid; - gap: 4px; - padding: 16px 20px; - border-left: 3px solid var(--notification-color); - color: var(--text); - font-size: 13px; - line-height: 1.6; - overflow-wrap: anywhere; -} - -.notification-item--closing { - --notification-color: var(--warning); - --notification-label: #8c5713; - background: rgb(217 144 32 / 6%); -} - -.notification-item--cancelled { - --notification-color: var(--error); - --notification-label: var(--error); - background: rgb(192 57 43 / 6%); -} - -.notification-item .notification-type { - color: var(--notification-label); - font-size: 12px; - font-weight: 700; -} - -.notification-item strong { - font-size: 14px; -} - -.notification-item time { - color: var(--text-secondary); - font-size: 12px; -} - -.notifications-panel > ul > li + li { - border-top: 1px solid var(--border); -} - -.notifications-panel > p { - margin: 0; - padding: 24px 20px; - color: var(--text-secondary); - font-size: 13px; - line-height: 1.5; - text-align: center; -} - -@media (max-width: 720px) { - .notifications-panel > h3, - .notifications-panel > ul > li { - padding: 16px; - } -} diff --git a/frontend/app/src/index.css b/frontend/app/src/index.css deleted file mode 100644 index 0acad56..0000000 --- a/frontend/app/src/index.css +++ /dev/null @@ -1,145 +0,0 @@ -/* MesaFlow — fundações globais - Importe este arquivo antes do CSS específico de cada tela. */ -:root { - /* Cores */ - --primary: #c94c3b; - --primary-dark: #8f2f26; - --accent: #e9a23b; - --background: #fff8f0; - --surface: #ffffff; - --text: #292524; - --text-secondary: #78716c; - --border: #e7e0d8; - --success: #3f8f5b; - --warning: #d99020; - --error: #c0392b; - --info: #3a6f8f; - - /* Tipografia */ - --font-heading: Poppins, Inter, system-ui, sans-serif; - --font-body: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - - /* Espaçamento, cantos e elevação */ - --space-1: 4px; - --space-2: 8px; - --space-3: 12px; - --space-4: 16px; - --space-5: 24px; - --space-6: 32px; - --radius-small: 4px; - --radius-card: 8px; - --radius-control: 12px; - --radius-large: 16px; - --radius-full: 999px; - --shadow-soft: 0 2px 10px rgb(41 37 36 / 6%); - --shadow: 0 8px 24px rgb(41 37 36 / 8%); - --shadow-large: 0 16px 40px rgb(41 37 36 / 10%); -} - -*, -*::before, -*::after { - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -body { - min-width: 320px; - min-height: 100vh; - margin: 0; - background: var(--background); - color: var(--text); - font-family: var(--font-body); - font-size: 16px; - line-height: 1.5; - -webkit-font-smoothing: antialiased; - text-rendering: optimizeLegibility; -} - -img, -picture, -video, -canvas, -svg { - display: block; - max-width: 100%; -} - -button, -input, -select, -textarea { - font: inherit; -} - -button, -select { - cursor: pointer; -} - -button:disabled, -input:disabled, -select:disabled, -textarea:disabled { - cursor: not-allowed; -} - -a { - color: inherit; -} - -h1, -h2, -h3, -h4, -p { - margin-top: 0; -} - -h1, -h2, -h3, -h4 { - margin-bottom: 0; - font-family: var(--font-heading); - line-height: 1.14; - letter-spacing: 0; -} - -p { - margin-bottom: 0; -} - -svg { - width: 20px; - height: 20px; - fill: none; - stroke: currentColor; - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 2; -} - -:focus-visible { - outline: 3px solid rgb(201 76 59 / 24%); - outline-offset: 2px; -} - -@media (prefers-reduced-motion: reduce) { - html { - scroll-behavior: auto; - } - - *, - *::before, - *::after { - animation-duration: .01ms !important; - animation-iteration-count: 1 !important; - transition-duration: .01ms !important; - } -} - -.table-origin { padding: 12px 20px; background: #f5eee6; color: #493529; text-align: center; font-weight: 600; } diff --git a/frontend/app/src/layouts/.ts b/frontend/app/src/layouts/.ts deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/app/src/main.tsx b/frontend/app/src/main.tsx deleted file mode 100644 index 0c9f991..0000000 --- a/frontend/app/src/main.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import './styles/components.css' -import App from './App.tsx' -import { CartProvider } from './contexts/CartProvider.tsx' - -createRoot(document.getElementById('root')!).render( - - - - - , -) \ No newline at end of file diff --git a/frontend/app/src/pages/.ts b/frontend/app/src/pages/.ts deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/app/src/pages/admin/categories/index.tsx b/frontend/app/src/pages/admin/categories/index.tsx deleted file mode 100644 index 80636e2..0000000 --- a/frontend/app/src/pages/admin/categories/index.tsx +++ /dev/null @@ -1,439 +0,0 @@ -import { useState, type FormEvent } from "react"; -import { Plus, Search, Tag } from "lucide-react"; -import { Button } from "@/components/buttons/Button"; -import { AdminSidebar } from "@/components/navigation/AdminSidebar"; -import { Switch } from "@/components/forms/Switch"; -import { TextAreaField } from "@/components/inputs/TextAreaField"; -import { TextField } from "@/components/inputs/TextField"; -import trashIcon from "@/assets/lixeira-icon.png"; -import "./styles.css"; - -type Category = { - id: number; - name: string; - description: string; - productCount: number; - active: boolean; -}; - -type CategoryForm = Pick; - -type CategoriesProps = { - onNavigate?: (id: string) => void; -}; - -const emptyCategoryForm: CategoryForm = { - name: "", - description: "", - active: true, -}; - -const initialCategories: Category[] = [ - { - id: 1, - name: "Lanches", - description: "Hambúrgueres, sanduíches e opções rápidas.", - productCount: 8, - active: true, - }, - { - id: 2, - name: "Pizzas", - description: "Pizzas tradicionais e especiais da casa.", - productCount: 12, - active: true, - }, - { - id: 3, - name: "Porções", - description: "Porções para compartilhar e acompanhamentos.", - productCount: 6, - active: true, - }, - { - id: 4, - name: "Bebidas", - description: "Sucos, refrigerantes e outras bebidas.", - productCount: 10, - active: true, - }, - { - id: 5, - name: "Sobremesas", - description: "Doces e sobremesas para finalizar o pedido.", - productCount: 4, - active: false, - }, -]; - -export default function Categories({ onNavigate }: CategoriesProps) { - const [activeItem, setActiveItem] = useState("categories"); - const [categories, setCategories] = useState(initialCategories); - const [searchQuery, setSearchQuery] = useState(""); - const [isModalOpen, setIsModalOpen] = useState(false); - const [editingCategoryId, setEditingCategoryId] = useState( - null, - ); - const [categoryForm, setCategoryForm] = - useState(emptyCategoryForm); - const [categoryToDelete, setCategoryToDelete] = useState( - null, - ); - - const normalizedSearch = searchQuery.trim().toLocaleLowerCase("pt-BR"); - const filteredCategories = categories.filter((category) => - `${category.name} ${category.description}` - .toLocaleLowerCase("pt-BR") - .includes(normalizedSearch), - ); - - const closeModal = () => { - setIsModalOpen(false); - setEditingCategoryId(null); - setCategoryForm(emptyCategoryForm); - }; - - const openNewCategory = () => { - setEditingCategoryId(null); - setCategoryForm(emptyCategoryForm); - setIsModalOpen(true); - }; - - const openEditCategory = (category: Category) => { - setEditingCategoryId(category.id); - setCategoryForm({ - name: category.name, - description: category.description, - active: category.active, - }); - setIsModalOpen(true); - }; - - const handleSaveCategory = (event: FormEvent) => { - event.preventDefault(); - - setCategories((currentCategories) => { - if (editingCategoryId !== null) { - return currentCategories.map((category) => - category.id === editingCategoryId - ? { - ...category, - name: categoryForm.name.trim(), - description: categoryForm.description.trim(), - active: categoryForm.active, - } - : category, - ); - } - - const nextId = - Math.max(0, ...currentCategories.map((category) => category.id)) + 1; - - return [ - ...currentCategories, - { - id: nextId, - name: categoryForm.name.trim(), - description: categoryForm.description.trim(), - productCount: 0, - active: categoryForm.active, - }, - ]; - }); - - closeModal(); - }; - - const updateCategoryStatus = (categoryId: number, active: boolean) => { - setCategories((currentCategories) => - currentCategories.map((category) => - category.id === categoryId ? { ...category, active } : category, - ), - ); - }; - - const confirmCategoryDeletion = () => { - if (!categoryToDelete) return; - - setCategories((currentCategories) => - currentCategories.filter( - (category) => category.id !== categoryToDelete.id, - ), - ); - setCategoryToDelete(null); - }; - - return ( -
- { - setActiveItem(id); - onNavigate?.(id); - }} - userName="Nome do usuário" - userRole="Administrador" - /> - -
-
-
-

CATEGORIAS

-

Organize os produtos para facilitar a escolha dos clientes.

-
- -
- - -
-
- -
-
-
-

Categorias do cardápio

-

- {normalizedSearch - ? `${filteredCategories.length} de ${categories.length} categorias` - : `${categories.length} categorias cadastradas`} -

-
-
- -
- {filteredCategories.map((category) => ( -
-
- -
-

{category.name}

-

{category.description}

-
-
- -
- {category.productCount} - - {category.productCount === 1 ? "produto" : "produtos"} - -
- -
- - updateCategoryStatus(category.id, event.target.checked) - } - /> - - -
-
- ))} - - {filteredCategories.length === 0 && ( -
-
- )} -
-
-
- - {isModalOpen && ( -
{ - if (event.target === event.currentTarget) closeModal(); - }} - > -
-
-
-

- {editingCategoryId === null - ? "Nova categoria" - : "Editar categoria"} -

-

- {editingCategoryId === null - ? "Crie uma categoria para organizar os produtos do cardápio." - : "Atualize as informações da categoria selecionada."} -

-
- -
- -
- - setCategoryForm((current) => ({ - ...current, - name: event.target.value, - })) - } - /> - - - setCategoryForm((current) => ({ - ...current, - description: event.target.value, - })) - } - /> - -
- Status - - setCategoryForm((current) => ({ - ...current, - active: event.target.checked, - })) - } - /> -
- -
- {editingCategoryId !== null && ( - - )} -
- - -
-
- -
-
- )} - - {categoryToDelete && ( -
{ - if (event.target === event.currentTarget) setCategoryToDelete(null); - }} - > -
- -

Excluir categoria?

-

- “{categoryToDelete.name}” será removida da lista. Os produtos - vinculados não serão excluídos. -

-
- - -
-
-
- )} -
- ); -} diff --git a/frontend/app/src/pages/admin/categories/styles.css b/frontend/app/src/pages/admin/categories/styles.css deleted file mode 100644 index 4127932..0000000 --- a/frontend/app/src/pages/admin/categories/styles.css +++ /dev/null @@ -1,495 +0,0 @@ -.categories-page { - display: grid; - grid-template-columns: 240px minmax(0, 1fr); - min-height: 100vh; -} - -.categories-page > .admin-sidebar { - position: sticky; - top: 0; - height: 100vh; - border-block: 0; - border-left: 0; - border-radius: 0; -} - -.categories-content { - min-width: 0; - padding: clamp(24px, 4vw, 48px); - font-family: var(--font-body); -} - -.categories-topbar { - display: flex; - gap: 24px; - align-items: flex-end; - justify-content: space-between; -} - -.categories-heading { - max-width: 720px; -} - -.categories-heading h1 { - color: var(--primary-dark); - font-family: var(--font-heading); - font-size: clamp(28px, 3vw, 40px); - font-weight: 800; -} - -.categories-heading p { - margin-top: 10px; - color: var(--text-secondary); - font-size: 16px; -} - -.categories-topbar-actions { - display: flex; - gap: 12px; - align-items: center; - flex: 0 0 auto; -} - -.category-search { - display: flex; - width: min(280px, 34vw); - min-height: 44px; - gap: 10px; - align-items: center; - padding: 0 12px; - border: 1px solid var(--border); - border-radius: var(--radius-control); - background: var(--surface); - color: var(--text-secondary); -} - -.category-search:focus-within { - border-color: var(--primary); - box-shadow: 0 0 0 3px rgb(201 76 59 / 14%); -} - -.category-search svg { - width: 18px; - height: 18px; - flex: 0 0 18px; -} - -.category-search input { - width: 100%; - min-width: 0; - border: 0; - outline: 0; - background: transparent; - color: var(--text); -} - -.category-search input::placeholder { - color: var(--text-secondary); -} - -.categories-list { - overflow: hidden; - margin-top: 36px; - border: 1px solid var(--border); - border-radius: var(--radius-control); - background: var(--surface); - box-shadow: var(--shadow-soft); -} - -.categories-list-heading { - padding: 20px 24px; - border-bottom: 1px solid var(--border); -} - -.categories-list-heading h2 { - color: var(--text); - font-size: 18px; -} - -.categories-list-heading p { - margin-top: 4px; - color: var(--text-secondary); - font-size: 13px; -} - -.categories-list-body { - display: grid; -} - -.category-row { - display: grid; - grid-template-columns: minmax(280px, 1fr) 100px minmax(250px, auto); - gap: 24px; - align-items: center; - padding: 18px 24px; -} - -.category-row + .category-row { - border-top: 1px solid var(--border); -} - -.category-identity { - display: flex; - min-width: 0; - gap: 14px; - align-items: center; -} - -.category-mark { - position: relative; - display: grid; - width: 48px; - height: 48px; - flex: 0 0 48px; - place-items: center; - border: 1px solid rgb(201 76 59 / 16%); - border-radius: var(--radius-card); - background: #fff3eb; - color: var(--primary-dark); -} - -.category-mark svg { - width: 27px; - height: 27px; - opacity: 0.68; -} - -.category-mark strong { - position: absolute; - right: 4px; - bottom: 2px; - font-family: var(--font-heading); - font-size: 11px; -} - -.category-identity h3 { - overflow: hidden; - color: var(--text); - font-size: 15px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.category-identity p { - overflow: hidden; - margin-top: 4px; - color: var(--text-secondary); - font-size: 13px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.category-product-count { - display: grid; - text-align: center; -} - -.category-product-count strong { - color: var(--primary-dark); - font-family: var(--font-heading); - font-size: 17px; -} - -.category-product-count span { - color: var(--text-secondary); - font-size: 12px; -} - -.category-controls { - display: flex; - gap: 16px; - align-items: center; - justify-content: flex-end; -} - -.category-status { - min-width: 92px; - min-height: 36px; - padding: 0; - font-size: 13px; -} - -.category-status.is-active { - color: #2c6e43; -} - -.category-status.is-inactive { - color: #9c2f24; -} - -.category-delete-button { - display: grid; - width: 36px; - height: 36px; - flex: 0 0 36px; - padding: 8px; - place-items: center; - border: 0; - border-radius: var(--radius-card); - background: rgb(192 57 43 / 8%); -} - -.category-delete-button img, -.category-delete-dialog-icon img { - width: 22px; - height: 22px; - filter: brightness(0) saturate(100%) invert(24%) sepia(40%) saturate(2137%) hue-rotate(337deg) brightness(90%) contrast(91%); - object-fit: contain; -} - -.categories-no-results { - display: grid; - gap: 10px; - place-items: center; - padding: 42px 24px; - color: var(--text-secondary); - text-align: center; -} - -.categories-no-results svg { - width: 32px; - height: 32px; - color: var(--primary); - opacity: 0.55; -} - -.category-modal-backdrop { - position: fixed; - inset: 0; - z-index: 20; - display: grid; - overflow-y: auto; - place-items: center; - padding: 24px; - background: rgb(41 37 36 / 58%); -} - -.category-modal { - width: min(100%, 560px); - max-height: calc(100vh - 48px); - overflow-y: auto; - border: 1px solid var(--border); - border-radius: var(--radius-large); - background: var(--surface); - box-shadow: var(--shadow-large); -} - -.category-modal-heading { - display: flex; - gap: 20px; - align-items: flex-start; - justify-content: space-between; - padding: 24px 24px 18px; - border-bottom: 1px solid var(--border); -} - -.category-modal-heading h2 { - color: var(--primary-dark); - font-size: 22px; -} - -.category-modal-heading p { - margin-top: 6px; - color: var(--text-secondary); - font-size: 14px; -} - -.category-modal-close { - display: grid; - width: 36px; - height: 36px; - flex: 0 0 36px; - padding: 0; - place-items: center; - border: 1px solid var(--border); - border-radius: var(--radius-card); - background: var(--surface); - color: var(--text-secondary); - font-size: 24px; - line-height: 1; -} - -.category-form { - display: grid; - gap: 18px; - padding: 24px; -} - -.category-form textarea { - min-height: 96px; -} - -.category-form-status { - display: grid; - gap: 8px; - color: var(--text); - font-size: 14px; - font-weight: 700; -} - -.category-form-status .switch-control { - width: fit-content; -} - -.category-modal-actions { - display: flex; - gap: 10px; - align-items: center; - justify-content: space-between; - margin: 4px -24px -24px; - padding: 18px 24px; - border-top: 1px solid var(--border); - background: #fffaf5; -} - -.category-delete-action { - background: rgb(192 57 43 / 8%); - color: #9c2f24; -} - -.category-modal-save-actions { - display: flex; - gap: 10px; - margin-left: auto; -} - -.category-delete-dialog { - width: min(100%, 420px); - padding: 28px; - border-radius: var(--radius-large); - background: var(--surface); - box-shadow: var(--shadow-large); - text-align: center; -} - -.category-delete-dialog-icon { - display: grid; - width: 48px; - height: 48px; - margin: 0 auto 18px; - padding: 12px; - place-items: center; - border-radius: var(--radius-card); - background: rgb(192 57 43 / 8%); -} - -.category-delete-dialog h2 { - color: var(--text); - font-size: 22px; -} - -.category-delete-dialog p { - margin-top: 10px; - color: var(--text-secondary); - font-size: 14px; -} - -.category-delete-dialog footer { - display: flex; - gap: 10px; - justify-content: center; - margin-top: 24px; -} - -@media (max-width: 1040px) and (min-width: 721px) { - .category-row { - grid-template-columns: minmax(0, 1fr) auto; - } - - .category-product-count { - justify-items: end; - } - - .category-controls { - grid-column: 1 / -1; - } -} - -@media (max-width: 720px) { - .categories-page { - grid-template-columns: 1fr; - } - - .categories-page > .admin-sidebar { - position: static; - height: auto; - border-right: 0; - } - - .categories-content { - padding: 24px 16px; - } - - .categories-topbar { - align-items: flex-start; - flex-direction: column; - } - - .categories-topbar-actions { - width: 100%; - align-items: stretch; - flex-direction: column; - } - - .category-search, - .categories-topbar-actions > .btn { - width: 100%; - } - - .category-row { - grid-template-columns: minmax(0, 1fr) auto; - gap: 16px; - padding: 18px; - } - - .category-product-count { - justify-items: end; - } - - .category-controls { - grid-column: 1 / -1; - justify-content: flex-start; - } -} - -@media (max-width: 420px) { - .category-row { - grid-template-columns: 1fr; - } - - .category-product-count { - display: flex; - gap: 5px; - justify-self: start; - } - - .category-controls { - grid-column: 1; - flex-wrap: wrap; - } - - .category-modal-backdrop { - align-items: end; - padding: 0; - } - - .category-modal { - max-height: 92vh; - border-radius: var(--radius-large) var(--radius-large) 0 0; - } - - .category-modal-actions { - align-items: stretch; - flex-direction: column-reverse; - } - - .category-modal-save-actions { - width: 100%; - margin-left: 0; - } - - .category-modal-save-actions .btn, - .category-modal-actions > .btn { - flex: 1; - } -} diff --git a/frontend/app/src/pages/admin/dashboard/index.tsx b/frontend/app/src/pages/admin/dashboard/index.tsx deleted file mode 100644 index 555725d..0000000 --- a/frontend/app/src/pages/admin/dashboard/index.tsx +++ /dev/null @@ -1,353 +0,0 @@ -import { useState } from "react"; -import { Info, Search } from "lucide-react"; -import { AdminSidebar } from "@/components/navigation/AdminSidebar"; -import { StatCard } from "@/components/cards/StatCard"; -import { Badge } from "@/components/status/Badge"; -import { Button } from "@/components/buttons/Button"; -import { Notifications } from "../../../components/notifications"; -import "./styles.css"; - -// Dados demonstrativos locais para a representação visual do Dashboard. -const weeklySales = [4200, 5800, 4900, 7200, 6100, 8400, 7500]; -const chartSalesHistory = Array.from({ length: 30 }, (_, index) => { - const daysAgo = 29 - index; - return weeklySales[6 - (daysAgo % 7)]; -}); -const chartPeriods = [7, 14, 30]; -const topProducts = [ - { name: "Risoto de cogumelos", quantity: 42 }, - { name: "Nhoque artesanal", quantity: 36 }, - { name: "Limonada com hortelã", quantity: 31 }, - { name: "Pudim de doce de leite", quantity: 24 }, -]; -const operationalSummary = [ - { label: "Tempo médio de preparo", value: "18 min" }, - { label: "Mesas atendidas", value: "52" }, - { label: "Pedidos cancelados", value: "2" }, - { label: "Pagamentos concluídos", value: "47" }, -]; - -type DashboardPeriod = "today" | "7days" | "30days" | "all" | "specific"; - -const dateDaysAgo = (days: number) => { - const date = new Date(); - date.setDate(date.getDate() - days); - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; -}; - - - -// Histórico demonstrativo local, sem integração com serviços. -const salesHistory = [ - { date: dateDaysAgo(0), sales: 8420, ticket: 96.4, orders: 87 }, - { date: dateDaysAgo(1), sales: 7518, ticket: 92.7, orders: 81 }, - { date: dateDaysAgo(4), sales: 6240, ticket: 89.14, orders: 70 }, - { date: dateDaysAgo(12), sales: 9150, ticket: 101.67, orders: 90 }, - { date: dateDaysAgo(35), sales: 5680, ticket: 88.75, orders: 64 }, -]; - -const currency = (value: number) => - value.toLocaleString("pt-BR", { - style: "currency", - currency: "BRL", - minimumFractionDigits: 2, - }); - -const orders = [ - { - id: 1842, - description: - "Mesa 08 · 2x Risoto de cogumelos, 1x Limonada com hortelã, 1x Pudim de doce de leite", - status: "Em preparo", - tone: "blue", - }, - { - id: 1843, - description: "Mesa 12 · 2x Nhoque artesanal", - status: "Aguardando", - tone: "amber", - }, -]; - -const tables = [ - "available", - "occupied", - "occupied", - "available", - "payment", - "available", - "occupied", - "occupied", - "available", - "payment", -]; -const tableLabels: Record = { - available: "Disponível", - occupied: "Ocupada", - payment: "Aguardando pagamento", -}; - -type DashboardProps = { onNavigate?: (id: string) => void }; - -export default function Dashboard({ onNavigate }: DashboardProps) { - const [chartPeriod, setChartPeriod] = useState(7); - const salesDays = chartSalesHistory.slice(-chartPeriod).map((value, index) => { - const date = new Date(); - date.setDate(date.getDate() - (chartPeriod - 1 - index)); - return { - value, - label: date.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit" }), - }; - }); - - - const [period, setPeriod] = useState("today"); - const [specificDate, setSpecificDate] = useState(() => dateDaysAgo(0)); - const today = dateDaysAgo(0); - const startDate = dateDaysAgo( - period === "7days" ? 6 : period === "30days" ? 29 : 0, - ); - const selectedSales = salesHistory.filter(({ date }) => { - if (period === "all") return true; - if (period === "specific") return date === specificDate; - return date >= startDate && date <= today; - }); - const totalSales = selectedSales.reduce((total, day) => total + day.sales, 0); - const totalOrders = selectedSales.reduce( - (total, day) => total + day.orders, - 0, - ); - const averageTicket = totalOrders - ? selectedSales.reduce((total, day) => total + day.ticket * day.orders, 0) / - totalOrders - : 0; - const periodDetail = selectedSales.length - ? "No período selecionado" - : "Sem vendas no período"; - const indicators = [ - { - label: period === "today" ? "Vendas hoje" : "Vendas no período", - value: totalSales.toLocaleString("pt-BR", { - style: "currency", - currency: "BRL", - maximumFractionDigits: 0, - }), - detail: period === "today" ? "+12% vs. ontem" : periodDetail, - }, - { label: "Pedidos ativos", value: "3", detail: "3 em preparo agora" }, - { label: "Mesas ocupadas", value: "14/20", detail: "70% do salão agora" }, - { - label: "Ticket médio", - value: currency(averageTicket), - detail: period === "today" ? "+4% na semana" : periodDetail, - }, - ]; - - return ( -
- onNavigate?.(id)} - userName="Nome do usuário" - userRole="Administrador" - /> - -
-
-
-

OPERAÇÃO

-

Dashboard

-
-
- - -
-
- -
- - {period === "specific" && ( - - )} - - - - O período se aplica às vendas e ao ticket médio. A operação mostra - a situação atual. - - -
- -
- {indicators.map(({ label, value, detail }) => ( - - ))} -
- -
-
-
-

Fila da cozinha

- Ao vivo -
-
- {orders.map((order) => ( -
-
-

Pedido #{order.id}

-

{order.description}

-
- - {order.status} - -
- ))} -
-
- -
-
-

Mapa rápido de mesas

- -
-
- {tables.map((status, index) => ( -
- {index + 1} -
- ))} -
-
-
- -
-
-
-

Vendas por período

- -
-
`${label}: ${currency(value)}`).join("; ")}.`} - > - {salesDays.map(({ label, value }) => ( - -
- -
-
-

Produtos mais vendidos

-
-
    - {topProducts.map(({ name, quantity }, index) => ( -
  1. - - {name} - {quantity} un. -
  2. - ))} -
-
- -
-
-

Resumo operacional

-
-
- {operationalSummary.map(({ label, value }) => ( -
-
{label}
-
{value}
-
- ))} -
-
-
-
-
- ); -} diff --git a/frontend/app/src/pages/admin/dashboard/styles.css b/frontend/app/src/pages/admin/dashboard/styles.css deleted file mode 100644 index ae69f58..0000000 --- a/frontend/app/src/pages/admin/dashboard/styles.css +++ /dev/null @@ -1,507 +0,0 @@ -.mf-dashboard { - display: grid; - grid-template-columns: 240px minmax(0, 1fr); - min-height: 100vh; - background: var(--background); - color: var(--text); - font-family: var(--font-body); -} - -.mf-dashboard > .admin-sidebar { - position: sticky; - top: 0; - height: 100vh; - border-block: 0; - border-left: 0; - border-radius: 0; -} - -.mf-dashboard-main { - min-width: 0; - padding: clamp(24px, 4vw, 48px); -} - -.mf-dashboard-header { - display: flex; - align-items: flex-end; - justify-content: space-between; - gap: 24px; - margin-bottom: 36px; -} - -.mf-dashboard-eyebrow { - margin: 0 0 6px; - color: var(--primary); - font-size: 14px; - font-weight: 800; -} - -.mf-dashboard-header h1 { - color: var(--primary-dark); - font-size: clamp(28px, 3vw, 40px); - font-weight: 800; -} - -.mf-dashboard-tools { - display: flex; - align-items: center; - gap: 12px; -} - -.mf-dashboard-search { - display: flex; - align-items: center; - gap: 10px; - width: min(340px, 31vw); - min-height: 44px; - padding: 0 12px; - border: 1px solid var(--border); - border-radius: var(--radius-control); - background: var(--surface); - color: var(--text-secondary); -} - -.mf-dashboard-search:focus-within { - border-color: var(--primary); - box-shadow: 0 0 0 3px rgb(201 76 59 / 14%); -} - -.mf-dashboard-search svg { - width: 18px; - height: 18px; - flex: 0 0 18px; -} - -.mf-dashboard-search input { - min-width: 0; - width: 100%; - border: 0; - outline: 0; - background: transparent; - color: var(--text); -} - -.mf-dashboard-search input::placeholder { - color: var(--text-secondary); -} -.mf-dashboard-notifications { - flex: 0 0 44px; -} -.mf-dashboard-notifications > span { - position: absolute; - top: 9px; - right: 10px; - width: 6px; - height: 6px; - border-radius: var(--radius-full); - background: var(--accent); -} - -.mf-dashboard-indicators { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 16px; - margin-bottom: 24px; -} - -.mf-dashboard-indicators .stat-card { - min-width: 0; - transition: border-color 150ms ease; -} - -.mf-dashboard-indicators .stat-card > strong { - font-variant-numeric: tabular-nums; -} -.mf-dashboard-indicators .stat-card:hover, -.mf-dashboard-order:hover { - border-color: #cfc1b6; -} - -.mf-dashboard-content { - display: grid; - grid-template-columns: minmax(0, 65fr) minmax(0, 35fr); - align-items: start; - gap: 24px; -} - -.mf-dashboard-panel { - min-width: 0; - overflow: hidden; - border: 1px solid var(--border); - border-radius: var(--radius-control); - background: var(--surface); - box-shadow: var(--shadow-soft); -} - -.mf-dashboard-panel-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - min-height: 77px; - padding: 20px 24px; - border-bottom: 1px solid var(--border); -} - -.mf-dashboard-panel-heading h2 { - font-size: 18px; - line-height: 1.3; -} -.mf-dashboard-panel .badge { - flex-shrink: 0; - white-space: nowrap; -} - -.mf-dashboard-orders { - display: grid; - gap: 12px; - padding: 20px 24px; -} -.mf-dashboard-order { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 16px; - padding: 16px; - border: 1px solid var(--border); - border-radius: var(--radius-card); - transition: border-color 150ms ease; -} - -.mf-dashboard-order h3 { - margin-bottom: 6px; - font-size: 16px; - line-height: 1.4; -} -.mf-dashboard-order p { - max-width: 75ch; - color: var(--text-secondary); - font-size: 14px; - line-height: 1.5; -} -.mf-dashboard-view-tables.btn { - color: var(--primary); -} - -.mf-dashboard-table-grid { - display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); - gap: 10px; - padding: 20px 24px; -} - -.mf-dashboard-table { - display: grid; - place-items: center; - min-height: 44px; - border-radius: var(--radius-card); - font-size: 14px; - font-weight: 700; - transition: filter 150ms ease; -} - -.mf-dashboard-table:hover { - filter: brightness(0.95); -} -.mf-dashboard-table.available { - background: rgb(63 143 91 / 14%); - color: #2c6e43; -} -.mf-dashboard-table.occupied { - background: rgb(58 111 143 / 14%); - color: var(--info); -} -.mf-dashboard-table.payment { - background: rgb(217 144 32 / 14%); - color: #8c5713; -} - -@media (max-width: 1100px) { - .mf-dashboard-header { - align-items: flex-start; - flex-direction: column; - } - .mf-dashboard-search { - width: min(340px, 100%); - } - .mf-dashboard-content { - grid-template-columns: minmax(0, 1fr) minmax(250px, 0.75fr); - gap: 16px; - } - .mf-dashboard-panel-heading, - .mf-dashboard-orders, - .mf-dashboard-table-grid { - padding: 16px; - } - .mf-dashboard-order { - flex-wrap: wrap; - gap: 10px; - } -} - -@media (max-width: 900px) { - .mf-dashboard-indicators { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - .mf-dashboard-content { - grid-template-columns: minmax(0, 1fr); - } -} - -@media (max-width: 720px) { - .mf-dashboard { - grid-template-columns: minmax(0, 1fr); - } - .mf-dashboard > .admin-sidebar { - position: static; - height: auto; - border-right: 0; - } - .mf-dashboard-main { - padding: 24px 16px; - } - .mf-dashboard-tools { - width: 100%; - } - .mf-dashboard-search { - flex: 1; - width: auto; - } -} - -.mf-dashboard-filters { - display: flex; - flex-wrap: wrap; - align-items: flex-end; - gap: 12px; - margin-bottom: 24px; -} - -.mf-dashboard-period-filter, -.mf-dashboard-specific-date { - display: grid; - gap: 4px; - color: #756d69; - font-size: 11px; - font-weight: 700; -} - -.mf-dashboard-period-filter select, -.mf-dashboard-specific-date input { - min-height: 44px; - padding: 0 34px 0 12px; - border: 1px solid #e9dfd7; - border-radius: 10px; - outline: 0; - background: var(--surface); - color: #302a28; - font-size: 13px; - font-weight: 600; -} - -.mf-dashboard-specific-date input { - min-width: 154px; - padding-right: 10px; -} -.mf-dashboard-period-filter select:focus, -.mf-dashboard-specific-date input:focus { - border-color: #dc8a7c; - box-shadow: 0 0 0 3px rgb(201 76 59 / 10%); -} - -.mf-dashboard-period-help { - position: relative; - display: inline-flex; - align-items: center; - height: 44px; -} - -.mf-dashboard-period-help button { - display: grid; - place-items: center; - width: 28px; - height: 28px; - padding: 0; - border: 0; - border-radius: var(--radius-card); - background: transparent; - color: var(--text-secondary); - cursor: help; -} - -.mf-dashboard-period-help button:hover, -.mf-dashboard-period-help button:focus-visible { - color: var(--primary); -} -.mf-dashboard-period-help svg { - width: 16px; - height: 16px; -} - -.mf-dashboard-period-help [role="tooltip"] { - position: absolute; - z-index: 2; - bottom: 100%; - right: 0; - width: 220px; - padding: 10px 12px; - border-radius: var(--radius-card); - background: var(--text); - color: var(--surface); - font-size: 12px; - line-height: 1.5; - visibility: hidden; - opacity: 0; - transition: opacity 150ms ease; -} - -.mf-dashboard-period-help:hover [role="tooltip"], -.mf-dashboard-period-help:focus-within [role="tooltip"] { - visibility: visible; - opacity: 1; -} - -@media (max-width: 720px) { - .mf-dashboard-period-filter, - .mf-dashboard-specific-date { - flex: 1 1 180px; - } - .mf-dashboard-period-filter select, - .mf-dashboard-specific-date input { - width: 100%; - min-width: 0; - } -} - -.mf-dashboard-analytics-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 24px; - margin-top: 24px; -} - -.mf-dashboard-analytics-card { - display: flex; - flex-direction: column; -} - -.mf-dashboard-analytics-heading { - flex-wrap: wrap; -} - -.mf-dashboard-analytics-period { - margin-left: auto; - color: var(--text-secondary); - font-size: 12px; - white-space: nowrap; -} - -.mf-dashboard-analytics-ranking, -.mf-dashboard-analytics-summary { - margin: 0; - padding: 0 24px; - list-style: none; -} - -.mf-dashboard-analytics-row { - display: flex; - align-items: center; - gap: 12px; - min-height: 64px; - padding: 16px 0; - font-size: 14px; -} - -.mf-dashboard-analytics-row + .mf-dashboard-analytics-row { - border-top: 1px solid var(--border); -} - -.mf-dashboard-analytics-rank { - display: grid; - place-items: center; - flex: 0 0 28px; - height: 28px; - border-radius: var(--radius-card); - background: rgb(201 76 59 / 10%); - color: var(--primary-dark); - font-weight: 700; -} - -.mf-dashboard-analytics-product, -.mf-dashboard-analytics-row dt { - min-width: 0; - overflow-wrap: anywhere; -} - -.mf-dashboard-analytics-row dt { - color: var(--text-secondary); -} - -.mf-dashboard-analytics-row strong, -.mf-dashboard-analytics-row dd { - flex-shrink: 0; - margin: 0 0 0 auto; - color: var(--primary-dark); - font-weight: 700; - font-variant-numeric: tabular-nums; - white-space: nowrap; -} - -.mf-dashboard-analytics-chart { - display: grid; - overflow-x: auto; - align-items: end; - gap: 10px; - flex: 1; - min-height: 256px; - padding: 24px; -} - -.mf-dashboard-analytics-chart-column { - display: flex; - flex-direction: column; - gap: 12px; - height: 100%; - text-align: center; - color: var(--text-secondary); - font-size: 11px; - font-variant-numeric: tabular-nums; -} - -.mf-dashboard-analytics-bar-track { - display: flex; - align-items: flex-end; - flex: 1; - height: 160px; - min-height: 160px; -} - -.mf-dashboard-analytics-bar { - width: 100%; - background: var(--primary); - border-radius: var(--radius-small) var(--radius-small) 0 0; -} - -@media (max-width: 1200px) { - .mf-dashboard-analytics-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 16px; - } -} - -@media (max-width: 1100px) { - .mf-dashboard-analytics-ranking, - .mf-dashboard-analytics-summary { - padding-inline: 16px; - } - - .mf-dashboard-analytics-chart { - padding: 16px; - } -} - -@media (max-width: 900px) { - .mf-dashboard-analytics-grid { - grid-template-columns: minmax(0, 1fr); - } -} diff --git a/frontend/app/src/pages/admin/order/OrderEditPanel.tsx b/frontend/app/src/pages/admin/order/OrderEditPanel.tsx deleted file mode 100644 index 93fcb4d..0000000 --- a/frontend/app/src/pages/admin/order/OrderEditPanel.tsx +++ /dev/null @@ -1,506 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from "react"; -import { - Check, - ChevronDown, - Minus, - Plus, - Search, - Trash2, - X, -} from "lucide-react"; -import { Button } from "@/components/buttons/Button"; -import { orderProductCatalog, orderTableOptions } from "./orderCatalog"; - -export type EditableOrderItem = { - name: string; - quantity: number; - unitPrice: number; - observation?: string; -}; - -type OrderEditPanelProps = { - orderId?: number; - mode?: "create" | "edit"; - table: string; - time: string; - items: EditableOrderItem[]; - onCancel: () => void; - onSave: (changes: { - table: string; - time: string; - items: EditableOrderItem[]; - }) => void; -}; - -type DraftItem = EditableOrderItem & { draftId: number }; - -const formatCurrency = (value: number) => - value.toLocaleString("pt-BR", { style: "currency", currency: "BRL" }); - -export function OrderEditPanel({ - orderId, - mode = "edit", - table: initialTable, - time: initialTime, - items: initialItems, - onCancel, - onSave, -}: OrderEditPanelProps) { - const modalRef = useRef(null); - const closeButtonRef = useRef(null); - const previousFocusRef = useRef(null); - const openProductIdRef = useRef(null); - const [table, setTable] = useState(initialTable); - const [time, setTime] = useState(initialTime); - const [items, setItems] = useState(() => - initialItems.map((item, index) => ({ ...item, draftId: index + 1 })), - ); - const [openProductId, setOpenProductId] = useState(null); - const [productSearch, setProductSearch] = useState(""); - - useEffect(() => { - openProductIdRef.current = openProductId; - }, [openProductId]); - - const filteredProducts = useMemo(() => { - const normalizedSearch = productSearch - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") - .toLocaleLowerCase("pt-BR") - .trim(); - - return orderProductCatalog.filter((product) => - product.name - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") - .toLocaleLowerCase("pt-BR") - .includes(normalizedSearch), - ); - }, [productSearch]); - - const total = useMemo( - () => - items.reduce( - (sum, item) => sum + item.quantity * Math.max(0, item.unitPrice), - 0, - ), - [items], - ); - const isValid = - table.trim().length > 0 && - time.length > 0 && - items.length > 0 && - items.every( - (item) => - item.name.trim().length > 0 && - item.quantity >= 1 && - item.unitPrice > 0, - ); - - useEffect(() => { - const previousOverflow = document.body.style.overflow; - previousFocusRef.current = document.activeElement as HTMLElement | null; - document.body.style.overflow = "hidden"; - closeButtonRef.current?.focus(); - - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === "Escape") { - if (openProductIdRef.current !== null) { - setOpenProductId(null); - setProductSearch(""); - } else { - onCancel(); - } - return; - } - if (event.key !== "Tab" || !modalRef.current) return; - - const focusableElements = Array.from( - modalRef.current.querySelectorAll( - 'button:not(:disabled), input:not(:disabled), select:not(:disabled), [tabindex]:not([tabindex="-1"])', - ), - ); - const firstElement = focusableElements[0]; - const lastElement = focusableElements.at(-1); - - if (event.shiftKey && document.activeElement === firstElement) { - event.preventDefault(); - lastElement?.focus(); - } else if (!event.shiftKey && document.activeElement === lastElement) { - event.preventDefault(); - firstElement?.focus(); - } - }; - - document.addEventListener("keydown", handleKeyDown); - return () => { - document.body.style.overflow = previousOverflow; - document.removeEventListener("keydown", handleKeyDown); - previousFocusRef.current?.focus(); - }; - }, [onCancel]); - - const updateItem = (draftId: number, changes: Partial) => { - setItems((current) => - current.map((item) => - item.draftId === draftId ? { ...item, ...changes } : item, - ), - ); - }; - - const addItem = () => { - setItems((current) => [ - ...current, - { - draftId: - current.length > 0 - ? Math.max(...current.map((item) => item.draftId)) + 1 - : 1, - name: "", - quantity: 1, - unitPrice: 0, - observation: "", - }, - ]); - }; - - const fillSimulatedOrder = () => { - const now = new Date().toLocaleTimeString("pt-BR", { - hour: "2-digit", - minute: "2-digit", - }); - setTable("Mesa 06"); - setTime(now); - setItems([ - { - draftId: 1, - name: "X-Burger artesanal", - quantity: 2, - unitPrice: 28.9, - observation: "Molho à parte", - }, - { - draftId: 2, - name: "Batata rústica", - quantity: 1, - unitPrice: 18, - observation: "", - }, - { - draftId: 3, - name: "Suco de laranja", - quantity: 1, - unitPrice: 12, - observation: "Sem gelo", - }, - ]); - setOpenProductId(null); - setProductSearch(""); - }; - - const saveChanges = () => { - if (!isValid) return; - onSave({ - table: table.trim(), - time, - items: items.map(({ draftId: _draftId, ...item }) => ({ - ...item, - name: item.name.trim(), - observation: item.observation?.trim() || undefined, - })), - }); - }; - - return ( -
{ - if (event.target === event.currentTarget) onCancel(); - }} - > -
-
-
- {mode === "create" ? "NOVO PEDIDO" : "EDIÇÃO DO PEDIDO"} -

- {mode === "create" ? "Criar pedido" : `Editar pedido #${orderId}`} -

-

- {mode === "create" - ? "Escolha a mesa e adicione os produtos do novo pedido." - : "Atualize a mesa, os produtos e as observações do pedido."} -

-
- -
- -
-
- {mode === "create" && ( -
-
- Precisa testar rapidamente? - Preencha um pedido completo com dados de exemplo. -
- -
- )} - -
- - -
- -
-
-

Itens do pedido

-

Busque um produto pelo nome para alterar ou incluir.

-
- -
- -
- {items.map((item, index) => ( -
-
- Item {index + 1} - -
- -
- Produto -
- - - {openProductId === item.draftId && ( -
- -
- {filteredProducts.map((product) => ( - - ))} - {filteredProducts.length === 0 && ( -

Nenhum produto encontrado.

- )} -
-
- )} -
-
- -
-
- Quantidade -
- - - updateItem(item.draftId, { - quantity: Math.max( - 1, - Number(event.target.value) || 1, - ), - }) - } - /> - -
-
- - -
- - -
- ))} - - {items.length === 0 && ( -
-

O pedido precisa ter pelo menos um item.

- -
- )} -
-
-
- -
-
- Novo total - {formatCurrency(total)} -
-
- - -
-
-
-
- ); -} diff --git a/frontend/app/src/pages/admin/order/index.tsx b/frontend/app/src/pages/admin/order/index.tsx deleted file mode 100644 index d87e1b1..0000000 --- a/frontend/app/src/pages/admin/order/index.tsx +++ /dev/null @@ -1,716 +0,0 @@ -import { useMemo, useState } from "react"; -import { CheckCircle2, ClipboardList, Pencil, Plus, RotateCcw, Search } from "lucide-react"; -import { Button } from "@/components/buttons/Button"; -import { AdminSidebar } from "@/components/navigation/AdminSidebar"; -import { - filterOrders, - filterOrdersByDate, - getNextStatus, - getPreviousStatus, - type OrderFilter, - type OrderPeriod, - type OrderStatus, - type OrderSummary, -} from "./orderLogic.js"; -import { - PaymentModal, - type CompletedPayment, -} from "./PaymentModal"; -import { OrderEditPanel } from "./OrderEditPanel"; -import { Notifications } from "../../../components/notifications/index.js"; -import "./styles.css"; - -type OrderItem = { - name: string; - quantity: number; - unitPrice: number; - observation?: string; -}; - -type Order = OrderSummary & { - date: string; - time: string; - items: OrderItem[]; - unread?: boolean; - payment?: { - entries: CompletedPayment[]; - closedAt: string; - }; -}; - -type OrdersProps = { - onNavigate?: (id: string) => void; -}; - -const paymentMethodLabels = { - pix: "Pix", - cash: "Dinheiro", - debit: "Débito", - credit: "Crédito", -} as const; - -const statusDetails: Record< - OrderStatus, - { label: string; nextAction: string } -> = { - waiting: { label: "Aguardando", nextAction: "Iniciar preparo" }, - preparing: { label: "Em preparo", nextAction: "Marcar como pronto" }, - ready: { label: "Pronto", nextAction: "Pedido pronto" }, - delivered: { label: "Entregue", nextAction: "Pedido entregue" }, - closed: { label: "Fechado", nextAction: "Pedido fechado" }, -}; - -const orderFilters: { value: OrderFilter; label: string }[] = [ - { value: "all", label: "Todos" }, - { value: "waiting", label: "Aguardando" }, - { value: "preparing", label: "Em preparo" }, - { value: "ready", label: "Pronto" }, - { value: "delivered", label: "Entregue" }, - { value: "closed", label: "Fechado" }, -]; - -const toDateKey = (date: Date) => - [ - date.getFullYear(), - String(date.getMonth() + 1).padStart(2, "0"), - String(date.getDate()).padStart(2, "0"), - ].join("-"); - -const dateDaysAgo = (days: number) => { - const date = new Date(); - date.setDate(date.getDate() - days); - return toDateKey(date); -}; - -const initialOrders: Order[] = [ - { - id: 1842, - date: dateDaysAgo(0), - table: "Mesa 08", - time: "12:48", - status: "waiting", - unread: true, - items: [ - { name: "X-Burger artesanal", quantity: 2, unitPrice: 28.9 }, - { - name: "Batata rústica", - quantity: 1, - unitPrice: 18, - observation: "Sem páprica", - }, - { name: "Refrigerante lata", quantity: 2, unitPrice: 7 }, - ], - }, - { - id: 1841, - date: dateDaysAgo(1), - table: "Balcão", - time: "12:41", - status: "preparing", - items: [ - { name: "Pizza margherita", quantity: 1, unitPrice: 44.9 }, - { name: "Suco de laranja", quantity: 1, unitPrice: 12 }, - ], - }, - { - id: 1840, - date: dateDaysAgo(4), - table: "Mesa 03", - time: "12:34", - status: "ready", - items: [ - { name: "X-Salada", quantity: 2, unitPrice: 31.9 }, - { name: "Água com gás", quantity: 2, unitPrice: 6 }, - ], - }, - { - id: 1839, - date: dateDaysAgo(12), - table: "Mesa 11", - time: "12:20", - status: "delivered", - items: [ - { name: "Pizza calabresa", quantity: 1, unitPrice: 46.9 }, - { name: "Refrigerante 1L", quantity: 1, unitPrice: 13 }, - { name: "Pudim da casa", quantity: 1, unitPrice: 14 }, - ], - }, -]; - -const formatCurrency = (value: number) => - value.toLocaleString("pt-BR", { style: "currency", currency: "BRL" }); - -const getItemCount = (order: Order) => - order.items.reduce((total, item) => total + item.quantity, 0); - -const getOrderTotal = (order: Order) => - order.items.reduce( - (total, item) => total + item.quantity * item.unitPrice, - 0, - ); - -const emptyOrderItems: OrderItem[] = [ - { name: "", quantity: 1, unitPrice: 0, observation: "" }, -]; - -const getCurrentTime = () => - new Date().toLocaleTimeString("pt-BR", { - hour: "2-digit", - minute: "2-digit", - }); - -export default function Orders({ onNavigate }: OrdersProps) { - const [orders, setOrders] = useState(initialOrders); - const [searchQuery, setSearchQuery] = useState(""); - const [activeFilter, setActiveFilter] = useState("all"); - const [periodFilter, setPeriodFilter] = useState("today"); - const [specificDate, setSpecificDate] = useState(toDateKey(new Date())); - const [selectedOrderId, setSelectedOrderId] = useState( - initialOrders[0].id, - ); - const [activityMessage, setActivityMessage] = useState(""); - const [paymentOrderId, setPaymentOrderId] = useState(null); - const [editingOrderId, setEditingOrderId] = useState(null); - const [isCreatingOrder, setIsCreatingOrder] = useState(false); - - const periodOrders = useMemo( - () => - filterOrdersByDate( - orders, - periodFilter, - specificDate, - toDateKey(new Date()), - ), - [orders, periodFilter, specificDate], - ); - const filteredOrders = useMemo( - () => filterOrders(periodOrders, activeFilter, searchQuery), - [activeFilter, periodOrders, searchQuery], - ); - const selectedOrder = - orders.find((order) => order.id === selectedOrderId) ?? null; - - const selectOrder = (orderId: number) => { - setSelectedOrderId(orderId); - setEditingOrderId(null); - setOrders((currentOrders) => - currentOrders.map((order) => - order.id === orderId ? { ...order, unread: false } : order, - ), - ); - }; - - const updateOrderStatus = ( - orderId: number, - direction: "next" | "previous", - ) => { - const currentOrder = orders.find((order) => order.id === orderId); - if (!currentOrder) return; - if ( - direction === "next" && - (currentOrder.status === "delivered" || currentOrder.status === "closed") - ) - return; - if (direction === "previous" && currentOrder.status === "waiting") return; - if (currentOrder.status === "closed") return; - - const nextStatus = - direction === "next" - ? getNextStatus(currentOrder.status) - : getPreviousStatus(currentOrder.status); - setOrders((currentOrders) => - currentOrders.map((order) => - order.id === orderId ? { ...order, status: nextStatus } : order, - ), - ); - setSelectedOrderId(orderId); - setActivityMessage( - `Pedido #${orderId} atualizado para ${statusDetails[nextStatus].label}.`, - ); - }; - - const createOrder = (changes: Pick) => { - const nextId = Math.max(0, ...orders.map((order) => order.id)) + 1; - const now = new Date(); - const newOrder: Order = { - id: nextId, - date: toDateKey(now), - ...changes, - status: "waiting", - unread: false, - }; - - setOrders((currentOrders) => [newOrder, ...currentOrders]); - setSelectedOrderId(nextId); - setActiveFilter("all"); - setPeriodFilter("today"); - setSearchQuery(""); - setIsCreatingOrder(false); - setActivityMessage(`Pedido #${nextId} criado e enviado para a cozinha.`); - }; - - const cancelOrder = () => { - if (!selectedOrder) return; - const confirmed = window.confirm( - `Cancelar o pedido #${selectedOrder.id}? Esta ação não poderá ser desfeita.`, - ); - if (!confirmed) return; - - const remainingOrders = orders.filter( - (order) => order.id !== selectedOrder.id, - ); - setOrders(remainingOrders); - setSelectedOrderId(remainingOrders[0]?.id ?? null); - setActivityMessage(`Pedido #${selectedOrder.id} cancelado.`); - }; - - const completeOrder = (orderId: number, entries: CompletedPayment[]) => { - const closedAt = new Date().toLocaleTimeString("pt-BR", { - hour: "2-digit", - minute: "2-digit", - }); - setOrders((currentOrders) => - currentOrders.map((order) => - order.id === orderId - ? { ...order, status: "closed", payment: { entries, closedAt } } - : order, - ), - ); - setSelectedOrderId(orderId); - setPaymentOrderId(null); - setActivityMessage(`Pedido #${orderId} fechado com sucesso.`); - }; - - const reopenOrder = (orderId: number) => { - setOrders((currentOrders) => - currentOrders.map((order) => - order.id === orderId - ? { ...order, status: "delivered", payment: undefined } - : order, - ), - ); - setSelectedOrderId(orderId); - setEditingOrderId(null); - setActivityMessage(`Pedido #${orderId} reaberto e marcado como entregue.`); - }; - - const saveOrderChanges = ( - orderId: number, - changes: Pick, - ) => { - setOrders((currentOrders) => - currentOrders.map((order) => - order.id === orderId ? { ...order, ...changes } : order, - ), - ); - setEditingOrderId(null); - setActivityMessage(`Alterações do pedido #${orderId} salvas.`); - }; - - const paymentOrder = - orders.find((order) => order.id === paymentOrderId) ?? null; - const editingOrder = - orders.find((order) => order.id === editingOrderId) ?? null; - - return ( -
- onNavigate?.(id)} - userName="Nome do usuário" - userRole="Administrador" - /> - -
-
-
- Cozinha e salão -

PEDIDOS

-

Acompanhe cada pedido do recebimento até ficar pronto.

-
- -
- - - -
-
- -
-
-
- {orderFilters.map((filter) => { - const count = - filter.value === "all" - ? periodOrders.length - : periodOrders.filter( - (order) => order.status === filter.value, - ).length; - return ( - - ); - })} -
- - - - {periodFilter === "specific" && ( - - )} -
- - -
- -

- {activityMessage} -

- -
-
-
-
-

Pedidos recentes

-

{filteredOrders.length} pedidos nesta visualização

-
-
- -
- {filteredOrders.map((order) => { - const isSelected = order.id === selectedOrderId; - const canAdvance = - order.status !== "delivered" && order.status !== "closed"; - const canRegress = - order.status !== "waiting" && order.status !== "closed"; - return ( -
selectOrder(order.id)} - > -
-

Pedido #{order.id}

- - {statusDetails[order.status].label} - -
- -

- {order.table} - {order.time} - {getItemCount(order)} itens -

- - - {formatCurrency(getOrderTotal(order))} - - -
- - {order.status === "closed" ? ( - - ) : ( - <> - - {order.status === "delivered" ? ( - - ) : ( - - )} - - )} -
-
- ); - })} - - {filteredOrders.length === 0 && ( -
-

Nenhum pedido encontrado

-

Ajuste a busca ou selecione outro status.

-
- )} -
-
- - -
-
- - {paymentOrder && ( - setPaymentOrderId(null)} - onComplete={(payments) => completeOrder(paymentOrder.id, payments)} - /> - )} - - {editingOrder && ( - setEditingOrderId(null)} - onSave={(changes) => saveOrderChanges(editingOrder.id, changes)} - /> - )} - - {isCreatingOrder && ( - setIsCreatingOrder(false)} - onSave={createOrder} - /> - )} -
- ); -} diff --git a/frontend/app/src/pages/admin/order/orderLogic.ts b/frontend/app/src/pages/admin/order/orderLogic.ts deleted file mode 100644 index 60cb2e8..0000000 --- a/frontend/app/src/pages/admin/order/orderLogic.ts +++ /dev/null @@ -1,75 +0,0 @@ -export type OrderStatus = - | "waiting" - | "preparing" - | "ready" - | "delivered" - | "closed"; -export type OrderFilter = "all" | OrderStatus; -export type OrderPeriod = "today" | "7days" | "30days" | "all" | "specific"; - -export type OrderSummary = { - id: number; - table: string; - status: OrderStatus; -}; - -type DatedOrderSummary = OrderSummary & { date: string }; - -export const orderStatuses: OrderStatus[] = [ - "waiting", - "preparing", - "ready", - "delivered", - "closed", -]; - -const normalizeSearchText = (value: string) => - value - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") - .toLocaleLowerCase("pt-BR"); - -export function getNextStatus(status: OrderStatus): OrderStatus { - const currentIndex = orderStatuses.indexOf(status); - return orderStatuses[Math.min(currentIndex + 1, orderStatuses.length - 1)]; -} - -export function getPreviousStatus(status: OrderStatus): OrderStatus { - const currentIndex = orderStatuses.indexOf(status); - return orderStatuses[Math.max(currentIndex - 1, 0)]; -} - -export function filterOrders( - orders: T[], - status: OrderFilter, - searchQuery: string, -): T[] { - const normalizedSearch = normalizeSearchText(searchQuery.trim()); - - return orders.filter((order) => { - const matchesStatus = status === "all" || order.status === status; - const searchableContent = normalizeSearchText(`${order.id} ${order.table}`); - - return matchesStatus && searchableContent.includes(normalizedSearch); - }); -} - -export function filterOrdersByDate( - orders: T[], - period: OrderPeriod, - specificDate: string, - todayDate: string, -): T[] { - if (period === "all") return orders; - if (period === "specific") { - return orders.filter((order) => order.date === specificDate); - } - const todayTime = Date.parse(`${todayDate}T00:00:00Z`); - const maximumAge = period === "today" ? 0 : period === "7days" ? 6 : 29; - - return orders.filter((order) => { - const orderTime = Date.parse(`${order.date}T00:00:00Z`); - const ageInDays = (todayTime - orderTime) / 86_400_000; - return ageInDays >= 0 && ageInDays <= maximumAge; - }); -} diff --git a/frontend/app/src/pages/admin/order/styles.css b/frontend/app/src/pages/admin/order/styles.css deleted file mode 100644 index d300c28..0000000 --- a/frontend/app/src/pages/admin/order/styles.css +++ /dev/null @@ -1,1849 +0,0 @@ -.orders-page { - display: grid; - grid-template-columns: 240px minmax(0, 1fr); - min-height: 100vh; - background: #fff9f2; -} - -.orders-page > .admin-sidebar { - position: sticky; - top: 0; - height: 100vh; - border-block: 0; - border-left: 0; - border-radius: 0; -} - -.orders-content { - width: 100%; - min-width: 0; - padding: clamp(24px, 3vw, 44px); - font-family: Inter, var(--font-body); -} - -.orders-topbar { - display: flex; - gap: 24px; - align-items: flex-end; - justify-content: space-between; -} - -.orders-heading > span { - display: block; - margin-bottom: 5px; - color: var(--primary); - font-size: 13px; - font-weight: 700; -} - -.orders-heading h1 { - color: #282322; - font-size: clamp(28px, 3vw, 38px); - font-weight: 800; - letter-spacing: -0.02em; -} - -.orders-heading p { - margin-top: 8px; - color: #7c7470; - font-size: 14px; -} - -.orders-topbar-actions { - display: flex; - gap: 10px; - align-items: center; -} - -.order-search { - display: flex; - width: min(300px, 34vw); - min-height: 44px; - gap: 10px; - align-items: center; - padding: 0 13px; - border: 1px solid #e9dfd7; - border-radius: 10px; - background: #fff; - color: #817874; -} - -.order-search:focus-within { - border-color: #dc8a7c; - box-shadow: 0 0 0 3px rgb(201 76 59 / 10%); -} - -.order-search svg { - width: 18px; - height: 18px; - flex: 0 0 18px; -} - -.order-search input { - width: 100%; - min-width: 0; - border: 0; - outline: 0; - background: transparent; - color: #282322; -} - -.order-search input::placeholder { - color: #958d89; -} - -.order-notifications { - position: relative; -} - -.order-notification-button { - position: relative; - display: grid; - width: 44px; - height: 44px; - place-items: center; - border: 1px solid #e9dfd7; - border-radius: 10px; - background: #fff; - color: var(--primary-dark); -} - -.order-notification-button:hover, -.order-notification-button[aria-expanded="true"] { - background: #fff2ed; -} - -.order-notification-button > span { - position: absolute; - top: -6px; - right: -6px; - display: grid; - min-width: 20px; - height: 20px; - padding: 0 5px; - place-items: center; - border: 2px solid #fff9f2; - border-radius: 999px; - background: var(--primary); - color: #fff; - font-size: 11px; - font-weight: 800; -} - -.order-notification-panel { - position: absolute; - z-index: 12; - top: calc(100% + 10px); - right: 0; - width: min(340px, calc(100vw - 32px)); - overflow: hidden; - border: 1px solid #ece3dc; - border-radius: 12px; - background: #fff; - box-shadow: 0 16px 35px rgb(67 49 43 / 12%); -} - -.order-notification-panel > div { - display: flex; - gap: 12px; - align-items: center; - justify-content: space-between; - padding: 16px; - border-bottom: 1px solid #eee6e0; -} - -.order-notification-panel > div > button { - padding: 0; - border: 0; - background: transparent; - color: var(--primary-dark); - font-size: 12px; - font-weight: 700; -} - -.order-notification-item { - display: grid; - width: 100%; - gap: 2px; - padding: 13px 16px; - border: 0; - border-bottom: 1px solid #eee6e0; - background: transparent; - color: #282322; - text-align: left; -} - -.order-notification-item:hover { - background: #fff8f4; -} - -.order-notification-item span { - font-weight: 700; -} - -.order-notification-item small, -.order-notification-panel > p { - color: #7c7470; - font-size: 12px; -} - -.order-notification-panel > p { - padding: 22px 16px; - text-align: center; -} - -.orders-toolbar { - display: flex; - gap: 18px; - align-items: center; - justify-content: space-between; - margin-top: 28px; -} - -.orders-toolbar-filters { - display: flex; - min-width: 0; - gap: 10px; - align-items: flex-end; - flex-wrap: wrap; -} - -.order-filter-tabs { - display: flex; - overflow-x: auto; - gap: 4px; - padding: 4px; - border: 1px solid #e9dfd7; - border-radius: 11px; - background: rgb(255 255 255 / 72%); -} - -.order-filter-tabs button { - display: inline-flex; - min-height: 36px; - gap: 7px; - align-items: center; - padding: 0 11px; - border: 0; - border-radius: 8px; - background: transparent; - color: #756d69; - font-size: 13px; - font-weight: 700; - white-space: nowrap; -} - -.order-filter-tabs button:hover { - color: var(--primary-dark); -} - -.order-filter-tabs button.active { - background: var(--primary-dark); - color: #fff; -} - -.order-filter-tabs button span { - font-size: 11px; - opacity: 0.78; -} - -.order-period-filter, -.order-specific-date { - display: grid; - gap: 4px; - color: #756d69; - font-size: 11px; - font-weight: 700; -} - -.order-period-filter select, -.order-specific-date input { - min-height: 44px; - padding: 0 34px 0 12px; - border: 1px solid #e9dfd7; - border-radius: 10px; - outline: 0; - background: #fff; - color: #302a28; - font-size: 13px; - font-weight: 600; -} - -.order-specific-date input { - min-width: 154px; - padding-right: 10px; -} - -.order-period-filter select:focus, -.order-specific-date input:focus { - border-color: #dc8a7c; - box-shadow: 0 0 0 3px rgb(201 76 59 / 10%); -} - -.orders-toolbar > .btn, -.order-card-actions .btn, -.order-details-actions .btn { - border-radius: 10px; -} - -.orders-toolbar > .order-create-button { - width: 44px; - height: 44px; - flex: 0 0 44px; - padding: 0; - border-radius: 10px; -} - -.order-create-button svg { - width: 20px; - height: 20px; -} - -.order-activity-message { - min-height: 18px; - margin-top: 8px; - color: #34764a; - font-size: 12px; - font-weight: 700; - text-align: right; -} - -.orders-workspace { - display: grid; - grid-template-columns: minmax(0, 55fr) minmax(330px, 45fr); - gap: 18px; - align-items: start; - margin-top: 4px; -} - -.orders-column { - min-width: 0; -} - -.orders-list-heading { - padding: 0 2px 12px; -} - -.orders-list-heading h2 { - color: #302a28; - font-size: 17px; -} - -.orders-list-heading p { - margin-top: 3px; - color: #817874; - font-size: 12px; -} - -.orders-list-body { - display: grid; - gap: 10px; -} - -.order-card { - padding: 17px 18px; - border: 1px solid transparent; - border-radius: 13px; - background: #fff; - box-shadow: 0 3px 12px rgb(67 49 43 / 5%); - cursor: pointer; -} - -.order-card:hover { - border-color: #ead9d1; - box-shadow: 0 5px 16px rgb(67 49 43 / 7%); -} - -.order-card.is-selected { - border-color: #e5a297; - box-shadow: 0 4px 16px rgb(180 80 61 / 8%); -} - -.order-card-heading, -.order-details-heading { - display: flex; - gap: 16px; - align-items: center; - justify-content: space-between; -} - -.order-card-heading h3 { - color: #2b2624; - font-size: 16px; - font-weight: 800; -} - -.order-status-badge { - display: inline-flex; - min-height: 26px; - align-items: center; - padding: 0 10px; - border-radius: 999px; - font-size: 11px; - font-weight: 800; - white-space: nowrap; -} - -.order-status-badge[data-status="waiting"] { - background: #fff2d8; - color: #7c5012; -} - -.order-status-badge[data-status="preparing"] { - background: #e6f2f8; - color: #275d7d; -} - -.order-status-badge[data-status="ready"] { - background: #e5f3e9; - color: #28633d; -} - -.order-status-badge[data-status="delivered"] { - background: #efedeb; - color: #68615d; -} - -.order-status-badge[data-status="closed"] { - background: #dff3e6; - color: #23643b; -} - -.order-card-meta { - display: flex; - flex-wrap: wrap; - gap: 0; - margin-top: 8px; - color: #817874; - font-size: 12px; -} - -.order-card-meta span + span::before { - margin: 0 8px; - color: #c8bbb4; - content: "•"; -} - -.order-card-total { - display: block; - margin-top: 12px; - color: #302a28; - font-size: 15px; -} - -.order-card-actions { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 9px; - margin-top: 14px; -} - -.order-card-actions .btn.secondary, -.order-details-actions .btn.secondary { - border-color: #eadfd8; - color: var(--primary-dark); -} - -.order-card-actions .btn svg, -.order-details-actions .btn svg, -.payment-modal .btn svg { - width: 17px; - height: 17px; -} - -.order-card-actions .order-card-reopen { - grid-column: span 2; -} - -.order-details-panel { - position: sticky; - top: 24px; - min-width: 0; - padding: clamp(22px, 3vw, 32px); - border: 1px solid #eee5de; - border-radius: 14px; - background: #fff; - box-shadow: 0 5px 20px rgb(67 49 43 / 6%); -} - -.order-details-label { - display: block; - margin-bottom: 8px; - color: var(--primary); - font-size: 11px; - font-weight: 800; - letter-spacing: 0.08em; -} - -.order-details-heading h2 { - color: #282322; - font-size: clamp(22px, 2.4vw, 30px); - letter-spacing: -0.025em; -} - -.order-details-meta { - margin-top: 8px; - color: #817874; - font-size: 13px; -} - -.order-details-tabs { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 4px; - margin-top: 20px; - padding: 4px; - border: 1px solid #e9dfd7; - border-radius: 11px; - background: #faf7f4; -} - -.order-details-tabs button { - display: flex; - min-height: 40px; - gap: 7px; - align-items: center; - justify-content: center; - border: 0; - border-radius: 8px; - background: transparent; - color: #766d68; - font-size: 12px; - font-weight: 750; -} - -.order-details-tabs button:hover:not(:disabled) { - color: var(--primary-dark); -} - -.order-details-tabs button.active { - background: #fff; - box-shadow: 0 2px 7px rgb(67 49 43 / 8%); - color: var(--primary-dark); -} - -.order-details-tabs button:disabled { - opacity: 0.42; -} - -.order-details-tabs svg { - width: 16px; - height: 16px; -} - -.order-details-items { - margin-top: 18px; - border-top: 1px solid #eee6e0; -} - -.order-edit-panel { - margin-top: 18px; -} - -.order-edit-modal-backdrop { - position: fixed; - z-index: 100; - inset: 0; - display: grid; - overflow-y: auto; - place-items: center; - padding: 24px; - background: rgb(37 28 25 / 58%); - backdrop-filter: blur(3px); -} - -.order-edit-modal { - display: grid; - grid-template-rows: auto minmax(0, 1fr) auto; - width: min(920px, 100%); - max-height: calc(100vh - 48px); - overflow: hidden; - border: 1px solid rgb(255 255 255 / 50%); - border-radius: 20px; - background: #fffdfb; - box-shadow: 0 30px 80px rgb(37 28 25 / 26%); -} - -.order-edit-modal-header { - display: flex; - gap: 24px; - align-items: flex-start; - justify-content: space-between; - padding: 25px 28px 22px; - border-bottom: 1px solid #eee4dc; -} - -.order-edit-modal-header > div > span { - display: block; - margin-bottom: 6px; - color: var(--primary); - font-size: 10px; - font-weight: 800; - letter-spacing: 0.1em; -} - -.order-edit-modal-header h2 { - color: #282322; - font-size: clamp(22px, 3vw, 30px); - letter-spacing: -0.025em; -} - -.order-edit-modal-header p { - margin-top: 7px; - color: #7c7470; - font-size: 13px; -} - -.order-edit-modal-close { - display: grid; - width: 44px; - height: 44px; - flex: 0 0 44px; - place-items: center; - border: 1px solid #eadfd8; - border-radius: 11px; - background: #fff; - color: #655d59; -} - -.order-edit-modal-close:hover { - border-color: #dfc6bc; - background: #fff5f0; - color: var(--primary-dark); -} - -.order-edit-modal-body { - min-height: 0; - overflow-y: auto; - padding: 24px 28px 28px; -} - -.order-edit-modal .order-edit-panel { - margin-top: 0; -} - -.order-simulation-bar { - display: flex; - gap: 18px; - align-items: center; - justify-content: space-between; - margin-bottom: 20px; - padding: 14px 16px; - border: 1px solid #ead9c9; - border-radius: 12px; - background: #fff8ef; -} - -.order-simulation-bar strong, -.order-simulation-bar span { - display: block; -} - -.order-simulation-bar strong { - color: #5e4430; - font-size: 12px; -} - -.order-simulation-bar span { - margin-top: 2px; - color: #826e5c; - font-size: 11px; -} - -.order-simulation-bar .btn { - flex: 0 0 auto; - border-radius: 9px; -} - -.order-edit-modal-footer { - display: flex; - gap: 24px; - align-items: center; - justify-content: space-between; - padding: 16px 28px; - border-top: 1px solid #eee4dc; - background: #fff; -} - -.order-edit-modal-footer .order-edit-total { - min-width: 180px; - gap: 20px; - margin-top: 0; - padding: 0; - border-top: 0; -} - -.order-edit-modal-footer .order-edit-actions { - display: flex; - margin-top: 0; -} - -.product-combobox { - position: relative; -} - -.product-combobox-trigger { - display: flex; - width: 100%; - min-height: 46px; - gap: 12px; - align-items: center; - justify-content: space-between; - padding: 0 12px; - border: 1px solid #e5dad3; - border-radius: 9px; - background: #fff; - color: #302a28; - text-align: left; -} - -.product-combobox-trigger:hover, -.product-combobox-trigger[aria-expanded="true"] { - border-color: #dc8a7c; -} - -.product-combobox-trigger[aria-expanded="true"] { - box-shadow: 0 0 0 3px rgb(201 76 59 / 10%); -} - -.product-combobox-trigger > span { - overflow: hidden; - font-size: 13px; - font-weight: 650; - text-overflow: ellipsis; - white-space: nowrap; -} - -.product-combobox-trigger > span.is-placeholder { - color: #9a918c; - font-weight: 500; -} - -.product-combobox-trigger > svg { - width: 17px; - height: 17px; - flex: 0 0 17px; - color: #847a75; -} - -.product-combobox-menu { - position: absolute; - z-index: 20; - top: calc(100% + 7px); - right: 0; - left: 0; - overflow: hidden; - border: 1px solid #e5dad3; - border-radius: 12px; - background: #fff; - box-shadow: 0 16px 38px rgb(67 49 43 / 16%); -} - -.product-combobox-search { - display: flex; - min-height: 46px; - gap: 9px; - align-items: center; - margin: 10px; - padding: 0 11px; - border: 1px solid #e5dad3; - border-radius: 9px; - background: #fff; -} - -.product-combobox-search:focus-within { - border-color: #dc8a7c; - box-shadow: 0 0 0 3px rgb(201 76 59 / 10%); -} - -.product-combobox-search svg { - width: 17px; - height: 17px; - flex: 0 0 17px; - color: #887f7a; -} - -.product-combobox-search input { - width: 100%; - min-width: 0; - border: 0; - outline: 0; - background: transparent; - color: #302a28; - font-size: 13px; -} - -.product-combobox-options { - max-height: 220px; - overflow-y: auto; - padding: 0 7px 7px; -} - -.product-combobox-options button { - display: flex; - width: 100%; - min-height: 48px; - gap: 12px; - align-items: center; - justify-content: space-between; - padding: 8px 10px; - border: 0; - border-radius: 8px; - background: transparent; - color: #3f3835; - text-align: left; -} - -.product-combobox-options button:hover, -.product-combobox-options button.is-selected { - background: #fff2ed; - color: var(--primary-dark); -} - -.product-combobox-options button > span, -.product-combobox-options strong, -.product-combobox-options small { - display: block; -} - -.product-combobox-options strong { - font-size: 12px; -} - -.product-combobox-options small { - margin-top: 2px; - color: #817874; - font-size: 10px; -} - -.product-combobox-options button > svg { - width: 17px; - height: 17px; - flex: 0 0 17px; -} - -.product-combobox-options > p { - padding: 20px 12px; - color: #817874; - font-size: 12px; - text-align: center; -} - -.order-edit-general { - display: grid; - grid-template-columns: minmax(0, 1fr) 130px; - gap: 10px; -} - -.order-edit-general label, -.order-edit-field { - display: grid; - gap: 5px; - color: #6f6662; - font-size: 11px; - font-weight: 700; -} - -.order-edit-general input, -.order-edit-general select, -.order-edit-field > input, -.order-edit-field > select, -.order-edit-price > div { - width: 100%; - min-height: 44px; - padding: 0 11px; - border: 1px solid #e5dad3; - border-radius: 9px; - outline: 0; - background: #fff; - color: #302a28; - font-size: 13px; - font-weight: 600; -} - -.order-edit-general input:focus, -.order-edit-general select:focus, -.order-edit-field > input:focus, -.order-edit-field > select:focus, -.order-edit-price > div:focus-within { - border-color: #dc8a7c; - box-shadow: 0 0 0 3px rgb(201 76 59 / 10%); -} - -.order-edit-general select, -.order-edit-field > select { - padding-right: 34px; -} - -.order-edit-section-heading { - display: flex; - gap: 12px; - align-items: center; - justify-content: space-between; - margin-top: 22px; -} - -.order-edit-section-heading h3 { - color: #302a28; - font-size: 14px; -} - -.order-edit-section-heading p { - margin-top: 3px; - color: #817874; - font-size: 11px; -} - -.order-edit-section-heading .btn { - flex: 0 0 auto; - border-radius: 9px; -} - -.order-edit-section-heading .btn svg, -.order-edit-empty .btn svg { - width: 16px; - height: 16px; -} - -.order-edit-items { - display: grid; - gap: 10px; - margin-top: 12px; -} - -.order-edit-item { - padding: 14px; - border: 1px solid #e9dfd7; - border-radius: 11px; - background: #fffdfb; -} - -.order-edit-item-heading { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 10px; -} - -.order-edit-item-heading strong { - color: #554c48; - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.04em; -} - -.order-edit-item-heading button { - display: grid; - width: 36px; - height: 36px; - place-items: center; - border: 0; - border-radius: 8px; - background: transparent; - color: #8f746d; -} - -.order-edit-item-heading button:hover { - background: #fff0ec; - color: #a83a2f; -} - -.order-edit-item-heading svg { - width: 17px; - height: 17px; -} - -.order-edit-item-row { - display: grid; - grid-template-columns: minmax(150px, 0.8fr) minmax(150px, 1fr); - gap: 10px; - margin-block: 11px; -} - -.order-edit-quantity { - display: grid; - gap: 5px; - color: #6f6662; - font-size: 11px; - font-weight: 700; -} - -.order-edit-quantity > div { - display: grid; - grid-template-columns: 42px minmax(42px, 1fr) 42px; - min-height: 44px; - overflow: hidden; - border: 1px solid #e5dad3; - border-radius: 9px; - background: #fff; -} - -.order-edit-quantity button { - display: grid; - place-items: center; - border: 0; - background: #faf7f4; - color: #6e625d; -} - -.order-edit-quantity button:hover:not(:disabled) { - background: #fff0eb; - color: var(--primary-dark); -} - -.order-edit-quantity button:disabled { - opacity: 0.4; -} - -.order-edit-quantity svg { - width: 15px; - height: 15px; -} - -.order-edit-quantity input { - min-width: 0; - border: 0; - outline: 0; - background: #fff; - color: #302a28; - font-size: 13px; - font-weight: 700; - text-align: center; -} - -.order-edit-price > div { - display: flex; - align-items: center; - padding: 0; - overflow: hidden; -} - -.order-edit-price > div > span { - align-self: stretch; - display: flex; - align-items: center; - padding: 0 9px; - border-right: 1px solid #ece2dc; - background: #faf7f4; - font-size: 10px; -} - -.order-edit-price input { - width: 100%; - min-width: 0; - padding: 0 9px; - border: 0; - outline: 0; - color: #302a28; - font-size: 13px; - font-weight: 700; -} - -.order-edit-price.is-readonly > div { - background: #f7f3f0; -} - -.order-edit-price.is-readonly input { - cursor: default; - background: #f7f3f0; - color: #625954; -} - -.order-edit-empty { - display: grid; - gap: 10px; - justify-items: center; - padding: 24px; - border: 1px dashed #decfc7; - border-radius: 11px; - color: #817874; - font-size: 12px; - text-align: center; -} - -.order-edit-total { - display: flex; - align-items: center; - justify-content: space-between; - margin-top: 14px; - padding: 15px 2px; - border-top: 1px solid #eee4de; - color: #4c4541; - font-size: 13px; -} - -.order-edit-total strong { - font-size: 18px; -} - -.order-edit-actions { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 10px; - margin-top: 4px; -} - -.order-edit-actions .btn { - border-radius: 9px; -} - -.order-details-item { - display: grid; - grid-template-columns: 28px minmax(0, 1fr) auto; - gap: 10px; - align-items: start; - padding: 15px 0; - border-bottom: 1px solid #eee6e0; -} - -.order-details-item > span { - color: var(--primary); - font-size: 13px; - font-weight: 800; -} - -.order-details-item strong, -.order-details-item small { - display: block; -} - -.order-details-item strong, -.order-details-item b { - color: #302a28; - font-size: 13px; -} - -.order-details-item small { - margin-top: 3px; - color: #8a5a28; - font-size: 11px; -} - -.order-details-total { - display: flex; - align-items: center; - justify-content: space-between; - padding: 20px 0 4px; - color: #302a28; - font-size: 15px; -} - -.order-details-total strong { - font-size: 18px; -} - -.order-details-actions { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 10px; - margin-top: 24px; -} - -.order-details-actions .btn:last-child { - grid-column: 1 / -1; -} - -.order-details-actions .order-details-reopen { - grid-column: 1 / -1; -} - -.closed-payment-summary { - margin-top: 20px; - overflow: hidden; - border: 1px solid #cee7d6; - border-radius: 12px; - background: #f3fbf5; -} - -.closed-payment-heading { - display: flex; - gap: 11px; - align-items: center; - padding: 14px 16px; - border-bottom: 1px solid #dbece0; -} - -.closed-payment-heading > svg { - width: 22px; - height: 22px; - color: #34764a; -} - -.closed-payment-heading strong, -.closed-payment-heading span { - display: block; -} - -.closed-payment-heading strong { - color: #235a35; - font-size: 13px; -} - -.closed-payment-heading span { - margin-top: 2px; - color: #5d7765; - font-size: 11px; -} - -.closed-payment-lines { - display: grid; - gap: 8px; - padding: 13px 16px; -} - -.closed-payment-lines > div { - display: grid; - grid-template-columns: 1fr auto; - gap: 2px 12px; - color: #4d6554; - font-size: 12px; -} - -.closed-payment-lines strong { - color: #284d33; -} - -.closed-payment-lines small { - grid-column: 1 / -1; - color: #6a7f70; -} - -.payment-modal-backdrop { - position: fixed; - z-index: 100; - inset: 0; - display: grid; - overflow-y: auto; - place-items: center; - padding: 24px; - background: rgb(37 28 25 / 58%); - backdrop-filter: blur(3px); -} - -.payment-modal { - width: min(1040px, 100%); - max-height: calc(100vh - 48px); - overflow: hidden; - border: 1px solid rgb(255 255 255 / 50%); - border-radius: 20px; - background: #fffdfb; - box-shadow: 0 30px 80px rgb(37 28 25 / 26%); -} - -.payment-modal-header { - display: flex; - gap: 24px; - align-items: flex-start; - justify-content: space-between; - padding: 25px 28px 22px; - border-bottom: 1px solid #eee4dc; -} - -.payment-modal-header span, -.payment-summary > span { - display: block; - margin-bottom: 6px; - color: var(--primary); - font-size: 10px; - font-weight: 800; - letter-spacing: 0.1em; -} - -.payment-modal-header h2 { - color: #282322; - font-size: clamp(22px, 3vw, 30px); - letter-spacing: -0.025em; -} - -.payment-modal-header p { - margin-top: 7px; - color: #7c7470; - font-size: 13px; -} - -.payment-modal-close { - display: grid; - width: 44px; - height: 44px; - flex: 0 0 44px; - place-items: center; - border: 1px solid #eadfd8; - border-radius: 11px; - background: #fff; - color: #655d59; -} - -.payment-modal-close:hover { - border-color: #dfc6bc; - background: #fff5f0; - color: var(--primary-dark); -} - -.payment-modal-body { - display: grid; - grid-template-columns: minmax(0, 1fr) 290px; - max-height: calc(100vh - 208px); - overflow-y: auto; -} - -.payment-modal-main { - min-width: 0; - padding: 24px 28px 28px; -} - -.payment-section-heading { - display: flex; - gap: 18px; - align-items: center; - justify-content: space-between; -} - -.payment-section-heading h3, -.payment-summary h3 { - color: #302a28; - font-size: 16px; -} - -.payment-section-heading p { - margin-top: 4px; - color: #817874; - font-size: 12px; -} - -.payment-section-heading .btn { - flex: 0 0 auto; - border-radius: 10px; -} - -.payment-entries { - display: grid; - gap: 14px; - margin-top: 18px; -} - -.payment-entry { - padding: 17px; - border: 1px solid #e9dfd7; - border-radius: 14px; - background: #fff; - box-shadow: 0 3px 10px rgb(67 49 43 / 4%); -} - -.payment-entry-heading { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 12px; -} - -.payment-entry-heading strong { - color: #3c3532; - font-size: 13px; -} - -.payment-entry-heading > button { - display: grid; - width: 36px; - height: 36px; - place-items: center; - border: 0; - border-radius: 9px; - background: transparent; - color: #8e7770; -} - -.payment-entry-heading > button:hover { - background: #fff0ec; - color: #a83a2f; -} - -.payment-entry-heading svg { - width: 17px; - height: 17px; -} - -.payment-method-options { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 8px; -} - -.payment-method-options button { - display: flex; - min-width: 0; - min-height: 48px; - gap: 7px; - align-items: center; - justify-content: center; - padding: 0 8px; - border: 1px solid #e5dad3; - border-radius: 10px; - background: #fff; - color: #706762; - font-size: 12px; - font-weight: 700; -} - -.payment-method-options button:hover:not(:disabled) { - border-color: #dca699; - color: var(--primary-dark); -} - -.payment-method-options button.is-selected { - border-color: #c94c3b; - background: #fff1ec; - box-shadow: 0 0 0 2px rgb(201 76 59 / 8%); - color: var(--primary-dark); -} - -.payment-method-options button:disabled { - opacity: 0.38; -} - -.payment-method-options svg { - width: 18px; - height: 18px; - flex: 0 0 18px; -} - -.payment-amount-field { - display: grid; - gap: 6px; - margin-top: 14px; - color: #675f5b; - font-size: 11px; - font-weight: 700; -} - -.payment-amount-field > div { - display: flex; - min-height: 46px; - align-items: center; - overflow: hidden; - border: 1px solid #e5dad3; - border-radius: 10px; - background: #fff; -} - -.payment-amount-field > div:focus-within { - border-color: #dc8a7c; - box-shadow: 0 0 0 3px rgb(201 76 59 / 10%); -} - -.payment-amount-field > div > span { - align-self: stretch; - display: flex; - align-items: center; - padding: 0 12px; - border-right: 1px solid #eee4dd; - background: #faf7f4; - color: #766c67; - font-size: 12px; - font-weight: 800; -} - -.payment-amount-field input { - width: 100%; - min-width: 0; - padding: 0 13px; - border: 0; - outline: 0; - background: transparent; - color: #302a28; - font-size: 15px; - font-weight: 700; -} - -.cash-change-box { - margin-top: 14px; - padding: 13px; - border-radius: 11px; - background: #fff8ed; -} - -.cash-change-toggle { - display: flex; - min-height: 28px; - gap: 9px; - align-items: center; - color: #5f4b31; - font-size: 12px; - font-weight: 700; -} - -.cash-change-toggle input { - width: 18px; - height: 18px; - accent-color: var(--primary); -} - -.cash-change-fields { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(150px, 0.6fr); - gap: 12px; - align-items: end; - margin-top: 4px; -} - -.cash-change-result { - display: grid; - min-height: 69px; - align-content: center; - padding: 10px 13px; - border: 1px solid #eedfc6; - border-radius: 10px; - background: #fff; -} - -.cash-change-result > span { - color: #7c6a50; - font-size: 10px; - font-weight: 700; -} - -.cash-change-result strong { - margin-top: 2px; - color: #4e422f; - font-size: 16px; -} - -.cash-change-result small { - margin-top: 3px; - color: #a43a2f; - font-size: 10px; -} - -.cash-change-result.has-error { - border-color: #e3aaa3; - background: #fff5f3; -} - -.payment-summary { - padding: 25px 24px; - border-left: 1px solid #eee4dc; - background: #faf6f2; -} - -.payment-summary-total { - display: block; - margin-top: 8px; - color: #282322; - font-size: 28px; - letter-spacing: -0.035em; -} - -.payment-summary-breakdown { - display: grid; - gap: 9px; - margin-top: 24px; - padding: 17px 0; - border-block: 1px solid #e7dcd5; -} - -.payment-summary-breakdown > div, -.payment-balance, -.payment-balance > div { - display: flex; - align-items: center; - justify-content: space-between; -} - -.payment-summary-breakdown > div { - gap: 12px; - color: #776e69; - font-size: 12px; -} - -.payment-summary-breakdown strong { - color: #4e4743; -} - -.payment-balance { - gap: 12px; - margin-top: 18px; - padding: 14px; - border: 1px solid #ead9c2; - border-radius: 11px; - background: #fff9ee; - color: #76511c; -} - -.payment-balance > div { - gap: 7px; -} - -.payment-balance svg { - width: 18px; - height: 18px; -} - -.payment-balance span { - font-size: 11px; - font-weight: 700; -} - -.payment-balance strong { - font-size: 14px; -} - -.payment-balance.is-complete { - border-color: #cce4d3; - background: #eff9f2; - color: #28633d; -} - -.payment-balance.has-error { - border-color: #e7bbb5; - background: #fff2ef; - color: #9a3329; -} - -.payment-summary-help { - margin-top: 11px; - color: #7c7470; - font-size: 11px; - line-height: 1.5; -} - -.payment-modal-footer { - display: flex; - gap: 10px; - justify-content: flex-end; - padding: 17px 28px; - border-top: 1px solid #eee4dc; - background: #fff; -} - -.payment-modal-footer .btn { - border-radius: 10px; -} - -.orders-empty-state, -.order-details-empty { - display: grid; - gap: 6px; - place-items: center; - padding: 48px 24px; - color: #817874; - text-align: center; -} - -.orders-empty-state { - border: 1px dashed #dfd3cb; - border-radius: 13px; - background: rgb(255 255 255 / 60%); -} - -.orders-empty-state h3, -.order-details-empty h2 { - color: #302a28; - font-size: 16px; -} - -.orders-empty-state p, -.order-details-empty p { - font-size: 13px; -} - -@media (max-width: 1080px) { - .orders-page { - grid-template-columns: 210px minmax(0, 1fr); - } - - .orders-workspace { - grid-template-columns: 1fr; - } - - .order-details-panel { - position: static; - } -} - -@media (max-width: 820px) { - .orders-toolbar { - align-items: stretch; - flex-direction: column; - } - - .order-filter-tabs { - width: 100%; - } - - .orders-toolbar-filters { - width: 100%; - } - - .orders-toolbar > .btn { - align-self: flex-end; - } - - .payment-modal-body { - grid-template-columns: 1fr; - } - - .payment-summary { - border-top: 1px solid #eee4dc; - border-left: 0; - } -} - -@media (max-width: 720px) { - .orders-page { - grid-template-columns: 1fr; - } - - .orders-page > .admin-sidebar { - position: static; - height: auto; - border-right: 0; - } - - .orders-content { - padding: 24px 16px; - } - - .orders-topbar { - align-items: flex-start; - flex-direction: column; - } - - .orders-topbar-actions, - .order-search { - width: 100%; - } - - .order-search { - flex: 1; - } - - .orders-toolbar { - margin-top: 24px; - } - - .orders-toolbar > .btn { - width: 100%; - } - - .orders-toolbar > .order-create-button { - width: 44px; - align-self: flex-end; - } - - .order-period-filter, - .order-specific-date { - flex: 1 1 180px; - } - - .order-period-filter select, - .order-specific-date input { - width: 100%; - } - - .order-activity-message { - text-align: left; - } - - .payment-modal-backdrop { - place-items: end center; - padding: 0; - } - - .order-edit-modal-backdrop { - place-items: end center; - padding: 0; - } - - .payment-modal, - .order-edit-modal { - width: 100%; - max-height: 94vh; - border-radius: 20px 20px 0 0; - } - - .payment-modal-header, - .payment-modal-main, - .payment-summary, - .order-edit-modal-header, - .order-edit-modal-body { - padding-right: 18px; - padding-left: 18px; - } - - .payment-modal-body, - .order-edit-modal-body { - max-height: calc(94vh - 204px); - } - - .payment-modal-footer, - .order-edit-modal-footer { - padding: 14px 18px; - } -} - -@media (max-width: 440px) { - .order-card-actions, - .order-details-actions { - grid-template-columns: 1fr; - } - - .order-details-heading { - align-items: flex-start; - flex-direction: column; - } - - .order-details-item { - grid-template-columns: 26px minmax(0, 1fr); - } - - .order-details-item > b { - grid-column: 2; - } - - .payment-section-heading { - align-items: stretch; - flex-direction: column; - } - - .payment-method-options { - grid-template-columns: repeat(2, 1fr); - } - - .cash-change-fields { - grid-template-columns: 1fr; - } - - .payment-modal-footer .btn { - flex: 1; - padding-inline: 10px; - } - - .order-edit-general, - .order-edit-item-row { - grid-template-columns: 1fr; - } - - .order-edit-section-heading { - align-items: stretch; - flex-direction: column; - } - - .order-simulation-bar { - align-items: stretch; - flex-direction: column; - } - - .order-edit-modal-footer { - align-items: stretch; - flex-direction: column; - gap: 12px; - } - - .order-edit-modal-footer .order-edit-total, - .order-edit-modal-footer .order-edit-actions { - width: 100%; - } - - .order-edit-modal-footer .order-edit-actions .btn { - flex: 1; - } -} diff --git a/frontend/app/src/pages/admin/products/index.tsx b/frontend/app/src/pages/admin/products/index.tsx deleted file mode 100644 index b593662..0000000 --- a/frontend/app/src/pages/admin/products/index.tsx +++ /dev/null @@ -1,600 +0,0 @@ -import { useState, type ChangeEvent, type FormEvent } from "react"; -import { Plus, Search } from "lucide-react"; -import { Button } from "@/components/buttons/Button"; -import { AdminSidebar } from "@/components/navigation/AdminSidebar"; -import { Switch } from "@/components/forms/Switch"; -import { SelectField } from "@/components/inputs/SelectField"; -import { TextAreaField } from "@/components/inputs/TextAreaField"; -import { TextField } from "@/components/inputs/TextField"; -import trashIcon from "@/assets/lixeira-icon.png"; -import "./styles.css"; - -type MockProduct = { - id: number; - name: string; - category: string; - price: string; - quantity: number; - available: boolean; - observation?: string; - imageUrl?: string; -}; - -type NewProductForm = { - name: string; - quantity: string; - price: string; - observation: string; - category: string; - imageUrl: string; - available: boolean; -}; - -const emptyProductForm: NewProductForm = { - name: "", - quantity: "0", - price: "", - observation: "", - category: "", - imageUrl: "", - available: true, -}; - -const productCategories = [ - "Lanches", - "Pizzas", - "Porções", - "Bebidas", - "Sobremesas", -]; - -const initialProducts: MockProduct[] = [ - { - id: 1, - name: "X-Burger artesanal", - category: "Lanches", - price: "R$ 28,90", - quantity: 18, - available: true, - }, - { - id: 2, - name: "Pizza margherita", - category: "Pizzas", - price: "R$ 44,90", - quantity: 12, - available: true, - }, - { - id: 3, - name: "Batata rústica", - category: "Porções", - price: "R$ 18,00", - quantity: 0, - available: false, - }, - { - id: 4, - name: "Suco de laranja", - category: "Bebidas", - price: "R$ 12,00", - quantity: 24, - available: true, - }, -]; - -type ProductsProps = { - onNavigate?: (id: string) => void; -}; - -export default function Products({ onNavigate }: ProductsProps) { - const [activeItem, setActiveItem] = useState("menu"); - const [products, setProducts] = useState(initialProducts); - const [isProductModalOpen, setIsProductModalOpen] = useState(false); - const [editingProductId, setEditingProductId] = useState(null); - const [newProduct, setNewProduct] = - useState(emptyProductForm); - const [searchQuery, setSearchQuery] = useState(""); - const [categoryFilter, setCategoryFilter] = useState("all"); - const [productToDelete, setProductToDelete] = useState( - null, - ); - - const normalizedSearch = searchQuery.trim().toLocaleLowerCase("pt-BR"); - const filteredProducts = products.filter((product) => { - const matchesSearch = `${product.name} ${product.category}` - .toLocaleLowerCase("pt-BR") - .includes(normalizedSearch); - const matchesCategory = - categoryFilter === "all" || product.category === categoryFilter; - - return matchesSearch && matchesCategory; - }); - - const updateAvailability = (productId: number, available: boolean) => { - setProducts((currentProducts) => - currentProducts.map((product) => - product.id === productId ? { ...product, available } : product, - ), - ); - }; - - const updateQuantity = (productId: number, quantity: number) => { - setProducts((currentProducts) => - currentProducts.map((product) => - product.id === productId ? { ...product, quantity } : product, - ), - ); - }; - - const closeProductModal = () => { - setIsProductModalOpen(false); - setEditingProductId(null); - setNewProduct(emptyProductForm); - }; - - const openNewProduct = () => { - setEditingProductId(null); - setNewProduct(emptyProductForm); - setIsProductModalOpen(true); - }; - - const openEditProduct = (product: MockProduct) => { - setEditingProductId(product.id); - setNewProduct({ - name: product.name, - quantity: String(product.quantity), - price: product.price - .replaceAll(".", "") - .replace(",", ".") - .replace(/[^\d.]/g, ""), - observation: product.observation ?? "", - category: product.category, - imageUrl: product.imageUrl ?? "", - available: product.available, - }); - setIsProductModalOpen(true); - }; - - const handlePhotoChange = (event: ChangeEvent) => { - const file = event.target.files?.[0]; - - if (!file) { - setNewProduct((current) => ({ ...current, imageUrl: "" })); - return; - } - - const reader = new FileReader(); - reader.onload = () => { - setNewProduct((current) => ({ - ...current, - imageUrl: typeof reader.result === "string" ? reader.result : "", - })); - }; - reader.readAsDataURL(file); - }; - - const handleSaveProduct = (event: FormEvent) => { - event.preventDefault(); - - const quantity = Math.max(0, Number(newProduct.quantity)); - const price = Math.max(0, Number(newProduct.price)); - const formattedPrice = price.toLocaleString("pt-BR", { - style: "currency", - currency: "BRL", - }); - - setProducts((currentProducts) => { - if (editingProductId !== null) { - return currentProducts.map((product) => - product.id === editingProductId - ? { - ...product, - name: newProduct.name.trim(), - category: newProduct.category, - price: formattedPrice, - quantity, - available: newProduct.available, - observation: newProduct.observation.trim() || undefined, - imageUrl: newProduct.imageUrl || undefined, - } - : product, - ); - } - - const nextId = - Math.max(0, ...currentProducts.map((product) => product.id)) + 1; - return [ - ...currentProducts, - { - id: nextId, - name: newProduct.name.trim(), - category: newProduct.category, - price: formattedPrice, - quantity, - available: newProduct.available, - observation: newProduct.observation.trim() || undefined, - imageUrl: newProduct.imageUrl || undefined, - }, - ]; - }); - - closeProductModal(); - }; - - const requestProductDeletion = (product: MockProduct) => { - closeProductModal(); - setProductToDelete(product); - }; - - const confirmProductDeletion = () => { - if (!productToDelete) return; - - setProducts((currentProducts) => - currentProducts.filter((product) => product.id !== productToDelete.id), - ); - setProductToDelete(null); - }; - - return ( -
- { - setActiveItem(id); - onNavigate?.(id); - }} - userName="Nome do usuário" - userRole="Administrador" - /> - -
-
-
- Cardápio -

PRODUTOS

-

Controle disponibilidade, preço e destaque para o cliente.

-
-
- - - -
-
- -
-
-
-

Itens do cardápio

-

- {normalizedSearch || categoryFilter !== "all" - ? `${filteredProducts.length} de ${products.length} produtos` - : `${products.length} produtos cadastrados`} -

-
-
- -
- {filteredProducts.map((product) => ( -
-
- -
-

{product.name}

-

{product.category}

-
-
- - {product.price} - -
- - updateQuantity( - product.id, - Math.max(0, Number(event.target.value)), - ) - } - /> - - updateAvailability(product.id, event.target.checked) - } - /> - - -
-
- ))} - {filteredProducts.length === 0 && ( -

- Nenhum produto encontrado com os filtros selecionados. -

- )} -
-
-
- - {isProductModalOpen && ( -
{ - if (event.target === event.currentTarget) closeProductModal(); - }} - > -
-
-
-

- {editingProductId === null - ? "Novo produto" - : "Editar produto"} -

-

- {editingProductId === null - ? "Preencha as informações que serão exibidas no cardápio." - : "Atualize as informações do produto selecionado."} -

-
- -
- -
- - setNewProduct((current) => ({ - ...current, - name: event.target.value, - })) - } - /> - - - setNewProduct((current) => ({ - ...current, - quantity: event.target.value, - })) - } - /> - - - setNewProduct((current) => ({ - ...current, - price: event.target.value, - })) - } - /> - - - setNewProduct((current) => ({ - ...current, - category: event.target.value, - })) - } - > - - {productCategories.map((category) => ( - - ))} - - -
- Status - - setNewProduct((current) => ({ - ...current, - available: event.target.checked, - })) - } - /> -
- - - setNewProduct((current) => ({ - ...current, - observation: event.target.value, - })) - } - /> - -
- - {newProduct.imageUrl && ( - Prévia do produto - )} -
- -
- {editingProductId !== null && ( - - )} -
- - -
-
- -
-
- )} - - {productToDelete && ( -
{ - if (event.target === event.currentTarget) setProductToDelete(null); - }} - > -
- -

Excluir produto?

-

- “{productToDelete.name}” será removido da lista. Esta ação não - poderá ser desfeita. -

-
- - -
-
-
- )} -
- ); -} diff --git a/frontend/app/src/pages/admin/products/styles.css b/frontend/app/src/pages/admin/products/styles.css deleted file mode 100644 index 01d4762..0000000 --- a/frontend/app/src/pages/admin/products/styles.css +++ /dev/null @@ -1,578 +0,0 @@ -.products-page { - display: grid; - grid-template-columns: 240px minmax(0, 1fr); - min-height: 100vh; -} - -.products-page > .admin-sidebar { - position: sticky; - top: 0; - height: 100vh; - border-block: 0; - border-left: 0; - border-radius: 0; -} - -.products-content { - min-width: 0; - padding: clamp(24px, 4vw, 48px); - font-family: var(--font-body); -} - -.products-heading { - max-width: 720px; -} - -.products-topbar { - display: flex; - gap: 24px; - align-items: flex-end; - justify-content: space-between; -} - -.products-topbar-actions { - display: flex; - gap: 12px; - align-items: center; - flex: 0 0 auto; -} - -.product-search { - display: flex; - width: min(280px, 34vw); - min-height: 44px; - gap: 10px; - align-items: center; - padding: 0 12px; - border: 1px solid var(--border); - border-radius: var(--radius-control); - background: var(--surface); - color: var(--text-secondary); -} - -.product-search:focus-within { - border-color: var(--primary); - box-shadow: 0 0 0 3px rgb(201 76 59 / 14%); -} - -.product-search svg { - width: 18px; - height: 18px; - flex: 0 0 18px; -} - -.product-search input { - width: 100%; - min-width: 0; - border: 0; - outline: 0; - background: transparent; - color: var(--text); -} - -.product-search input::placeholder { - color: var(--text-secondary); -} - -.visually-hidden { - position: absolute; - width: 1px; - height: 1px; - overflow: hidden; - clip: rect(0 0 0 0); - clip-path: inset(50%); - white-space: nowrap; -} - -.product-category-filter select { - min-height: 44px; - padding: 0 36px 0 12px; - border: 1px solid var(--border); - border-radius: var(--radius-control); - outline: 0; - background: var(--surface); - color: var(--text); - font-size: 14px; -} - -.product-category-filter select:focus { - border-color: var(--primary); - box-shadow: 0 0 0 3px rgb(201 76 59 / 14%); -} - -.products-heading > span { - display: block; - margin-bottom: 6px; - color: var(--primary); - font-size: 14px; - font-weight: 800; -} - -.products-heading h1 { - color: var(--primary-dark); - font-family: var(--font-heading); - font-size: clamp(28px, 3vw, 40px); - font-weight: 800; -} - -.products-heading p { - margin-top: 10px; - color: var(--text-secondary); - font-family: var(--font-body); - font-size: 16px; -} - -.products-list { - overflow: hidden; - margin-top: 36px; - border: 1px solid var(--border); - border-radius: var(--radius-control); - background: var(--surface); - box-shadow: var(--shadow-soft); -} - -.products-list-heading { - display: flex; - align-items: center; - justify-content: space-between; - padding: 20px 24px; - border-bottom: 1px solid var(--border); -} - -.products-list-heading h2 { - color: var(--text); - font-family: var(--font-heading); - font-size: 18px; -} - -.products-list-heading p { - margin-top: 4px; - color: var(--text-secondary); - font-size: 13px; -} - -.products-list-body { - display: grid; -} - -.products-no-results { - padding: 36px 24px; - color: var(--text-secondary); - text-align: center; -} - -.product-row { - display: grid; - grid-template-columns: minmax(220px, 1fr) minmax(100px, 140px) minmax(320px, auto); - gap: 24px; - align-items: center; - padding: 18px 24px; -} - -.product-row + .product-row { - border-top: 1px solid var(--border); -} - -.product-identity { - display: flex; - gap: 14px; - align-items: center; - min-width: 0; -} - -.product-initial { - display: grid; - width: 44px; - height: 44px; - flex: 0 0 44px; - place-items: center; - border-radius: var(--radius-card); - background: rgb(201 76 59 / 10%); - color: var(--primary-dark); - font-family: var(--font-heading); - font-size: 18px; - font-weight: 800; -} - -.product-initial img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.product-identity h3 { - overflow: hidden; - color: var(--text); - font-family: var(--font-heading); - font-size: 15px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.product-identity p { - margin-top: 3px; - color: var(--text-secondary); - font-size: 13px; -} - -.product-price { - display: block; - color: var(--primary-dark); - font-size: 15px; - text-align: center; - white-space: nowrap; -} - -.product-controls { - display: flex; - gap: 20px; - align-items: flex-end; - justify-content: flex-end; -} - -.product-controls .switch-control { - min-height: 36px; - font-size: 13px; -} - -.product-controls .product-status { - padding: 0; -} - -.product-controls .product-status.is-active { - background: transparent; - color: #2c6e43; -} - -.product-controls .product-status.is-inactive { - background: transparent; - color: #9c2f24; -} - -.product-quantity { - gap: 5px; - width: 96px; - font-size: 13px; -} - -.product-quantity input { - min-height: 36px; - padding: 6px 10px; -} - -.product-delete-button { - display: grid; - width: 36px; - height: 36px; - flex: 0 0 36px; - padding: 8px; - place-items: center; - border: 0; - border-radius: var(--radius-card); - background: rgb(192 57 43 / 8%); -} - -.product-delete-button img, -.button-trash-icon { - width: 22px; - height: 22px; - filter: brightness(0) saturate(100%) invert(24%) sepia(40%) saturate(2137%) hue-rotate(337deg) brightness(90%) contrast(91%); - object-fit: contain; -} - -.product-delete-action { - background: rgb(192 57 43 / 8%); - color: #9c2f24; -} - -.product-delete-action:hover { - background: rgb(192 57 43 / 14%); -} - -.product-modal-backdrop { - position: fixed; - inset: 0; - z-index: 20; - display: grid; - overflow-y: auto; - place-items: center; - padding: 24px; - background: rgb(41 37 36 / 58%); -} - -.product-modal { - width: min(100%, 620px); - max-height: calc(100vh - 48px); - overflow-y: auto; - border: 1px solid var(--border); - border-radius: var(--radius-large); - background: var(--surface); - box-shadow: var(--shadow-large); -} - -.product-modal-heading { - display: flex; - gap: 20px; - align-items: flex-start; - justify-content: space-between; - padding: 24px 24px 18px; - border-bottom: 1px solid var(--border); -} - -.product-modal-heading h2 { - color: var(--primary-dark); - font-family: var(--font-heading); - font-size: 22px; -} - -.product-modal-heading p { - margin-top: 6px; - color: var(--text-secondary); - font-size: 14px; -} - -.product-modal-close { - display: grid; - width: 36px; - height: 36px; - flex: 0 0 36px; - padding: 0; - place-items: center; - border: 1px solid var(--border); - border-radius: var(--radius-card); - background: var(--surface); - color: var(--text-secondary); - font-size: 24px; - line-height: 1; -} - -.new-product-form { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 18px; - padding: 24px; -} - -.new-product-name, -.new-product-category, -.new-product-status-field, -.new-product-observation, -.new-product-photo, -.product-modal-actions { - grid-column: 1 / -1; -} - -.new-product-status-field { - display: grid; - gap: 8px; - color: var(--text); - font-size: 14px; - font-weight: 700; -} - -.new-product-status-field .switch-control { - width: fit-content; - min-height: 36px; - padding: 0; - font-size: 13px; -} - -.new-product-status-field .is-active { - background: transparent; - color: #2c6e43; -} - -.new-product-status-field .is-inactive { - background: transparent; - color: #9c2f24; -} - -.new-product-observation textarea { - min-height: 92px; -} - -.new-product-photo { - display: grid; - grid-template-columns: minmax(0, 1fr) 72px; - gap: 16px; - align-items: end; -} - -.new-product-photo > img { - width: 72px; - height: 72px; - border: 1px solid var(--border); - border-radius: var(--radius-card); - object-fit: cover; -} - -.product-modal-actions { - display: flex; - gap: 10px; - align-items: center; - justify-content: space-between; - margin: 4px -24px -24px; - padding: 18px 24px; - border-top: 1px solid var(--border); - background: #fffaf5; -} - -.product-modal-save-actions { - display: flex; - gap: 10px; - margin-left: auto; -} - -.product-delete-dialog { - width: min(100%, 420px); - padding: 28px; - border-radius: var(--radius-large); - background: var(--surface); - box-shadow: var(--shadow-large); - text-align: center; -} - -.product-delete-dialog-icon { - display: grid; - width: 48px; - height: 48px; - margin: 0 auto 18px; - padding: 12px; - place-items: center; - border-radius: var(--radius-card); - background: rgb(192 57 43 / 8%); -} - -.product-delete-dialog-icon img { - width: 24px; - height: 24px; - filter: brightness(0) saturate(100%) invert(24%) sepia(40%) saturate(2137%) hue-rotate(337deg) brightness(90%) contrast(91%); - object-fit: contain; -} - -.product-delete-dialog h2 { - color: var(--text); - font-size: 22px; -} - -.product-delete-dialog p { - margin-top: 10px; - color: var(--text-secondary); - font-size: 14px; -} - -.product-delete-dialog footer { - display: flex; - gap: 10px; - justify-content: center; - margin-top: 24px; -} - -@media (max-width: 1040px) and (min-width: 721px) { - .product-row { - grid-template-columns: minmax(0, 1fr) auto; - } - - .product-controls { - grid-column: 1 / -1; - } -} - -@media (max-width: 720px) { - .products-page { - grid-template-columns: 1fr; - } - - .products-page > .admin-sidebar { - position: static; - height: auto; - border-right: 0; - } - - .products-content { - padding: 24px 16px; - } - - .products-topbar { - align-items: flex-start; - flex-direction: column; - } - - .products-topbar-actions { - width: 100%; - align-items: stretch; - flex-direction: column; - } - - .product-search, - .product-category-filter select, - .products-topbar-actions > .btn { - width: 100%; - } - - .product-row { - grid-template-columns: 1fr; - gap: 14px; - padding: 18px; - } - - .product-controls { - justify-content: flex-start; - } - - .product-price { - text-align: left; - } -} - -@media (max-width: 420px) { - .product-row { - grid-template-columns: 1fr; - } - - .product-controls { - grid-column: 1; - } - - .product-controls { - flex-wrap: wrap; - } - - .product-modal-backdrop { - align-items: end; - padding: 0; - } - - .product-modal { - max-height: 92vh; - border-radius: var(--radius-large) var(--radius-large) 0 0; - } - - .new-product-form { - grid-template-columns: 1fr; - } - - .new-product-name, - .new-product-category, - .new-product-status-field, - .new-product-observation, - .new-product-photo, - .product-modal-actions { - grid-column: 1; - } - - .product-modal-actions { - align-items: stretch; - flex-direction: column-reverse; - } - - .product-modal-save-actions { - width: 100%; - margin-left: 0; - } - - .product-modal-save-actions .btn, - .product-modal-actions > .btn { - flex: 1; - } -} diff --git a/frontend/app/src/pages/admin/table/index.tsx b/frontend/app/src/pages/admin/table/index.tsx deleted file mode 100644 index bafe0ff..0000000 --- a/frontend/app/src/pages/admin/table/index.tsx +++ /dev/null @@ -1,547 +0,0 @@ -import { fetchTables, saveTable as persistTable, removeTable, tableQrUrl, downloadTableQr, changeTableStatus, type TableRecord, type TableStatus } from "@/services/tables.service"; -import { useEffect, useState, type ComponentProps } from "react"; -import { - Armchair, - Download, - Pencil, - Plus, - QrCode, - ReceiptText, - Search, -} from "lucide-react"; -import { Button } from "@/components/buttons/Button"; -import { AdminSidebar } from "@/components/navigation/AdminSidebar"; -import { TextField } from "@/components/inputs/TextField"; -import trashIcon from "@/assets/lixeira-icon.png"; -import { Notifications } from "../../../components/notifications"; -import "./styles.css"; - -type OrderItem = { name: string; quantity: number; price: string }; -type RestaurantTable = Omit & { name: string; orderValue?: string; order?: OrderItem[] }; -type TableForm = { name: string; number: string; active: boolean }; -type TablesProps = { onNavigate?: (id: string) => void }; -type FormSubmitEvent = Parameters< - NonNullable["onSubmit"]> ->[0]; - -const emptyTableForm: TableForm = { name: "", number: "", active: true }; -const statusInfo: Record = - { - available: { - label: "Disponível", - description: "Disponível para novo atendimento", - }, - occupied: { label: "Em aberto", description: "Mesa ocupada • atendimento em andamento" }, - closing_requested: { label: "Conta solicitada", description: "Mesa ocupada • preparando a conta" }, - awaiting_payment: { - label: "Aguardando pagamento", - description: "Mesa ocupada • aguardando fechamento", - }, - }; -const nextStatus: Record = { - available: { status: "occupied", label: "Iniciar atendimento" }, - occupied: { status: "closing_requested", label: "Solicitar conta" }, - closing_requested: { status: "awaiting_payment", label: "Aguardar pagamento" }, - awaiting_payment: { status: "available", label: "Fechar atendimento e liberar mesa" }, -}; - -export default function Tables({ onNavigate }: TablesProps) { - const [tables, setTables] = useState([]); - const [searchQuery, setSearchQuery] = useState(""); - const [statusFilter, setStatusFilter] = useState<"all" | TableStatus>("all"); - const [isFormOpen, setIsFormOpen] = useState(false); - const [editingTableId, setEditingTableId] = useState(null); - const [tableForm, setTableForm] = useState(emptyTableForm); - const [selectedTable, setSelectedTable] = useState( - null, - ); - const [selectedDetail, setSelectedDetail] = useState<"qr" | "order" | null>( - null, - ); - const [tableToDelete, setTableToDelete] = useState( - null, - ); - - const [error, setError] = useState(""); - const [busy, setBusy] = useState(false); - const [loading, setLoading] = useState(true); - const refresh = async () => { - const data = await fetchTables(); - const updated = data.map((table) => ({ ...table, name: table.name || `Mesa ${table.number}` })); - setTables(updated); - setSelectedTable((current) => current ? updated.find((table) => table.id === current.id) ?? null : null); - }; - useEffect(() => { - let cancelled = false; - fetchTables().then((data) => { - if (!cancelled) setTables(data.map((table) => ({ ...table, name: table.name || `Mesa ${table.number}` }))); - }).catch((reason: Error) => { if (!cancelled) setError(reason.message); }).finally(() => { if (!cancelled) setLoading(false); }); - return () => { cancelled = true; }; - }, []); - - const normalizedSearch = searchQuery.trim().toLocaleLowerCase("pt-BR"); - const filteredTables = tables.filter( - (table) => - (statusFilter === "all" || table.status === statusFilter) && - table.name.toLocaleLowerCase("pt-BR").includes(normalizedSearch), - ); - const closeForm = () => { - setIsFormOpen(false); - setEditingTableId(null); - setTableForm(emptyTableForm); - }; - const openNewTable = () => { - setEditingTableId(null); - setTableForm(emptyTableForm); - setIsFormOpen(true); - }; - const openEditTable = (table: RestaurantTable) => { - setSelectedTable(null); - setEditingTableId(table.id); - setTableForm({ - name: table.name, - number: String(table.number), - active: table.active, - }); - setIsFormOpen(true); - }; - - const saveTable = async (event: FormSubmitEvent) => { - event.preventDefault(); - setBusy(true); - setError(""); - try { - await persistTable({ name: tableForm.name.trim(), number: Number(tableForm.number), active: tableForm.active }, editingTableId ?? undefined); - await refresh(); - closeForm(); - } catch (reason) { setError((reason as Error).message); } - finally { setBusy(false); } - }; - const deleteTable = async () => { - if (!tableToDelete) return; - setBusy(true); - setError(""); - try { - await removeTable(tableToDelete.id); - await refresh(); - setTableToDelete(null); - setSelectedTable(null); - } catch (reason) { setError((reason as Error).message); } - finally { setBusy(false); } - }; - const advanceStatus = async () => { - if (!selectedTable || busy) return; - setBusy(true); - setError(""); - try { - const updated = await changeTableStatus(selectedTable, nextStatus[selectedTable.status].status); - const table = { ...updated, name: updated.name || `Mesa ${updated.number}` }; - setTables((current) => current.map((item) => item.id === table.id ? table : item)); - setSelectedTable(table); - } catch (reason) { - setError((reason as Error).message); - await refresh().catch(() => undefined); - } finally { setBusy(false); } - }; - - return ( -
- undefined)} - userName="Nome do usuário" - userRole="Administrador" - /> -
- {error &&

{error}

} - {loading &&

Carregando mesas…

} -
-
- SALÃO -

Mesas

-

Acompanhe a ocupação e organize as mesas do seu restaurante.

-
-
- - - -
-
- -
-
-
-

Mapa de mesas

-

- {normalizedSearch || statusFilter !== "all" - ? `${filteredTables.length} de ${tables.length} mesas` - : "Clique em uma mesa para ver o pedido e o QR Code."} -

-
-
-
- {filteredTables.map((table) => ( -
- -
- Valor atual - {table.orderValue ?? "—"} -
-
- ))} - {filteredTables.length === 0 && ( -
-
- )} -
-
-
- - {selectedTable && ( -
- event.target === event.currentTarget && setSelectedTable(null) - } - > -
-
-
- - {statusInfo[selectedTable.status].label} - -

{selectedTable.name}

-

{statusInfo[selectedTable.status].description}

-
- -
-
- {error && selectedDetail !== "qr" &&

{error}

} - {selectedDetail === null && ( -
- -

O que você deseja consultar?

- - {selectedTable.order?.length ? ( - - ) : ( - - Detalhes dos pedidos disponíveis na área de pedidos. - - )} -
- )} - {selectedDetail === "qr" && ( -
-
- )} - {selectedDetail === "order" && ( -
-
-
-
- {selectedTable.order?.map((item) => ( -
- - {item.quantity}× {item.name} - - {item.price} -
- ))} -
-
- Total da conta - {selectedTable.orderValue} -
-
- )} -
-
- {selectedDetail ? ( - - ) : ( - - )} - -
-
-
- )} - {isFormOpen && ( -
- event.target === event.currentTarget && closeForm() - } - > -
-
-
-

- {editingTableId === null ? "Nova mesa" : "Editar mesa"} -

-

- {editingTableId === null - ? "Cadastre uma mesa para organizar o atendimento no salão." - : "Atualize as informações e o status desta mesa."} -

-
- -
-
- - setTableForm((current) => ({ - ...current, - name: event.target.value, - })) - } - /> - setTableForm((current) => ({ ...current, number: event.target.value }))} /> - - {error &&

{error}

} -
-
- {editingTableId !== null && ( - - )} -
-
- - -
-
- -
-
- )} - {tableToDelete && ( -
- event.target === event.currentTarget && setTableToDelete(null) - } - > -
-
- -
-

Excluir mesa?

- {error &&

{error}

} -

- “{tableToDelete.name}” será removida permanentemente da lista de - mesas. -

-
- - -
-
-
- )} -
- ); -} diff --git a/frontend/app/src/pages/admin/table/styles.css b/frontend/app/src/pages/admin/table/styles.css deleted file mode 100644 index 599feb8..0000000 --- a/frontend/app/src/pages/admin/table/styles.css +++ /dev/null @@ -1,699 +0,0 @@ -.tables-page { - display: grid; - grid-template-columns: 240px minmax(0, 1fr); - min-height: 100vh; - background: #fcfbfa; -} -.tables-page > .admin-sidebar { - position: sticky; - top: 0; - height: 100vh; - border-block: 0; - border-left: 0; - border-radius: 0; -} -.tables-content { - min-width: 0; - padding: clamp(24px, 4vw, 48px); -} -.tables-topbar { - display: flex; - gap: 24px; - align-items: flex-end; - justify-content: space-between; -} -.tables-heading span { - display: block; - margin-bottom: 7px; - color: var(--primary); - font-size: 12px; - font-weight: 800; - letter-spacing: 0.1em; -} -.tables-heading h1 { - color: var(--primary-dark); - font-size: clamp(30px, 3vw, 42px); - font-weight: 800; - letter-spacing: -0.04em; -} -.tables-heading p { - margin-top: 10px; - color: var(--text-secondary); -} -.tables-topbar-actions { - display: flex; - gap: 12px; - align-items: center; -} -.table-search { - display: flex; - width: min(300px, 31vw); - min-height: 46px; - gap: 10px; - align-items: center; - padding: 0 14px; - border: 1px solid var(--border); - border-radius: var(--radius-control); - background: var(--surface); - color: var(--text-secondary); - box-shadow: var(--shadow-soft); -} -.table-search:focus-within, -.table-status-field select:focus { - border-color: var(--primary); - box-shadow: 0 0 0 3px rgb(201 76 59 / 14%); -} -.table-search svg { - width: 18px; - flex: 0 0 auto; -} -.table-search input { - width: 100%; - min-width: 0; - border: 0; - outline: 0; - background: transparent; - color: var(--text); -} -.table-search input::placeholder { - color: var(--text-secondary); -} -.table-notifications { - display: grid; - width: 46px; - height: 46px; - flex: 0 0 46px; - place-items: center; - border: 1px solid var(--border); - border-radius: var(--radius-control); - background: var(--surface); - color: var(--primary-dark); - box-shadow: var(--shadow-soft); -} -.table-notifications:hover { - background: #fff3eb; -} -.table-notifications svg { - width: 19px; -} -.table-status-tabs { - display: flex; - gap: 4px; - width: fit-content; - max-width: 100%; - margin-top: 32px; - padding: 4px; - overflow-x: auto; - border: 1px solid var(--border); - border-radius: var(--radius-control); - background: var(--surface); - box-shadow: var(--shadow-soft); -} -.table-status-tabs button { - display: flex; - gap: 8px; - align-items: center; - padding: 9px 12px; - border: 0; - border-radius: var(--radius-card); - background: transparent; - color: var(--text-secondary); - font-size: 13px; - font-weight: 700; - white-space: nowrap; -} -.table-status-tabs button:hover { - background: #fff3eb; - color: var(--primary-dark); -} -.table-status-tabs button.active { - background: var(--primary); - color: white; -} -.table-status-tabs button span { - padding: 0 5px; - border-radius: var(--radius-full); - background: rgb(41 37 36 / 9%); - font-size: 11px; -} -.table-status-tabs button.active span { - background: rgb(255 255 255 / 20%); -} -.tables-list { - overflow: hidden; - margin-top: 24px; - border: 1px solid var(--border); - border-radius: var(--radius-control); - background: var(--surface); - box-shadow: var(--shadow-soft); -} -.tables-list-heading { - display: flex; - gap: 16px; - align-items: center; - justify-content: space-between; - padding: 20px 24px; - border-bottom: 1px solid var(--border); -} -.tables-list-heading h2 { - font-size: 18px; -} -.tables-list-heading p { - margin-top: 4px; - color: var(--text-secondary); - font-size: 13px; -} -.tables-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); - gap: 16px; - padding: 24px; -} -.table-card { - --table-accent: var(--primary); - --table-accent-soft: rgb(201 76 59 / 10%); - display: flex; - min-height: 210px; - overflow: hidden; - flex-direction: column; - border: 1px solid color-mix(in srgb, var(--table-accent) 26%, var(--border)); - border-top: 4px solid var(--table-accent); - border-radius: var(--radius-card); - background: linear-gradient( - 145deg, - var(--table-accent-soft), - var(--surface) 46% - ); - box-shadow: 0 3px 12px rgb(41 37 36 / 5%); - transition: - transform 0.18s ease, - box-shadow 0.18s ease; -} -.table-card.available { - --table-accent: #2c8a50; - --table-accent-soft: rgb(44 138 80 / 9%); -} -.table-card.occupied { - --table-accent: #2878c7; - --table-accent-soft: rgb(40 120 199 / 10%); -} -.table-card.awaiting_payment { - --table-accent: #b87412; - --table-accent-soft: rgb(184 116 18 / 11%); -} -.table-card.closing_requested { - --table-accent: #8b5bb5; - --table-accent-soft: rgb(139 91 181 / 10%); -} -.table-card:hover { - transform: translateY(-3px); - box-shadow: var(--shadow); -} -.table-card-main { - display: flex; - width: 100%; - min-height: 145px; - padding: 20px 18px 18px; - flex: 1; - flex-direction: column; - border: 0; - background: transparent; - color: inherit; - text-align: left; - cursor: pointer; -} -.table-card-main:focus-visible { - outline: 3px solid color-mix(in srgb, var(--table-accent) 35%, transparent); - outline-offset: -3px; -} -.table-card-topline { - display: flex; - gap: 12px; - align-items: flex-start; - justify-content: space-between; -} -.table-number { - color: var(--table-accent); - font-family: var(--font-heading); - font-size: 42px; - font-weight: 800; - letter-spacing: -0.04em; - line-height: 0.86; -} -.table-status { - display: inline-block; - padding: 5px 9px; - border-radius: var(--radius-full); - font-size: 11px; - font-weight: 800; - line-height: 1.1; -} -.table-status.available { - background: rgb(44 138 80 / 12%); - color: #247444; -} -.table-status.occupied { - background: rgb(40 120 199 / 12%); - color: #2166aa; -} -.table-status.awaiting_payment { - background: rgb(184 116 18 / 14%); - color: #9b5d08; -} -.table-status.closing_requested { - background: rgb(139 91 181 / 12%); - color: #75469d; -} -.table-card-info { - margin: auto 0 0; - padding-top: 26px; -} -.table-card h3 { - font-size: 18px; -} -.table-card-main p { - margin-top: 6px; - color: var(--text-secondary); - font-size: 13px; -} -.table-card-value { - display: flex; - min-height: 62px; - align-items: end; - justify-content: space-between; - padding: 12px 18px 14px; - border-top: 1px solid - color-mix(in srgb, var(--table-accent) 20%, var(--border)); - background: rgb(255 255 255 / 60%); -} -.table-card-value span { - color: var(--text-secondary); - font-size: 11px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.05em; -} -.table-card-value strong { - color: var(--table-accent); - font-size: 21px; - line-height: 1; - white-space: nowrap; -} -.tables-no-results { - display: grid; - grid-column: 1 / -1; - gap: 10px; - justify-items: center; - padding: 42px 24px; - color: var(--text-secondary); - text-align: center; -} -.tables-no-results svg { - width: 34px; - color: var(--primary); - opacity: 0.55; -} -.table-modal-backdrop { - position: fixed; - inset: 0; - z-index: 1000; - display: grid; - overflow: hidden; - place-items: center; - padding: 24px; - background: rgb(41 37 36 / 58%); -} -.table-modal, -.table-details-modal { - width: min(100%, 520px); - max-height: 90vh; - overflow: hidden; - border: 1px solid var(--border); - border-radius: var(--radius-large); - background: var(--surface); - box-shadow: var(--shadow-large); -} -.table-details-modal { - display: flex; - width: min(100%, 680px); - flex-direction: column; -} -.table-modal-heading { - display: flex; - gap: 20px; - flex: 0 0 auto; - align-items: flex-start; - justify-content: space-between; - padding: 24px; - border-bottom: 1px solid var(--border); -} -.table-modal-heading h2 { - margin-top: 10px; - color: var(--primary-dark); - font-size: 22px; -} -.table-modal-heading p { - margin-top: 6px; - color: var(--text-secondary); - font-size: 14px; -} -.table-modal-close { - display: grid; - width: 36px; - height: 36px; - flex: 0 0 36px; - padding: 0; - place-items: center; - border: 1px solid var(--border); - border-radius: var(--radius-full); - background: #fffaf5; - color: var(--text-secondary); - font-size: 24px; - line-height: 1; -} -.table-modal-close:hover { - background: rgb(201 76 59 / 12%); - color: var(--primary-dark); -} -.table-modal-close:focus-visible { - outline: 3px solid rgb(201 76 59 / 22%); - outline-offset: 2px; -} -.table-modal-body { - min-height: 0; - overflow-y: auto; -} -.table-detail-actions { - display: flex; - gap: 12px; - flex: 0 0 auto; - align-items: center; - justify-content: space-between; - padding: 18px 24px; - border-top: 1px solid var(--border); - background: #fffaf5; -} -.table-form { - display: grid; - gap: 18px; - max-height: calc(90vh - 106px); - overflow-y: auto; - padding: 24px; -} -.table-status-field { - display: grid; - gap: 8px; - font-size: 14px; - font-weight: 700; -} -.table-status-field select { - width: 100%; - min-height: 40px; - padding: 0 36px 0 12px; - border: 1px solid var(--border); - border-radius: var(--radius-card); - outline: 0; - background: var(--surface); - color: var(--text); - font-size: 14px; -} -.table-modal-actions { - display: flex; - gap: 12px; - align-items: center; - justify-content: space-between; - padding-top: 18px; - border-top: 1px solid var(--border); -} -.table-modal-save-actions { - display: flex; - gap: 10px; - margin-left: auto; -} -.table-delete-action img, -.table-delete-dialog-icon img { - width: 18px; - height: 18px; - filter: brightness(0) saturate(100%) invert(24%) sepia(40%) saturate(2137%) - hue-rotate(337deg) brightness(90%) contrast(91%); -} -.table-delete-action { - background: rgb(192 57 43 / 8%); - color: #9c2f24; -} -.table-options { - display: grid; - gap: 12px; - padding: 28px 24px; -} -.table-options > p { - color: var(--text-secondary); - font-size: 14px; -} -.table-options .btn { - justify-content: flex-start; - min-height: 52px; -} -.table-no-order { - padding: 12px; - border-radius: var(--radius-card); - background: #fffaf5; - color: var(--text-secondary); - font-size: 13px; - text-align: center; -} -.table-order-view, -.table-qr-view { - padding: 28px 24px; -} -.table-section-heading { - display: flex; - gap: 9px; - align-items: center; -} -.table-section-heading svg, -.table-qr-view > svg { - color: var(--primary); -} -.table-section-heading h3, -.table-qr-view h3 { - font-size: 17px; -} -.order-items { - display: grid; - gap: 12px; - margin-top: 18px; -} -.order-items div, -.order-total { - display: flex; - gap: 10px; - justify-content: space-between; - font-size: 14px; -} -.order-items span { - color: var(--text-secondary); -} -.order-total { - margin-top: 18px; - padding-top: 14px; - border-top: 1px solid var(--border); - font-size: 15px; - font-weight: 800; -} -.order-total strong { - color: var(--primary-dark); -} -.table-qr-view { - display: grid; - justify-items: center; - text-align: center; -} -.table-qr-view > svg { - width: 25px; -} -.table-qr-view h3 { - margin-top: 8px; -} -.table-qr-view p { - max-width: 320px; - margin-top: 6px; - color: var(--text-secondary); - font-size: 13px; -} -.table-qr-view small { - margin-top: 9px; - color: var(--text-secondary); - font-size: 11px; -} -.table-qr-view .btn { - margin-top: 15px; -} -.visual-qr { - position: relative; - width: 132px; - height: 132px; - margin-top: 16px; - background: - repeating-linear-gradient(90deg, #292524 0 7px, transparent 7px 14px), - repeating-linear-gradient(#292524 0 7px, transparent 7px 14px); - background-blend-mode: multiply; -} -.visual-qr::after { - position: absolute; - inset: 0; - content: ""; - background: - repeating-linear-gradient( - 90deg, - transparent 0 14px, - rgb(255 255 255 / 0.9) 14px 21px - ), - repeating-linear-gradient( - 0deg, - transparent 0 21px, - rgb(255 255 255 / 0.9) 21px 28px - ); - mix-blend-mode: screen; -} -.visual-qr span { - position: absolute; - z-index: 1; - width: 36px; - height: 36px; - border: 7px solid #292524; - background: var(--surface); -} -.visual-qr span::after { - position: absolute; - inset: 6px; - content: ""; - background: #292524; -} -.visual-qr span:nth-child(1) { - top: 0; - left: 0; -} -.visual-qr span:nth-child(2) { - top: 0; - right: 0; -} -.visual-qr span:nth-child(3) { - bottom: 0; - left: 0; -} -.visual-qr i { - position: absolute; - z-index: 2; - right: 9px; - bottom: 12px; - width: 14px; - height: 14px; - background: #292524; -} -.table-delete-dialog { - width: min(100%, 420px); - padding: 28px; - border-radius: var(--radius-large); - background: var(--surface); - box-shadow: var(--shadow-large); - text-align: center; -} -.table-delete-dialog-icon { - display: grid; - width: 48px; - height: 48px; - margin: 0 auto 18px; - padding: 12px; - place-items: center; - border-radius: var(--radius-card); - background: rgb(192 57 43 / 8%); -} -.table-delete-dialog h2 { - font-size: 22px; -} -.table-delete-dialog p { - margin-top: 10px; - color: var(--text-secondary); - font-size: 14px; -} -.table-delete-dialog footer { - display: flex; - gap: 10px; - justify-content: center; - margin-top: 24px; -} -@media (max-width: 1040px) { - .tables-grid { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } -} -@media (max-width: 820px) { - .tables-page { - grid-template-columns: 1fr; - } - .tables-page > .admin-sidebar { - position: static; - height: auto; - border-right: 0; - } - .tables-topbar { - align-items: flex-start; - flex-direction: column; - } - .tables-topbar-actions { - width: 100%; - } - .table-search { - width: 100%; - } - .tables-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } -} -@media (max-width: 560px) { - .tables-content { - padding: 24px 16px; - } - .tables-topbar-actions > .btn { - flex: 1; - } - .tables-list-heading { - padding: 18px; - } - .tables-grid { - grid-template-columns: 1fr; - padding: 16px; - } - .table-modal-backdrop { - padding: 16px; - } - .table-modal-heading, - .table-detail-actions { - padding-right: 18px; - padding-left: 18px; - } - .table-options, - .table-order-view, - .table-qr-view { - padding: 24px 18px; - } - .table-detail-actions, - .table-modal-actions { - align-items: stretch; - flex-direction: column-reverse; - } - .table-detail-actions .btn, - .table-modal-actions .btn, - .table-modal-save-actions { - width: 100%; - } - .table-modal-save-actions { - display: grid; - grid-template-columns: 1fr 1fr; - margin-left: 0; - } - .table-modal-actions > div:first-child:empty { - display: none; - } -} diff --git a/frontend/app/src/pages/client/Cart/index.tsx b/frontend/app/src/pages/client/Cart/index.tsx deleted file mode 100644 index 2386ad5..0000000 --- a/frontend/app/src/pages/client/Cart/index.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { useState } from "react"; -import { ChevronLeft, ShoppingCart, Menu as MenuIcon, Clock, ReceiptText } from "lucide-react"; -import { useCart } from "@/contexts/useCart"; -import { QuantitySelector, Button } from "@/components"; -import { createOrder, CURRENT_ATTENDANCE_ID } from "@/services/orders.service"; -import "./styles.css"; - -type CartPageProps = { - allowOrdering?: boolean; - onBack: () => void; - onGoToMenu: () => void; - onOrderConfirmed: () => void; -}; - -function formatPrice(value: number) { - return `R$ ${value.toFixed(2).replace(".", ",")}`; -} - -export function Cart({ onBack, onGoToMenu, onOrderConfirmed, allowOrdering = true }: CartPageProps) { - const { items, itemCount, subtotal, serviceFee, total, removeItem, updateQuantity, updateNotes, splitLine, clearCart } = useCart(); - const [isSubmitting, setIsSubmitting] = useState(false); - const [error, setError] = useState(null); - const [orderPlaced, setOrderPlaced] = useState(false); - - async function handleConfirm() { - if (!allowOrdering) { - setError("Pedidos pelo QR Code ainda não estão disponíveis. Solicite ao atendente."); - return; - } - setIsSubmitting(true); - setError(null); - - try { - await createOrder({ - attendanceId: CURRENT_ATTENDANCE_ID, - items: items.map((line) => ({ - productId: line.product.id, - quantity: line.quantity, - notes: line.notes || undefined, - })), - }); - - clearCart(); - setOrderPlaced(true); - } catch { - setError("Não foi possível enviar o pedido. Tente novamente."); - } finally { - setIsSubmitting(false); - } - } - - return ( -
-
- - -
- MesaFlow - Seu pedido -
- - -
- -
- {!allowOrdering &&

Pedidos pelo QR Code ainda não estão disponíveis. Solicite ao atendente.

} - {orderPlaced ? ( -
-
- ) : ( - <> -
-

Seu pedido

- {itemCount} {itemCount === 1 ? "item" : "itens"} -
- - {items.length === 0 ? ( -
-
- ) : ( - <> -
    - {items.map((line) => { - const unitPrice = Number(line.product.price); - const lineSubtotal = unitPrice * line.quantity; - - const quantityReservedElsewhere = items - .filter((other) => other.id !== line.id && other.product.id === line.product.id) - .reduce((sum, other) => sum + other.quantity, 0); - const maxQuantity = Math.max( - line.quantity, - line.product.stockQuantity - quantityReservedElsewhere - ); - - return ( -
  • - {line.product.imageUrl ? ( - {line.product.name} - ) : ( -