Merge branch 'release(MVP/Sprint11)'

This commit is contained in:
Keven 2025-12-05 16:13:02 -03:00
commit e98a293286
157 changed files with 5768 additions and 1102 deletions

249
package-lock.json generated
View file

@ -19,10 +19,11 @@
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
@ -123,6 +124,7 @@
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3",
@ -1434,6 +1436,24 @@
}
}
},
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-arrow": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
@ -1570,6 +1590,24 @@
}
}
},
"node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
@ -1636,6 +1674,24 @@
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-direction": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
@ -1828,6 +1884,24 @@
}
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
@ -1865,6 +1939,24 @@
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
@ -1968,6 +2060,24 @@
}
}
},
"node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-progress": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz",
@ -1992,6 +2102,38 @@
}
}
},
"node_modules/@radix-ui/react-radio-group": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz",
"integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-direction": "1.1.1",
"@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-roving-focus": "1.1.11",
"@radix-ui/react-use-controllable-state": "1.2.2",
"@radix-ui/react-use-previous": "1.1.1",
"@radix-ui/react-use-size": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-roving-focus": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
@ -2097,13 +2239,54 @@
}
}
},
"node_modules/@radix-ui/react-separator": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz",
"integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==",
"node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.3"
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-separator": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz",
"integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.4"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
"integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.2.4"
},
"peerDependencies": {
"@types/react": "*",
@ -2121,9 +2304,9 @@
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
"integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
@ -2231,6 +2414,24 @@
}
}
},
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
@ -2939,6 +3140,7 @@
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@ -2949,6 +3151,7 @@
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
"devOptional": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@ -2965,6 +3168,7 @@
"integrity": "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.46.1",
@ -2995,6 +3199,7 @@
"integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.1",
"@typescript-eslint/types": "8.46.1",
@ -3469,6 +3674,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -3821,6 +4027,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.9",
"caniuse-lite": "^1.0.30001746",
@ -4625,6 +4832,7 @@
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@ -4685,6 +4893,7 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@ -4811,6 +5020,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@ -6965,6 +7175,7 @@
"resolved": "https://registry.npmjs.org/next/-/next-15.5.5.tgz",
"integrity": "sha512-OQVdBPtpBfq7HxFN0kOVb7rXXOSIkt5lTzDJDGRBcOyVvNRIWFauMqi1gIHd1pszq1542vMOGY0HP4CaiALfkA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@next/env": "15.5.5",
"@swc/helpers": "0.5.15",
@ -7361,6 +7572,7 @@
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@ -7518,6 +7730,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -7527,6 +7740,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@ -7539,6 +7753,7 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.65.0.tgz",
"integrity": "sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.0.0"
},
@ -7554,7 +7769,8 @@
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/react-masked-text": {
"version": "1.0.5",
@ -7567,6 +7783,7 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@ -7685,7 +7902,8 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@ -8445,6 +8663,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -8456,7 +8675,8 @@
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/tinymce/-/tinymce-8.1.2.tgz",
"integrity": "sha512-KITxHEEHRlxC5xOnxA123eAJ67NgsWxNphtItWt9TRu07DiTZrWIqJeIKRX9euE51/l3kJO4WQiqoBXKTJJGsA==",
"license": "GPL-2.0-or-later"
"license": "GPL-2.0-or-later",
"peer": true
},
"node_modules/to-regex-range": {
"version": "5.0.1",
@ -8610,6 +8830,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -8675,6 +8896,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"napi-postinstall": "^0.3.0"
},
@ -8961,6 +9183,7 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}

View file

@ -21,10 +21,11 @@
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",

BIN
public/sounds/success.mp3 Normal file

Binary file not shown.

View file

@ -3,13 +3,10 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { Input } from '@/components/ui/input';
import { GUsuarioSchema } from '../../../../../../packages/administrativo/schemas/GUsuario/GUsuarioSchema';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import {
Form,
FormControl,
@ -18,8 +15,10 @@ import {
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { useGUsuarioSaveHook } from '../../../../../../packages/administrativo/hooks/GUsuario/useGUsuarioSaveHook';
import { GUsuarioSchema } from '../../../../../../packages/administrativo/schemas/GUsuario/GUsuarioSchema';
type FormValues = z.infer<typeof GUsuarioSchema>;

View file

@ -1,7 +1,10 @@
'use client';
import { Card, CardContent } from '@/components/ui/card';
import Link from 'next/link';
import { useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import {
Table,
TableBody,
@ -10,14 +13,14 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
import Usuario from '../../../../../packages/administrativo/interfaces/GUsuario/GUsuarioInterface';
import { Button } from '@/components/ui/button';
import Link from 'next/link';
import { useGUsuarioIndexHook } from '../../../../../packages/administrativo/hooks/GUsuario/useGUsuarioIndexHook';
import { useEffect } from 'react';
import Loading from '@/shared/components/loading/loading';
import { useGUsuarioIndexHook } from '../../../../../packages/administrativo/hooks/GUsuario/useGUsuarioIndexHook';
import Usuario from '../../../../../packages/administrativo/interfaces/GUsuario/GUsuarioInterface';
export default function UsuarioPage() {
const { usuarios, fetchUsuarios } = useGUsuarioIndexHook();

View file

@ -1,26 +1,26 @@
'use client';
import { useEffect, useState, useCallback } from 'react';
import { Card, CardContent } from '@/components/ui/card';
import { useCallback, useEffect, useState } from 'react';
import Loading from '@/shared/components/loading/loading';
// Componentes específicos para TServicoTipo
import TServicoTipoTable from '../../_components/t_servico_tipo/TServicoTipoTable';
import TServicoTipoForm from '../../_components/t_servico_tipo/TServicoTipoForm';
import TServicoTipoTable from '../../_components/t_servico_tipo/TServicoTipoTable';
// Hooks específicos para TServicoTipo
import { useTServicoTipoReadHook } from '../../_hooks/t_servico_tipo/useTServicoTipoReadHook';
import { useTServicoTipoSaveHook } from '../../_hooks/t_servico_tipo/useTServicoTipoSaveHook';
import { useTServicoTipoRemoveHook } from '../../_hooks/t_servico_tipo/useTServicoTipoRemoveHook';
import { useTServicoTipoEditHook } from '../../_hooks/t_servico_tipo/useTServicoTipoEditHook';
import { useTServicoTipoEditHook } from '../../../../../../packages/administrativo/hooks/TServicoTipo/useTServicoTipoEditHook';
import { useTServicoTipoReadHook } from '../../../../../../packages/administrativo/hooks/TServicoTipo/useTServicoTipoReadHook';
import { useTServicoTipoRemoveHook } from '../../../../../../packages/administrativo/hooks/TServicoTipo/useTServicoTipoRemoveHook';
import { useTServicoTipoSaveHook } from '../../../../../../packages/administrativo/hooks/TServicoTipo/useTServicoTipoSaveHook';
import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog';
import { useConfirmDialog } from '@/shared/components/confirmDialog/useConfirmDialog';
// Interface específica para TServicoTipo
import TServicoTipoInterface from '../../_interfaces/TServicoTipoInterface';
import Header from '@/shared/components/structure/Header';
import TServicoTipoInterface from '../../_interfaces/TServicoTipoInterface';
export default function TServicoTipoPage() {
// Hooks para leitura, salvamento e remoção

View file

@ -1,11 +1,7 @@
'use client';
import React from 'react';
import z from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
Dialog,
DialogClose,
@ -15,8 +11,6 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { CirclePlus, DollarSign, Settings, SquarePen, Trash } from 'lucide-react';
import {
Form,
FormControl,
@ -26,13 +20,10 @@ import {
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Checkbox } from '@/components/ui/checkbox';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
SelectItem
} from '@/components/ui/select';
import {
Table,
@ -42,23 +33,28 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { zodResolver } from '@hookform/resolvers/zod';
import { CirclePlus, DollarSign, Settings, SquarePen, Trash } from 'lucide-react';
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import { TServicoTipoSchema, TServicoTipoFormValues } from '../../_schemas/TServicoTipoSchema';
import { useEffect } from 'react';
import GMarcacaoTipoSelect from '@/packages/administrativo/components/GMarcacaoTipo/GMarcacaoTipoSelect';
import GEmolumentoSelect from '@/packages/administrativo/components/GEmolumento/GEmolumentoSelect';
import { useGEmolumentoItemReadHook } from '@/app/(protected)/(cadastros)/cadastros/_hooks/g_emolumento_item/useGEmolumentoItemReadHook';
import { useTServicoEtiquetaReadHook } from '@/app/(protected)/(cadastros)/cadastros/_hooks/t_servico_etiqueta/useTServicoEtiquetaReadHook';
import { useTServicoEtiquetaRemoveHook } from '@/app/(protected)/(cadastros)/cadastros/_hooks/t_servico_etiqueta/useTServicoEtiquetaRemoveHook';
import { useTServicoEtiquetaSaveHook } from '@/app/(protected)/(cadastros)/cadastros/_hooks/t_servico_etiqueta/useTServicoEtiquetaSaveHook';
import { GEmolumentoItemReadInterface } from '@/app/(protected)/(cadastros)/cadastros/_interfaces/GEmolumentoItemReadInterface';
import CategoriaServicoSelect from '@/shared/components/categoriaServicoSelect/CategoriaServicoSelect';
import CCaixaServicoSelect from '@/packages/administrativo/components/CCaixaServico/CCaixaServicoSelect';
import { TServicoTipoSaveData } from '../../_data/TServicoTipo/TServicoTipoSaveData';
import GEmolumentoSelect from '@/packages/administrativo/components/GEmolumento/GEmolumentoSelect';
import GMarcacaoTipoSelect from '@/packages/administrativo/components/GMarcacaoTipo/GMarcacaoTipoSelect';
import TTBReconhecimentoTipoSelect from '@/packages/administrativo/components/TTBReconhecimentoTipo/TTBReconhecimentoTipoSelect';
import CategoriaServicoSelect from '@/shared/components/categoriaServicoSelect/CategoriaServicoSelect';
import { ConfirmacaoCheckBox } from '@/shared/components/confirmacao/ConfirmacaoCheckBox';
import ConfirmacaoSelect from '@/shared/components/confirmacao/ConfirmacaoSelect';
import { TipoPessoaSelect } from '@/shared/components/tipoPessoa/tipoPessoaSelect';
import { useTServicoEtiquetaReadHook } from '@/app/(protected)/(cadastros)/cadastros/_hooks/t_servico_etiqueta/useTServicoEtiquetaReadHook';
import { useTServicoEtiquetaSaveHook } from '@/app/(protected)/(cadastros)/cadastros/_hooks/t_servico_etiqueta/useTServicoEtiquetaSaveHook';
import { useTServicoEtiquetaRemoveHook } from '@/app/(protected)/(cadastros)/cadastros/_hooks/t_servico_etiqueta/useTServicoEtiquetaRemoveHook';
import { useEffect } from 'react';
import { TServicoTipoSaveData } from '../../../../../../packages/administrativo/data/TServicoTipo/TServicoTipoSaveData';
import { TServicoTipoFormValues, TServicoTipoSchema } from '../../_schemas/TServicoTipoSchema';
// Propriedades esperadas pelo componente
interface Props {

View file

@ -6,20 +6,19 @@ import API from '@/shared/services/api/Api';
// Importa o enum que define os métodos HTTP disponíveis (GET, POST, PUT, DELETE, etc.)
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import { GEmolumentoReadInterface } from '../../_interfaces/GEmolumentoReadInterface';
// Função assíncrona responsável por executar a requisição para listar os tipos de marcação
async function executeGEmolumentoIndexData(data: GEmolumentoReadInterface) {
// Cria uma nova instância da classe API para enviar a requisição
const api = new API();
// Concatena o endpoint com a query string (caso existam parâmetros)
const endpoint = `administrativo/g_emolumento/${data.sistema_id}`;
// Envia uma requisição GET para o endpoint 'administrativo/g_marcacao_tipo/'
return await api.send({
method: Methods.GET,
endpoint: endpoint,
endpoint: `administrativo/g_emolumento/sistema/${data.sistema_id}?${new URLSearchParams(data.urlParams).toString()}`,
});
}

View file

@ -1,15 +1,16 @@
// Importa o hook responsável por gerenciar e exibir respostas globais (sucesso, erro, etc.)
import { useMemo, useState } from 'react';
import { useResponse } from '@/shared/components/response/ResponseContext';
// Importa hooks do React para gerenciamento de estado e memorização de valores
import { useMemo, useState } from 'react';
// Importa a interface que define a estrutura dos dados de "GEmolumento"
import { GEmolumentoInterface } from '../../_interfaces/GEmolumentoInterface';
import { GEmolumentoReadInterface } from '../../_interfaces/GEmolumentoReadInterface';
// Importa o serviço responsável por buscar os dados de "GEmolumento" na API
import { GEmolumentoIndexService } from '../../_services/g_emolumento/GEmolumentoIndexService';
import { GEmolumentoInterface } from '../../_interfaces/GEmolumentoInterface';
// Hook personalizado para leitura (consulta) dos emolumentos
export const useGEmolumentoReadHook = () => {

View file

@ -1,15 +1,16 @@
// Importa o hook responsável por gerenciar e exibir respostas globais (sucesso, erro, etc.)
import { useMemo, useState } from 'react';
import { useResponse } from '@/shared/components/response/ResponseContext';
// Importa hooks do React para gerenciamento de estado e memorização de valores
import { useMemo, useState } from 'react';
// Importa a interface que define a estrutura dos dados de "gEmolumentoItem"
import { GEmolumentoItemInterface } from '../../_interfaces/GEmolumentoItemInterface';
import { GEmolumentoItemReadInterface } from '../../_interfaces/GEmolumentoItemReadInterface';
// Importa o serviço responsável por buscar os dados de "GEmolumentoItem" na API
import { GEmolumentoItemValorService } from '../../_services/g_emolumento_item/GEmolumentoItemValorService';
import { GEmolumentoItemInterface } from '../../_interfaces/GEmolumentoItemInterface';
// Hook personalizado para leitura (consulta) dos emolumentos
export const useGEmolumentoItemReadHook = () => {

View file

@ -1,3 +1,4 @@
export interface GEmolumentoReadInterface {
sistema_id?: number;
urlParams?: object
}

View file

@ -2,6 +2,7 @@ export default interface TServicoTipoInterface {
servico_tipo_id?: number; // SERVICO_TIPO_ID NUMERIC(10,2) NOT NULL (PK)
descricao: string; // DESCRICAO VARCHAR(60)
valor?: number; // VALOR NUMERIC(14,3)
tipo_item?: string;
requer_autorizacao?: string; // REQUER_AUTORIZACAO VARCHAR(1)
requer_biometria?: string; // REQUER_BIOMETRIA VARCHAR(1)
tipo_pessoa?: string; // TIPO_PESSOA VARCHAR(1)

View file

@ -7,6 +7,7 @@ import { GEmolumentoReadInterface } from '../../_interfaces/GEmolumentoReadInter
// Função assíncrona responsável por executar o serviço de listagem de tipos de marcação
async function executeGEmolumentoIndexService(data: GEmolumentoReadInterface) {
// Chama a função que realiza a requisição à API e aguarda a resposta
const response = await GEmolumentoIndexData(data);

View file

@ -2,8 +2,8 @@ import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import '../globals.css';
import { ResponseProvider } from '../../shared/components/response/ResponseContext';
import { AppSidebar } from '@/components/app-sidebar';
import { ThemeProvider } from '@/components/theme-provider';
import {
Breadcrumb,
BreadcrumbItem,
@ -15,7 +15,10 @@ import {
import { Separator } from '@/components/ui/separator';
import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar';
import { Toaster } from '@/components/ui/sonner';
import Response from '../../shared/components/response/response';
import { ResponseProvider } from '../../shared/components/response/ResponseContext';
const geistSans = Geist({
variable: '--font-geist-sans',
@ -35,46 +38,48 @@ export const metadata: Metadata = {
},
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<html lang="en" suppressHydrationWarning>
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
<SidebarProvider>
<AppSidebar />
<SidebarInset>
<header className="mb-4 flex h-16 shrink-0 items-center gap-2 border-b-1 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
<div className="flex items-center gap-2 px-4">
<SidebarTrigger className="-ml-1" />
<Separator
orientation="vertical"
className="mr-2 data-[orientation=vertical]:h-4"
/>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem className="hidden md:block">
<BreadcrumbLink href="#">Building Your Application</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
</header>
<ResponseProvider>
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
{children}
<Toaster richColors position="top-center" />
<Response />
</div>
</ResponseProvider>
</SidebarInset>
</SidebarProvider>
<ThemeProvider>
<SidebarProvider>
<AppSidebar />
<SidebarInset>
<header className="mb-4 flex h-16 shrink-0 items-center gap-2 border-b-1 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
<div className="flex items-center gap-2 px-4">
<SidebarTrigger className="-ml-1" />
<Separator
orientation="vertical"
className="mr-2 data-[orientation=vertical]:h-4"
/>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem className="hidden md:block">
<BreadcrumbLink href="#">
Building Your Application
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
</header>
<ResponseProvider>
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
{children}
<Toaster richColors position="top-center" />
<Response />
</div>
</ResponseProvider>
</SidebarInset>
</SidebarProvider>
</ThemeProvider>
</body>
</html>
);

View file

@ -0,0 +1,16 @@
'use client'
import { useParams } from "next/navigation";
import TServicoPedidoDetails from "@/packages/servicos/components/TServicoPedido/TServicoPedidoDetails";
export default function TServicoPedidoDetailsPage() {
const params = useParams();
return (
<TServicoPedidoDetails
servico_pedido_id={Number(params.servicoPedidoId)}
/>
)
}

View file

@ -0,0 +1,16 @@
'use client'
import { useParams } from "next/navigation";
import TServicoPedidoForm from "@/packages/servicos/components/TServicoPedido/TServicoPedidoForm";
export default function TServicoPedidoPage() {
const params = useParams();
return (
<TServicoPedidoForm
servico_pedido_id={Number(params.servicoPedidoId)}
/>
)
}

View file

@ -1,90 +1,7 @@
import { Card, CardContent } from "@/components/ui/card";
import TServicoItemPedidoResumo from "@/packages/servicos/components/TServicoItemPedido/TServicoItemPedidoResumo";
import TServicoPedidoForm from "@/packages/servicos/components/TServicoPedido/TServicoPedidoForm"
const itens = [
{
id: 1,
descricao: "Reconhecimento de Firma",
valor: 12.50,
},
{
id: 2,
descricao: "Autenticação de Cópia",
valor: 6.00,
},
{
id: 3,
descricao: "Procuração Pública",
valor: 98.75,
},
{
id: 4,
descricao: "Certidão de Escritura",
valor: 42.30,
},
{
id: 5,
descricao: "Registro de Documento",
valor: 73.10,
},
]
export default function PedidoPage() {
export default function TServicoPedidoPage() {
return (
<div>
<div className="grid w-full grid-cols-12 gap-4">
<div className="col-span-12 sm:col-span-9 md:col-span-9">
<Card>
<CardContent>
<div className="grid w-full grid-cols-12 gap-4">
<div className="col-span-12 sm:col-span-3 md:col-span-3">
<Card>
<CardContent>
</CardContent>
</Card>
</div>
<div className="col-span-12 sm:col-span-3 md:col-span-3">
<Card>
<CardContent>
</CardContent>
</Card>
</div>
<div className="col-span-12 sm:col-span-3 md:col-span-3">
<Card>
<CardContent>
</CardContent>
</Card>
</div>
<div className="col-span-12 sm:col-span-3 md:col-span-3">
<Card>
<CardContent>
</CardContent>
</Card>
</div>
<div className="col-span-12 sm:col-span-3 md:col-span-3">
<Card>
<CardContent>
</CardContent>
</Card>
</div>
</div>
</CardContent>
</Card>
</div>
<div className="col-span-12 sm:col-span-3 md:col-span-3">
<TServicoItemPedidoResumo
dataPedido="01/01/2026"
numeroPedido={123}
itens={itens}
/>
</div>
</div>
</div>
<TServicoPedidoForm />
)
}

View file

@ -3,60 +3,179 @@
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(0.9911 0 0);
--foreground: oklch(0.2988 0.0123 222.4429);
--card: oklch(1.0000 0 0);
--card-foreground: oklch(0.2988 0.0123 222.4429);
--popover: oklch(0.9881 0 0);
--popover-foreground: oklch(0.2988 0.0123 222.4429);
--primary: oklch(0.7210 0.1873 47.5640);
--primary-foreground: oklch(1.0000 0 0);
--secondary: oklch(0.2988 0.0123 222.4429);
--secondary-foreground: oklch(1.0000 0 0);
--muted: oklch(0.9700 0 0);
--muted-foreground: oklch(0.5560 0 0);
--accent: oklch(0.9551 0 0);
--accent-foreground: oklch(0.2988 0.0123 222.4429);
--destructive: oklch(0.5770 0.2450 27.3250);
--destructive-foreground: oklch(1 0 0);
--border: oklch(0.9220 0 0);
--input: oklch(0.9220 0 0);
--ring: oklch(0.7080 0 0);
--chart-1: oklch(0.8100 0.1000 252);
--chart-2: oklch(0.6200 0.1900 260);
--chart-3: oklch(0.5500 0.2200 263);
--chart-4: oklch(0.4900 0.2200 264);
--chart-5: oklch(0.4200 0.1800 266);
--sidebar: oklch(1.0000 0 0);
--sidebar-foreground: oklch(0.2988 0.0123 222.4429);
--sidebar-primary: oklch(0.2364 0.0083 240.2365);
--sidebar-primary-foreground: oklch(0.9850 0 0);
--sidebar-accent: oklch(0.9700 0 0);
--sidebar-accent-foreground: oklch(0.2050 0 0);
--sidebar-border: oklch(0.9220 0 0);
--sidebar-ring: oklch(0.7080 0 0);
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--radius: 0.625rem;
--shadow-x: 0;
--shadow-y: 1px;
--shadow-blur: 3px;
--shadow-spread: 0px;
--shadow-opacity: 0.1;
--shadow-color: oklch(0 0 0);
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
--tracking-normal: 0em;
--spacing: 0.25rem;
--shadow-offset-x: 0;
--shadow-offset-y: 1px;
--letter-spacing: 0em;
}
.dark {
--background: oklch(0.1934 0.0062 236.9149);
--foreground: oklch(0.9881 0 0);
--card: oklch(0.2364 0.0083 240.2365);
--card-foreground: oklch(0.9881 0 0);
--popover: oklch(0.2988 0.0123 222.4429);
--popover-foreground: oklch(0.9881 0 0);
--primary: oklch(0.7210 0.1873 47.5640);
--primary-foreground: oklch(0.2988 0.0123 222.4429);
--secondary: oklch(0.2988 0.0123 222.4429);
--secondary-foreground: oklch(1.0000 0 0);
--muted: oklch(0.2690 0 0);
--muted-foreground: oklch(0.7080 0 0);
--accent: oklch(0.2988 0.0123 222.4429);
--accent-foreground: oklch(1.0000 0 0);
--destructive: oklch(0.7040 0.1910 22.2160);
--destructive-foreground: oklch(0.9850 0 0);
--border: oklch(0.2750 0 0);
--input: oklch(0.3250 0 0);
--ring: oklch(0.5560 0 0);
--chart-1: oklch(0.8100 0.1000 252);
--chart-2: oklch(0.6200 0.1900 260);
--chart-3: oklch(0.5500 0.2200 263);
--chart-4: oklch(0.4900 0.2200 264);
--chart-5: oklch(0.4200 0.1800 266);
--sidebar: oklch(0.2364 0.0083 240.2365);
--sidebar-foreground: oklch(0.9881 0 0);
--sidebar-primary: oklch(0.4880 0.2430 264.3760);
--sidebar-primary-foreground: oklch(0.9850 0 0);
--sidebar-accent: oklch(0.2690 0 0);
--sidebar-accent-foreground: oklch(0.9850 0 0);
--sidebar-border: oklch(0.2750 0 0);
--sidebar-ring: oklch(0.4390 0 0);
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--radius: 0.625rem;
--shadow-x: 0;
--shadow-y: 1px;
--shadow-blur: 3px;
--shadow-spread: 0px;
--shadow-opacity: 0.1;
--shadow-color: oklch(0 0 0);
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
--shadow-offset-x: 0;
--shadow-offset-y: 1px;
--letter-spacing: 0em;
--spacing: 0.25rem;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: Inter, sans-serif;
--font-mono: JetBrains Mono, monospace;
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--font-serif: Source Serif 4, serif;
--radius: 0.375rem;
--shadow-2xs: var(--shadow-2xs);
--shadow-xs: var(--shadow-xs);
--shadow-sm: var(--shadow-sm);
--shadow: var(--shadow);
--shadow-md: var(--shadow-md);
--shadow-lg: var(--shadow-lg);
--shadow-xl: var(--shadow-xl);
--shadow-2xl: var(--shadow-2xl);
--radius: 0.625rem;
--tracking-tighter: calc(var(--tracking-normal) - 0.05em);
--tracking-tight: calc(var(--tracking-normal) - 0.025em);
--tracking-wide: calc(var(--tracking-normal) + 0.025em);
--tracking-wider: calc(var(--tracking-normal) + 0.05em);
--tracking-widest: calc(var(--tracking-normal) + 0.1em);
--tracking-normal: var(--tracking-normal);
--shadow-2xl: var(--shadow-2xl);
--shadow-xl: var(--shadow-xl);
--shadow-lg: var(--shadow-lg);
--shadow-md: var(--shadow-md);
--shadow: var(--shadow);
--shadow-sm: var(--shadow-sm);
--shadow-xs: var(--shadow-xs);
--shadow-2xs: var(--shadow-2xs);
--spacing: var(--spacing);
--letter-spacing: var(--letter-spacing);
--shadow-offset-y: var(--shadow-offset-y);
@ -65,118 +184,6 @@
--shadow-blur: var(--shadow-blur);
--shadow-opacity: var(--shadow-opacity);
--color-shadow-color: var(--shadow-color);
--color-destructive-foreground: var(--destructive-foreground);
}
:root {
--radius: 0.375rem;
--background: oklch(1 0 0);
--foreground: oklch(0.2686 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.2686 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.2686 0 0);
--primary: oklch(0.7686 0.1647 70.0804);
--primary-foreground: oklch(0 0 0);
--secondary: oklch(0.967 0.0029 264.5419);
--secondary-foreground: oklch(0.4461 0.0263 256.8018);
--muted: oklch(0.9846 0.0017 247.8389);
--muted-foreground: oklch(0.551 0.0234 264.3637);
--accent: oklch(0.9869 0.0214 95.2774);
--accent-foreground: oklch(0.4732 0.1247 46.2007);
--destructive: oklch(0.6368 0.2078 25.3313);
--border: oklch(0.9276 0.0058 264.5313);
--input: oklch(0.9276 0.0058 264.5313);
--ring: oklch(0.7686 0.1647 70.0804);
--chart-1: oklch(0.7686 0.1647 70.0804);
--chart-2: oklch(0.6658 0.1574 58.3183);
--chart-3: oklch(0.5553 0.1455 48.9975);
--chart-4: oklch(0.4732 0.1247 46.2007);
--chart-5: oklch(0.4137 0.1054 45.9038);
--sidebar: oklch(0.9846 0.0017 247.8389);
--sidebar-foreground: oklch(0.2686 0 0);
--sidebar-primary: oklch(0.7686 0.1647 70.0804);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.9869 0.0214 95.2774);
--sidebar-accent-foreground: oklch(0.4732 0.1247 46.2007);
--sidebar-border: oklch(0.9276 0.0058 264.5313);
--sidebar-ring: oklch(0.7686 0.1647 70.0804);
--destructive-foreground: oklch(1 0 0);
--font-sans: Inter, sans-serif;
--font-serif: Source Serif 4, serif;
--font-mono: JetBrains Mono, monospace;
--shadow-color: hsl(0 0% 0%);
--shadow-opacity: 0.1;
--shadow-blur: 8px;
--shadow-spread: -1px;
--shadow-offset-x: 0px;
--shadow-offset-y: 4px;
--letter-spacing: 0em;
--spacing: 0.25rem;
--shadow-2xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05);
--shadow-xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05);
--shadow-sm: 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 1px 2px -2px hsl(0 0% 0% / 0.1);
--shadow: 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 1px 2px -2px hsl(0 0% 0% / 0.1);
--shadow-md: 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 2px 4px -2px hsl(0 0% 0% / 0.1);
--shadow-lg: 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 4px 6px -2px hsl(0 0% 0% / 0.1);
--shadow-xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 8px 10px -2px hsl(0 0% 0% / 0.1);
--shadow-2xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.25);
--tracking-normal: 0em;
}
.dark {
--background: oklch(0.2046 0 0);
--foreground: oklch(0.9219 0 0);
--card: oklch(0.2686 0 0);
--card-foreground: oklch(0.9219 0 0);
--popover: oklch(0.2686 0 0);
--popover-foreground: oklch(0.9219 0 0);
--primary: oklch(0.7686 0.1647 70.0804);
--primary-foreground: oklch(0 0 0);
--secondary: oklch(0.2686 0 0);
--secondary-foreground: oklch(0.9219 0 0);
--muted: oklch(0.2686 0 0);
--muted-foreground: oklch(0.7155 0 0);
--accent: oklch(0.4732 0.1247 46.2007);
--accent-foreground: oklch(0.9243 0.1151 95.7459);
--destructive: oklch(0.6368 0.2078 25.3313);
--border: oklch(0.3715 0 0);
--input: oklch(0.3715 0 0);
--ring: oklch(0.7686 0.1647 70.0804);
--chart-1: oklch(0.8369 0.1644 84.4286);
--chart-2: oklch(0.6658 0.1574 58.3183);
--chart-3: oklch(0.4732 0.1247 46.2007);
--chart-4: oklch(0.5553 0.1455 48.9975);
--chart-5: oklch(0.4732 0.1247 46.2007);
--sidebar: oklch(0.1684 0 0);
--sidebar-foreground: oklch(0.9219 0 0);
--sidebar-primary: oklch(0.7686 0.1647 70.0804);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.4732 0.1247 46.2007);
--sidebar-accent-foreground: oklch(0.9243 0.1151 95.7459);
--sidebar-border: oklch(0.3715 0 0);
--sidebar-ring: oklch(0.7686 0.1647 70.0804);
--destructive-foreground: oklch(1 0 0);
--radius: 0.375rem;
--font-sans: Inter, sans-serif;
--font-serif: Source Serif 4, serif;
--font-mono: JetBrains Mono, monospace;
--shadow-color: hsl(0 0% 0%);
--shadow-opacity: 0.1;
--shadow-blur: 8px;
--shadow-spread: -1px;
--shadow-offset-x: 0px;
--shadow-offset-y: 4px;
--letter-spacing: 0em;
--spacing: 0.25rem;
--shadow-2xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05);
--shadow-xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05);
--shadow-sm: 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 1px 2px -2px hsl(0 0% 0% / 0.1);
--shadow: 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 1px 2px -2px hsl(0 0% 0% / 0.1);
--shadow-md: 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 2px 4px -2px hsl(0 0% 0% / 0.1);
--shadow-lg: 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 4px 6px -2px hsl(0 0% 0% / 0.1);
--shadow-xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 8px 10px -2px hsl(0 0% 0% / 0.1);
--shadow-2xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.25);
}
@layer base {
@ -190,4 +197,4 @@
.bg-brand {
background-color: #1a292f;
}
}
}

View file

@ -5,6 +5,7 @@ import {
Frame,
GalleryVerticalEnd,
HouseIcon,
SquareMousePointer,
SquareTerminal,
UsersIcon
} from 'lucide-react';
@ -47,7 +48,7 @@ const data = {
{
title: 'Servicos',
url: '#',
icon: UsersIcon,
icon: SquareMousePointer,
isActive: false,
items: [
{
@ -177,6 +178,34 @@ const data = {
title: 'Atos/Partes Tipos',
url: '/administrativo/atos/partes-tipos',
},
{
title: "Valores de Serviços",
url: "/administrativo/valores-de-servicos",
},
{
title: "Gramatica",
url: "/administrativo/gramatica",
},
{
title: "Cartório",
url: "/administrativo/cartorio",
},
{
title: "Financeiro/Periodo",
url: "/administrativo/financeiro/periodos",
},
{
title: "Financeiro/Emolumentos",
url: "/administrativo/financeiro/emolumentos",
},
{
title: "Selos/Grupos",
url: "/administrativo/selos/grupos",
},
{
title: "Financeiro/Cálculo Rápido",
url: "/administrativo/financeiro/calculo-rapido",
}
],
},
],

View file

@ -1,6 +1,15 @@
'use client';
import { ChevronsUpDown, LogOut, Sparkles } from 'lucide-react';
import {
ChevronsUpDown,
LogOut,
Moon,
Sparkles,
Sun,
} from 'lucide-react';
import { useTheme } from 'next-themes';
import { useCallback, useState } from 'react';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import {
DropdownMenu,
@ -17,11 +26,9 @@ import {
SidebarMenuItem,
useSidebar,
} from '@/components/ui/sidebar';
import GUsuarioAuthenticatedInterface from '@/shared/interfaces/GUsuarioAuthenticatedInterface';
import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog';
import { useGUsuarioLogoutHook } from '@/packages/administrativo/hooks/GUsuario/useGUsuarioLogoutHook';
import { use, useCallback, useState } from 'react';
import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog';
import GUsuarioAuthenticatedInterface from '@/shared/interfaces/GUsuarioAuthenticatedInterface';
export function NavUser({ user }: { user: GUsuarioAuthenticatedInterface }) {
// Hook para encerrar sessão
@ -32,19 +39,26 @@ export function NavUser({ user }: { user: GUsuarioAuthenticatedInterface }) {
const { isMobile } = useSidebar();
// Manipulação de formulário de confirmação
const handleConfirmOpen = useCallback(async () => {
// Tema (claro/escuro) - next-themes
const { theme, setTheme } = useTheme();
const isDark = theme === 'dark';
const handleConfirmOpen = useCallback(() => {
setIsConfirmOpen(true);
}, []);
const handleLogoutConfirm = useCallback(async () => {
const handleLogoutConfirm = useCallback(() => {
logoutUsuario();
}, []);
}, [logoutUsuario]);
const handleLogoutCancel = useCallback(async () => {
const handleLogoutCancel = useCallback(() => {
setIsConfirmOpen(false);
}, []);
const handleToggleTheme = useCallback(() => {
setTheme(isDark ? 'light' : 'dark');
}, [isDark, setTheme]);
if (!user) {
return 'Carregando...';
}
@ -61,13 +75,13 @@ export function NavUser({ user }: { user: GUsuarioAuthenticatedInterface }) {
>
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage src={user.sigla} alt={user.nome} />
<AvatarFallback className="rounded-lg">{user.sigla}</AvatarFallback>
<AvatarFallback className="rounded-lg">
{user.sigla}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.nome}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
@ -85,13 +99,13 @@ export function NavUser({ user }: { user: GUsuarioAuthenticatedInterface }) {
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage src={user.sigla} alt={user.nome} />
<AvatarFallback className="rounded-lg">{user.sigla}</AvatarFallback>
<AvatarFallback className="rounded-lg">
{user.sigla}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.nome}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
</div>
@ -100,16 +114,32 @@ export function NavUser({ user }: { user: GUsuarioAuthenticatedInterface }) {
<DropdownMenuSeparator />
<DropdownMenuGroup>
{/* Alternância de tema */}
<DropdownMenuItem
className="cursor-pointer"
onClick={handleToggleTheme}
>
{isDark ? (
<Sun className="mr-2 h-4 w-4" />
) : (
<Moon className="mr-2 h-4 w-4" />
)}
{isDark ? 'Modo claro' : 'Modo escuro'}
</DropdownMenuItem>
<DropdownMenuItem className="cursor-pointer">
<Sparkles />
<Sparkles className="mr-2 h-4 w-4" />
Configurações
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem className="cursor-pointer" onClick={() => handleConfirmOpen()}>
<LogOut />
<DropdownMenuItem
className="cursor-pointer"
onClick={handleConfirmOpen}
>
<LogOut className="mr-2 h-4 w-4" />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
@ -122,11 +152,11 @@ export function NavUser({ user }: { user: GUsuarioAuthenticatedInterface }) {
isOpen={isConfirmOpen}
title="Log-out"
description="Atenção"
message={`Deseja realmente encerrar a sessão ? Dados não salvos serão perdidos`}
message="Deseja realmente encerrar a sessão? Dados não salvos serão perdidos."
confirmText="Sim, sair"
cancelText="Cancelar"
onConfirm={() => handleLogoutConfirm()}
onCancel={() => handleLogoutCancel()}
onConfirm={handleLogoutConfirm}
onCancel={handleLogoutCancel}
/>
</>
);

View file

@ -0,0 +1,17 @@
"use client";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import * as React from "react";
export function ThemeProvider({ children }: { children: React.ReactNode }) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</NextThemesProvider>
);
}

View file

@ -0,0 +1,83 @@
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Separator } from "@/components/ui/separator"
const buttonGroupVariants = cva(
"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
{
variants: {
orientation: {
horizontal:
"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
vertical:
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
},
},
defaultVariants: {
orientation: "horizontal",
},
}
)
function ButtonGroup({
className,
orientation,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
return (
<div
role="group"
data-slot="button-group"
data-orientation={orientation}
className={cn(buttonGroupVariants({ orientation }), className)}
{...props}
/>
)
}
function ButtonGroupText({
className,
asChild = false,
...props
}: React.ComponentProps<"div"> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "div"
return (
<Comp
className={cn(
"bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function ButtonGroupSeparator({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="button-group-separator"
orientation={orientation}
className={cn(
"bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto",
className
)}
{...props}
/>
)
}
export {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
buttonGroupVariants,
}

193
src/components/ui/item.tsx Normal file
View file

@ -0,0 +1,193 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Separator } from "@/components/ui/separator"
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
role="list"
data-slot="item-group"
className={cn("group/item-group flex flex-col", className)}
{...props}
/>
)
}
function ItemSeparator({
className,
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="item-separator"
orientation="horizontal"
className={cn("my-0", className)}
{...props}
/>
)
}
const itemVariants = cva(
"group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
{
variants: {
variant: {
default: "bg-transparent",
outline: "border-border",
muted: "bg-muted/50",
},
size: {
default: "p-4 gap-4 ",
sm: "py-3 px-4 gap-2.5",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Item({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"div"> &
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "div"
return (
<Comp
data-slot="item"
data-variant={variant}
data-size={size}
className={cn(itemVariants({ variant, size, className }))}
{...props}
/>
)
}
const itemMediaVariants = cva(
"flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none group-has-[[data-slot=item-description]]/item:translate-y-0.5",
{
variants: {
variant: {
default: "bg-transparent",
icon: "size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4",
image:
"size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover",
},
},
defaultVariants: {
variant: "default",
},
}
)
function ItemMedia({
className,
variant = "default",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>) {
return (
<div
data-slot="item-media"
data-variant={variant}
className={cn(itemMediaVariants({ variant, className }))}
{...props}
/>
)
}
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-content"
className={cn(
"flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none",
className
)}
{...props}
/>
)
}
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-title"
className={cn(
"flex w-fit items-center gap-2 text-sm leading-snug font-medium",
className
)}
{...props}
/>
)
}
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="item-description"
className={cn(
"text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance",
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
className
)}
{...props}
/>
)
}
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-actions"
className={cn("flex items-center gap-2", className)}
{...props}
/>
)
}
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-header"
className={cn(
"flex basis-full items-center justify-between gap-2",
className
)}
{...props}
/>
)
}
function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-footer"
className={cn(
"flex basis-full items-center justify-between gap-2",
className
)}
{...props}
/>
)
}
export {
Item,
ItemMedia,
ItemContent,
ItemActions,
ItemGroup,
ItemSeparator,
ItemTitle,
ItemDescription,
ItemHeader,
ItemFooter,
}

View file

@ -1,13 +1,13 @@
'use client';
"use client"
import * as React from 'react';
import * as SeparatorPrimitive from '@radix-ui/react-separator';
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from '@/lib/utils';
import { cn } from "@/lib/utils"
function Separator({
className,
orientation = 'horizontal',
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
@ -17,12 +17,12 @@ function Separator({
decorative={decorative}
orientation={orientation}
className={cn(
'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
className,
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className
)}
{...props}
/>
);
)
}
export { Separator };
export { Separator }

View file

@ -1,12 +1,10 @@
'use client';
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, VariantProps } from 'class-variance-authority';
import { PanelLeftIcon } from 'lucide-react';
import * as React from 'react';
import { useIsMobile } from '@/hooks/use-mobile';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
@ -19,6 +17,8 @@ import {
} from '@/components/ui/sheet';
import { Skeleton } from '@/components/ui/skeleton';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { useIsMobile } from '@/hooks/use-mobile';
import { cn } from '@/lib/utils';
const SIDEBAR_COOKIE_NAME = 'sidebar_state';
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
@ -548,7 +548,7 @@ function SidebarMenuAction({
'peer-data-[size=lg]/menu-button:top-2.5',
'group-data-[collapsible=icon]:hidden',
showOnHover &&
'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',
'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',
className,
)}
{...props}
@ -690,5 +690,6 @@ export {
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
useSidebar
};

View file

@ -3,6 +3,20 @@
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react';
import React from 'react';
import { useGEmolumentoReadHook } from '@/app/(protected)/(cadastros)/cadastros/_hooks/g_emolumento/useGEmolumentoReadHook';
import { GEmolumentoReadInterface } from '@/app/(protected)/(cadastros)/cadastros/_interfaces/GEmolumentoReadInterface';
import { Button } from '@/components/ui/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react';
import React from 'react';
import { useGEmolumentoReadHook } from '@/app/(protected)/(cadastros)/cadastros/_hooks/g_emolumento/useGEmolumentoReadHook';
import { GEmolumentoReadInterface } from '@/app/(protected)/(cadastros)/cadastros/_interfaces/GEmolumentoReadInterface';
import { Button } from '@/components/ui/button';
@ -154,4 +168,28 @@ export default function GEmolumentoSelect({
</PopoverContent>
</Popover>
);
// Fecha o popover
setOpen(false);
}}
>
{/* Ícone de seleção (check) */ }
< CheckIcon
className = {
cn(
'mr-2 h-4 w-4',
String(field.value?.key ?? field.value) === String(item.emolumento_id)
? 'opacity-100'
: 'opacity-0',
)}
/>
{/* Nome formatado do emolumento */ }
{ GetCapitalize(item.descricao) }
</CommandItem >
))}
</CommandGroup >
</CommandList >
</Command >
</PopoverContent >
</Popover >
);
}

View file

@ -0,0 +1,151 @@
'use client';
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useGEmolumentoReadHook } from '@/app/(protected)/(cadastros)/cadastros/_hooks/g_emolumento/useGEmolumentoReadHook';
import { GEmolumentoReadInterface } from '@/app/(protected)/(cadastros)/cadastros/_interfaces/GEmolumentoReadInterface';
import { Button } from '@/components/ui/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/command';
import { FormControl } from '@/components/ui/form';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import GetCapitalize from '@/shared/actions/text/GetCapitalize';
// Tipagem das props do componente
interface GEmolumentoSelectProps {
sistema_id: number;
field: any;
onSelectChange?: (emolumento: { emolumento_id: number; descricao: string }) => void;
className?: string;
}
// Componente principal do select de emolumentos
export default function GEmolumentoServicoSelect({
sistema_id,
field,
onSelectChange,
className,
}: GEmolumentoSelectProps) {
const [open, setOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
// Define parâmetros de leitura para o hook que busca os emolumentos
const gEmolumentoReadParams: GEmolumentoReadInterface = { sistema_id, urlParams: { situacao: 'A' } };
const { gEmolumento = [], fetchGEmolumento } = useGEmolumentoReadHook();
/**
* Efeito para buscar os dados apenas uma vez.
*/
const loadData = useCallback(async () => {
if (gEmolumento.length) return;
setIsLoading(true);
await fetchGEmolumento(gEmolumentoReadParams);
setIsLoading(false);
}, [gEmolumento.length, fetchGEmolumento, gEmolumentoReadParams]);
useEffect(() => {
loadData();
}, [loadData]);
/**
* Memoriza o item selecionado para evitar reprocessamentos.
*/
const selected = useMemo(
() =>
gEmolumento.find(
(item) => String(item.emolumento_id) === String(field?.value?.id ?? ''),
),
[gEmolumento, field?.value],
);
/**
* Manipulador de seleção com verificação segura.
*/
const handleSelect = useCallback(
(item: any) => {
if (!field?.onChange) return;
const selectedValue = {
emolumento_id: Number(item.emolumento_id),
descricao: item.descricao,
sistema_id: item.sistema_id,
};
field.onChange(selectedValue);
if (onSelectChange) onSelectChange(selectedValue);
setOpen(false);
},
[field, onSelectChange],
);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<FormControl className="w-full">
<Button
variant="outline"
role="combobox"
aria-expanded={open}
disabled={isLoading}
className={cn(
'justify-between min-h-[2.5rem] w-full text-left break-words whitespace-normal',
className,
)}
>
{isLoading
? 'Carregando...'
: field.value?.descricao
? GetCapitalize(field.value.descricao)
: 'Selecione emolumento'}
<ChevronsUpDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent
align="start"
side="bottom"
className="w-[var(--radix-popover-trigger-width)] max-w-full p-0"
>
<Command>
<CommandInput placeholder="Buscar emolumentos..." disabled={isLoading} />
<CommandList>
<CommandEmpty>
{isLoading ? 'Carregando...' : 'Nenhum resultado encontrado.'}
</CommandEmpty>
<CommandGroup>
{gEmolumento.map((item) => (
<CommandItem
key={item.emolumento_id}
value={item.descricao?.toLowerCase() ?? ''}
onSelect={() => handleSelect(item)}
>
<CheckIcon
className={cn(
'mr-2 h-4 w-4',
String(field?.value?.emolumento_id ?? '') ===
String(item.emolumento_id)
? 'opacity-100'
: 'opacity-0',
)}
/>
{GetCapitalize(item.descricao ?? '')}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View file

@ -1,6 +1,6 @@
'use client';
import React, { useEffect } from 'react';
import { useEffect } from 'react';
import { Button } from '@/components/ui/button';
import {

View file

@ -1,6 +1,6 @@
'use client';
import { useEffect, useState, useCallback } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useGGramaticaDeleteHook } from '@/packages/administrativo/hooks/GGramatica/useGGramaticaDeleteHook';

View file

@ -2,8 +2,8 @@
import { DataTable } from '@/shared/components/dataTable/DataTable';
import GGramaticaColumns from './GGramaticaColumns';
import GGramaticaTableInterface from '../../interfaces/GGramatica/GGramaticaTableInterface';
import GGramaticaColumns from './GGramaticaColumns';
/**
* Componente principal da tabela de Naturezas

View file

@ -0,0 +1,125 @@
'use client';
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/command';
import { FormControl } from '@/components/ui/form';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { useGUsuarioIndexHook } from '@/packages/administrativo/hooks/GUsuario/useGUsuarioIndexHook';
import GUsuarioSelectInterface from '@/packages/administrativo/interfaces/GUsuario/GUsuarioSelectInterface';
import GetCapitalize from '@/shared/actions/text/GetCapitalize';
import GUsuarioIndexInterface from '../../interfaces/GUsuario/GusuarioIndexInterface';
export default function GUsuarioSelect({ field }: GUsuarioSelectInterface) {
const [open, setOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const { usuarios, fetchUsuarios } = useGUsuarioIndexHook();
/**
* Efeito para buscar os dados apenas uma vez.
* useCallback evita recriação desnecessária da função.
*/
const loadData = useCallback(async () => {
const urlParams = {
assina: 'S',
situacao: 'A'
}
const GUsuarioIndex: GUsuarioIndexInterface = {
urlParams: urlParams
}
if (usuarios?.length) return;
setIsLoading(true);
await fetchUsuarios(GUsuarioIndex);
setIsLoading(false);
}, [usuarios?.length, fetchUsuarios]);
useEffect(() => {
loadData();
}, [loadData]);
/**
* Memoriza o bairro selecionado para evitar reprocessamentos.
*/
const selected = useMemo(
() => usuarios?.find((b) => String(b.usuario_id) === String(field?.value ?? '')),
[usuarios, field?.value],
);
/**
* Manipulador de seleção com verificação segura.
*/
const handleSelect = useCallback(
(bairroId: string | number) => {
if (!field?.onChange) return;
field.onChange(bairroId);
setOpen(false);
},
[field],
);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<FormControl className="w-full">
<Button
variant="outline"
role="combobox"
aria-expanded={open}
disabled={isLoading}
className="justify-between"
>
{isLoading
? 'Carregando...'
: selected
? GetCapitalize(selected.nome_completo)
: 'Selecione...'}
<ChevronsUpDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="Buscar bairro..." disabled={isLoading} />
<CommandList>
<CommandEmpty>
{isLoading ? 'Carregando...' : 'Nenhum resultado encontrado.'}
</CommandEmpty>
<CommandGroup>
{usuarios?.map((item) => (
<CommandItem
key={item.usuario_id}
value={item?.nome_completo?.toLowerCase() ?? ''}
onSelect={() => handleSelect(item.usuario_id)}
>
<CheckIcon
className={cn(
'mr-2 h-4 w-4',
String(field?.value ?? '') === String(item.usuario_id)
? 'opacity-100'
: 'opacity-0',
)}
/>
{GetCapitalize(item.nome_completo ?? '')}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View file

@ -1,7 +1,7 @@
'use client';
import { HouseIcon, IdCardIcon, UserIcon } from 'lucide-react';
import React, { useEffect } from 'react';
import { useEffect } from 'react';
import { Button } from '@/components/ui/button';
import {

View file

@ -0,0 +1,161 @@
import { ColumnDef } from '@tanstack/react-table';
import { ArrowUpDownIcon } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { FormatCPF } from '@/shared/actions/CPF/FormatCPF';
import { FormatDateTime } from '@/shared/actions/dateTime/FormatDateTime';
import { FormatPhone } from '@/shared/actions/phone/FormatPhone';
import GetNameInitials from '@/shared/actions/text/GetNameInitials';
import empty from '@/shared/actions/validations/empty';
import TPessoaInterface from '../../interfaces/TPessoa/TPessoaInterface';
/**
* Função para criar a definição das colunas da tabela
*/
export function TPessoaTableFormColumnsDialog(setSelectedTPessoa: React.Dispatch<React.SetStateAction<any | null>>): ColumnDef<TPessoaInterface>[] {
return [
{
id: 'select',
header: '',
cell: ({ row, table }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => {
// Limpa todas as seleções antes de selecionar uma nova
table.resetRowSelection();
row.toggleSelected(!!value);
setSelectedTPessoa(value ? row.original : null);
}}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
// ID
{
accessorKey: 'pessoa_id',
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
# <ArrowUpDownIcon className="ml-1 h-4 w-4" />
</Button>
),
cell: ({ row }) => Number(row.getValue('pessoa_id')),
enableSorting: false,
},
// Nome / Email / Foto
{
id: 'nome_completo',
accessorFn: (row) => row,
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Nome / Email <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => {
const pessoa = row.original;
return (
<div className="flex items-center gap-3">
{/* Foto ou Iniciais */}
<div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-gray-200">
{pessoa.foto ? (
<img
src={pessoa.foto}
alt={pessoa.nome || 'Avatar'}
className="h-full w-full object-cover"
/>
) : (
<span className="text-sm font-medium text-gray-700">
{GetNameInitials(pessoa.nome)}
</span>
)}
</div>
{/* Nome e Email */}
<div>
<div className="font-semibold text-gray-900 capitalize">{pessoa.nome || '-'}</div>
<div className="text-sm text-gray-500">
{empty(pessoa.email) ? 'Email não informado' : pessoa.email}
</div>
</div>
</div>
);
},
sortingFn: (a, b) =>
(a.original.nome?.toLowerCase() || '').localeCompare(b.original.nome?.toLowerCase() || ''),
},
// CPF
{
accessorKey: 'cpf_cnpj',
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
CPF <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => FormatCPF(row.getValue('cpf_cnpj')),
},
// Telefone
{
accessorKey: 'telefone',
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Telefone <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => FormatPhone(row.getValue('telefone')),
},
// Cidade / UF
{
id: 'cidade_uf',
accessorFn: (row) => `${row.cidade}/${row.uf}`,
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Cidade/UF <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => <span>{row.getValue('cidade_uf') || '-'}</span>,
sortingFn: (a, b) =>
`${a.original.cidade}/${a.original.uf}`
.toLowerCase()
.localeCompare(`${b.original.cidade}/${b.original.uf}`.toLowerCase()),
},
// Data de cadastro
{
accessorKey: 'data_cadastro',
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Cadastro <ArrowUpDownIcon className="ml-1 h-4 w-4 cursor-pointer" />
</Button>
),
cell: ({ row }) => FormatDateTime(row.getValue('data_cadastro')),
sortingFn: 'datetime',
},
];
}

View file

@ -0,0 +1,128 @@
'use client';
import { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog';
import { useTPessoaFisicaIndexHook } from '@/packages/administrativo/hooks/TPessoa/TPessoaFisica/useTPessoaFisicaIndexHook';
import { useTPessoaJuridicaIndexHook } from '@/packages/administrativo/hooks/TPessoa/TPessoaJuridica/useTPessoaJuridicaIndexHook';
import TPessoaInterface from '@/packages/administrativo/interfaces/TPessoa/TPessoaInterface';
import TPessoaTableFormInterface from '@/packages/administrativo/interfaces/TPessoa/TPessoaTableFormInterface';
import { DataTable } from '@/shared/components/dataTable/DataTable';
import LoadingButton from '@/shared/components/loadingButton/LoadingButton';
import { TPessoaTableFormColumnsDialog } from './TPessoaTableFormColumnsDialog';
export default function TPessoaTableFormDialog({
isOpen,
tipoPessoa,
onClose,
onSave,
buttonIsLoading,
}: TPessoaTableFormInterface) {
const { tPessoaFisica, fetchTPessoaFisica } = useTPessoaFisicaIndexHook();
const { tPessoaJuridica, fetchTPessoaJuridica } = useTPessoaJuridicaIndexHook();
const [pessoas, setPessoas] = useState<any>()
const [selectedTPessoa, setSelectedTPessoa] = useState<TPessoaInterface | null>(null);
// Executa o Hook de Acordo com o tipo de pessoa informado
const loadData = async (tipoPessoa: string) => {
switch (tipoPessoa) {
case "F":
await fetchTPessoaFisica();
break;
case "J":
await fetchTPessoaJuridica();
break;
}
};
// Atualiza a variavel de pessoa quando tiver alteração na variavel de pessoas fisicas
useEffect(() => {
setPessoas(tPessoaFisica)
}, [tPessoaFisica])
// Atualiza a variavel de pessoa quando tiver alteração na variavel de pessoas juridicas
useEffect(() => {
setPessoas(tPessoaJuridica)
}, [tPessoaJuridica])
// Executa o hook correspondente ao tipo de pessoa, sempre que o tipo pessoa mudar
useEffect(() => {
// Verifica se o tipo pessoa esta preenchido
if (tipoPessoa) {
// Dispara o carregamento de informações
loadData(tipoPessoa);
}
}, [tipoPessoa]);
const columns = TPessoaTableFormColumnsDialog(setSelectedTPessoa);
return (
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) onClose(null, false);
}}
>
<DialogContent className="max-h-[70vh] w-full max-w-full overflow-auto p-6 sm:max-w-4xl md:max-w-6xl lg:max-w-6xl">
<DialogHeader>
<DialogTitle>Pessoa</DialogTitle>
<DialogDescription>Busque a pessoa desejada</DialogDescription>
</DialogHeader>
<div className="max-h-[50vh] overflow-y-auto">
<DataTable
data={pessoas}
columns={columns}
filterColumn="nome_completo"
filterPlaceholder="Buscar por nome ou email..."
/>
</div>
{/* Rodapé do Dialog */}
<DialogFooter className="mt-4">
<DialogClose asChild>
<Button
variant="outline"
type="button"
onClick={() => onClose(null, false)}
className="cursor-pointer"
>
Cancelar
</Button>
</DialogClose>
<LoadingButton
text="Selecionar"
textLoading="Aguarde..."
loading={buttonIsLoading}
type="button"
onClick={() => { onSave(selectedTPessoa); onClose(null, false); }}
/>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View file

@ -0,0 +1,129 @@
import { FingerprintIcon, WebcamIcon } from 'lucide-react';
import {
memo,
useCallback,
useMemo,
useState
} from 'react';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle
} from '@/components/ui/item';
import TPessoaCartaoForm from '@/packages/servicos/components/TPessoaCartao/TPessoaCartaoForm';
import GetNameInitials from '@/shared/actions/text/GetNameInitials';
import WebCamDialog from '@/shared/components/webcam/WebCamDialog';
import { useFingerTechCaptureHook } from '@/shared/hooks/FingerTech/useFingerTechCaptureHook';
import TPessoaTableFormSubviewInterface from '../../interfaces/TPessoa/TPessoaTableFormSubviewInterface';
function TPessoaTableFormSubview({
item_index,
data,
params,
form,
}: TPessoaTableFormSubviewInterface) {
const [isWebCamOpenDialog, setIsWebCamOpenDialog] = useState(false)
const { base64, captureFingerTech } = useFingerTechCaptureHook();
// Chama o leitor biométrico
const handleBiometria = useCallback(() => {
console.log(captureFingerTech())
}, [])
// Define a classe do botão de biometria com base no status, sem estado extra
const biometriaButtonClass = useMemo(() => {
switch (1) {
case 0:
// Amarelo (aviso)
return 'bg-amber-100 text-amber-700 border border-amber-300 hover:bg-amber-200 hover:text-amber-800';
case 1:
// Verde discreto
return 'bg-green-100 text-green-700 border border-green-300 hover:bg-green-200 hover:text-green-800';
case 2:
// Vermelho (erro)
return 'bg-red-100 text-red-700 border border-red-300 hover:bg-red-200 hover:text-red-800';
default:
return '';
}
}, []);
return (
<div>
<Item variant="outline">
<ItemMedia>
<Avatar className="size-10">
<AvatarImage src={``} />
<AvatarFallback>
{GetNameInitials(data.pessoa?.nome)}
</AvatarFallback>
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>
{data.pessoa?.cpf_cnpj} - {data.pessoa?.nome}
</ItemTitle>
<ItemDescription>
{data.pessoa?.email || 'Email não informado'}
</ItemDescription>
{params
.filter((param) => Number(param.valor) === data.servico.servico_tipo_id)
.map((param) => (
<TPessoaCartaoForm
form={form}
index={item_index}
key={param.config_id}
/>
))}
</ItemContent>
<ItemActions>
{data.servico.requer_biometria === 'S' && (
<Button
type="button"
size="icon-lg"
variant="outline"
className={`rounded-full cursor-pointer ${biometriaButtonClass}`}
aria-label="Capturar biometria"
onClick={() => { handleBiometria() }}
>
<FingerprintIcon />
</Button>
)}
{data.servico.requer_biometria && (
<Button
type="button"
size="icon-lg"
variant="outline"
className="rounded-full cursor-pointer"
aria-label="Capturar imagem"
onClick={() => { setIsWebCamOpenDialog(true) }}
>
<WebcamIcon />
</Button>
)}
</ItemActions>
</Item>
{isWebCamOpenDialog && (
<WebCamDialog
isOpen={isWebCamOpenDialog}
onClose={() => { setIsWebCamOpenDialog(false) }}
onSave={() => { }}
key={item_index}
/>
)}
</div>
);
}
// Memo para evitar re-renderizações desnecessárias da subview
export default memo(TPessoaTableFormSubview);

View file

@ -1,6 +1,6 @@
'use client';
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import {
@ -17,11 +17,11 @@ import { ResetFormIfData } from '@/shared/actions/form/ResetFormIfData';
import { DataTable } from '@/shared/components/dataTable/DataTable';
import LoadingButton from '@/shared/components/loadingButton/LoadingButton';
import TPessoasRepresentanteFormColumns from './TPessoasRepresentanteFormColumns';
import { useTPessoaFisicaIndexHook } from '../../hooks/TPessoa/TPessoaFisica/useTPessoaFisicaIndexHook';
import { useTPessoaRepresentanteFormHook } from '../../hooks/TPessoaRepresentante/useTPessoaRepresentanteFormHook';
import TPessoaRepresentanteInterface from '../../interfaces/TPessoaRepresentante/TPessoaRepresentanteInterface';
import TPessoaRepresentanteFormInterface from '../../interfaces/TPessoaRepresentante/TPessoaRepresentnateFormInterface';
import TPessoasRepresentanteFormColumns from './TPessoasRepresentanteFormColumns';
export default function TPessoaRepresentanteForm({
isOpen,

View file

@ -0,0 +1,138 @@
'use client';
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/command';
import { FormControl } from '@/components/ui/form';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { useTServicoTipoReadHook } from '@/packages/administrativo/hooks/TServicoTipo/useTServicoTipoReadHook';
import TServicoTipoSelectInterface from '@/packages/administrativo/interfaces/TServicoTipo/TServicoTipoSelectInterface';
import GetCapitalize from '@/shared/actions/text/GetCapitalize';
import TServicoTipoIndexInteface from '../../interfaces/TServicoTipo/TServicoTipoIndexInteface';
export default function TServicoTipoSelect({ field }: TServicoTipoSelectInterface) {
const [open, setOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const { tServicoTipo = [], fetchTServicoTipo } = useTServicoTipoReadHook();
/**
* Efeito para buscar os dados apenas uma vez.
*/
const loadData = useCallback(async () => {
const TServicoTipoIndex: TServicoTipoIndexInteface = {
urlParams: {
situacao: 'A'
}
}
if (tServicoTipo.length) return;
setIsLoading(true);
await fetchTServicoTipo(TServicoTipoIndex);
setIsLoading(false);
}, [tServicoTipo.length, fetchTServicoTipo]);
useEffect(() => {
loadData();
}, [loadData]);
/**
* Item selecionado (comparando por ID)
*/
const selected = useMemo(
() =>
tServicoTipo.find(
(item) => String(item.servico_tipo_id) === String(field?.value?.servico_tipo_id ?? ''),
),
[tServicoTipo, field?.value],
);
/**
* Manipulador de seleção
*/
const handleSelect = useCallback(
(item: any) => {
if (!field?.onChange) return;
const selectedValue = {
servico_tipo_id: Number(item.servico_tipo_id),
descricao: item.descricao,
tipo_item: item.tipo_item,
tipo_pessoa: item.tipo_pessoa,
requer_biometria: item.requer_biometria,
requer_cpf: item.requer_cpf,
servico_caixa_id: item.servico_caixa_id,
selar: item.selar,
};
field.onChange(selectedValue);
setOpen(false);
},
[field],
);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<FormControl className="w-full">
<Button
variant="outline"
role="combobox"
aria-expanded={open}
disabled={isLoading}
className="justify-between"
>
{isLoading
? 'Carregando...'
: field.value?.descricao
? GetCapitalize(field.value.descricao)
: 'Selecione...'}
<ChevronsUpDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="Buscar tipo de serviço..." disabled={isLoading} />
<CommandList>
<CommandEmpty>
{isLoading ? 'Carregando...' : 'Nenhum resultado encontrado.'}
</CommandEmpty>
<CommandGroup>
{tServicoTipo.map((item) => (
<CommandItem
key={item.servico_tipo_id}
value={item.descricao?.toLowerCase() ?? ''}
onSelect={() => handleSelect(item)}
>
<CheckIcon
className={cn(
'mr-2 h-4 w-4',
String(field?.value?.servico_tipo_id ?? '') ===
String(item.servico_tipo_id)
? 'opacity-100'
: 'opacity-0',
)}
/>
{GetCapitalize(item.descricao ?? '')}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View file

@ -0,0 +1,17 @@
import GCalculoInterface from '@/packages/administrativo/interfaces/GCalculo/GCalculoInterface';
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
import API from '@/shared/services/api/Api';
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import ApiResponseInterface from '@/shared/services/api/interfaces/ApiResponseInterface';
async function executeGCalculoCalcularData(data: GCalculoInterface): Promise<ApiResponseInterface> {
const api = new API();
return api.send({
method: Methods.POST,
endpoint: `administrativo/g_calculo/servico/`,
body: data,
});
}
export const GCalculoServico = withClientErrorHandler(executeGCalculoCalcularData);

View file

@ -3,13 +3,17 @@
import API from '@/shared/services/api/Api';
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
export default async function GUsuarioIndexData() {
import GUsuarioIndexInterface from '../../interfaces/GUsuario/GusuarioIndexInterface';
export default async function GUsuarioIndexData(data: GUsuarioIndexInterface) {
const api = new API();
const response = await api.send({
method: Methods.GET,
endpoint: `administrativo/g_usuario/`,
endpoint: `administrativo/g_usuario?${new URLSearchParams(data.urlParams).toString()}`,
});
console.log(response)
return response;
}

View file

@ -5,7 +5,7 @@ import API from '@/shared/services/api/Api'; //
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum'; //
// Importa a interface tipada que define a estrutura dos dados do tipo de serviço
import TServicoTipoInterface from '../../_interfaces/TServicoTipoInterface'; // Alterado de GCidadeInterface
import TServicoTipoInterface from '../../../../app/(protected)/(cadastros)/cadastros/_interfaces/TServicoTipoInterface'; // Alterado de GCidadeInterface
// Importa função que encapsula chamadas assíncronas e trata erros automaticamente
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler'; //

View file

@ -1,21 +1,23 @@
// Importa o serviço de API que será utilizado para realizar requisições HTTP
import API from '@/shared/services/api/Api'; //
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
import API from '@/shared/services/api/Api';
// Importa o enum que contém os métodos HTTP disponíveis (GET, POST, PUT, DELETE)
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum'; //
import TServicoTipoIndexInteface from '../../interfaces/TServicoTipo/TServicoTipoIndexInteface';
// Importa função que encapsula chamadas assíncronas e trata erros automaticamente
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler'; //
// Função assíncrona que implementa a lógica de buscar todos os tipos de serviço (GET)
async function executeTServicoTipoIndexData() {
async function executeTServicoTipoIndexData(data: TServicoTipoIndexInteface) {
// Instancia o cliente da API para enviar a requisição
const api = new API(); //
// Executa a requisição para a API com o método apropriado e o endpoint da tabela t_servico_tipo
return await api.send({
method: Methods.GET, // GET listar todos os itens
endpoint: `administrativo/t_servico_tipo/`, // Endpoint atualizado
endpoint: `administrativo/t_servico_tipo/?${new URLSearchParams(data.urlParams).toString()}`,
});
}

View file

@ -5,7 +5,7 @@ import API from '@/shared/services/api/Api'; //
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum'; //
// Importa a interface tipada que define a estrutura dos dados do tipo de serviço
import TServicoTipoInterface from '../../_interfaces/TServicoTipoInterface'; // Alterado de GCidadeInterface
import TServicoTipoInterface from '../../../../app/(protected)/(cadastros)/cadastros/_interfaces/TServicoTipoInterface'; // Alterado de GCidadeInterface
// Importa função que encapsula chamadas assíncronas e trata erros automaticamente
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler'; //

View file

@ -2,13 +2,12 @@
import API from '@/shared/services/api/Api'; //
// Importa o esquema de validação de dados para tipos de serviço
import { TServicoTipoFormValues } from '../../_schemas/TServicoTipoSchema';
import { TServicoTipoFormValues } from '../../../../app/(protected)/(cadastros)/cadastros/_schemas/TServicoTipoSchema';
// Importa o enum que contém os métodos HTTP disponíveis (GET, POST, PUT, DELETE)
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum'; //
// Importa a interface tipada que define a estrutura dos dados do tipo de serviço
import TServicoTipoInterface from '../../_interfaces/TServicoTipoInterface'; // Interface alterada
// Importa função que encapsula chamadas assíncronas e trata erros automaticamente
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler'; //

View file

@ -2,18 +2,21 @@
import { useState } from 'react';
import Usuario from '@/packages/administrativo/interfaces/GUsuario/GUsuarioInterface';
import { useResponse } from '@/shared/components/response/ResponseContext';
import Usuario from '../../interfaces/GUsuario/GUsuarioInterface';
import GUsuarioIndexInterface from '../../interfaces/GUsuario/GusuarioIndexInterface';
import GUsuarioIndex from '../../services/GUsuario/GUsuarioIndex';
export const useGUsuarioIndexHook = () => {
const { setResponse } = useResponse();
const [usuarios, setUsuarios] = useState<Usuario[] | null>(null);
const fetchUsuarios = async () => {
const response = await GUsuarioIndex();
const fetchUsuarios = async (data: GUsuarioIndexInterface) => {
const response = await GUsuarioIndex(data);
setUsuarios(response.data);

View file

@ -15,6 +15,8 @@ export const useTPessoaFisicaIndexHook = () => {
setTPessoa(response.data);
setResponse(response);
return response.data
};
return { tPessoaFisica, fetchTPessoaFisica };

View file

@ -1,10 +1,10 @@
import { useResponse } from '@/shared/components/response/ResponseContext'; // Contexto global para gerenciar respostas da API
// Interface tipada do tipo de serviço
import TServicoTipoInterface from '../../_interfaces/TServicoTipoInterface';
import TServicoTipoInterface from '../../../../app/(protected)/(cadastros)/cadastros/_interfaces/TServicoTipoInterface';
// Função que Edit o tipo de serviço via API
import { TServicoTipoEditData } from '../../_data/TServicoTipo/TServicoTipoEditData';
import { TServicoTipoEditData } from '../../data/TServicoTipo/TServicoTipoEditData';
// Hook customizado para remoção de tipos de serviço
export const useTServicoTipoEditHook = () => {

View file

@ -1,14 +1,17 @@
import { useResponse } from '@/shared/components/response/ResponseContext'; // Contexto global para gerenciar respostas da API
import { useState } from 'react';
import { useResponse } from '@/shared/components/response/ResponseContext'; // Contexto global para gerenciar respostas da API
// Serviço que busca a lista de tipos de serviço (TServicoTipoIndexService)
import { TServicoTipoIndexService } from '../../_services/t_servico_tipo/TServicoTipoIndexService';
import TServicoTipoInterface from '../../../../app/(protected)/(cadastros)/cadastros/_interfaces/TServicoTipoInterface';
import TServicoTipoIndexInteface from '../../interfaces/TServicoTipo/TServicoTipoIndexInteface';
import { TServicoTipoIndexService } from '../../services/TServicoTipo/TServicoTipoIndexService';
// Interface tipada do tipo de serviço
import TServicoTipoInterface from '../../_interfaces/TServicoTipoInterface';
// Hook customizado para leitura de dados de tipos de serviço
export const useTServicoTipoReadHook = () => {
// Hook do contexto de resposta para feedback global (alertas, mensagens etc.)
const { setResponse } = useResponse();
@ -16,9 +19,9 @@ export const useTServicoTipoReadHook = () => {
const [tServicoTipo, setTServicoTipo] = useState<TServicoTipoInterface[]>([]);
// Função assíncrona que busca os dados dos tipos de serviço
const fetchTServicoTipo = async () => {
const fetchTServicoTipo = async (data: TServicoTipoIndexInteface) => {
// Chama o serviço responsável por consultar a API
const response = await TServicoTipoIndexService();
const response = await TServicoTipoIndexService(data);
// Atualiza o estado local com os dados retornados
setTServicoTipo(response.data);

View file

@ -1,10 +1,10 @@
import { useResponse } from '@/shared/components/response/ResponseContext'; // Contexto global para gerenciar respostas da API
// Interface tipada do tipo de serviço
import TServicoTipoInterface from '../../_interfaces/TServicoTipoInterface';
import TServicoTipoInterface from '../../../../app/(protected)/(cadastros)/cadastros/_interfaces/TServicoTipoInterface';
// Função que remove o tipo de serviço via API
import { TServicoTipoRemoveData } from '../../_data/TServicoTipo/TServicoTipoRemoveData';
import { TServicoTipoRemoveData } from '../../data/TServicoTipo/TServicoTipoRemoveData';
// Hook customizado para remoção de tipos de serviço
export const useTServicoTipoRemoveHook = () => {

View file

@ -1,11 +1,11 @@
import { useState } from 'react';
import { useResponse } from '@/shared/components/response/ResponseContext';
import { useState } from 'react';
// Interface tipada do tipo de serviço
import TServicoTipoInterface from '../../_interfaces/TServicoTipoInterface';
import TServicoTipoInterface from '../../../../app/(protected)/(cadastros)/cadastros/_interfaces/TServicoTipoInterface';
// Serviço que salva os dados do tipo de serviço
import { TServicoTipoSaveService } from '../../_services/t_servico_tipo/TServicoTipoSaveService';
import { TServicoTipoSaveService } from '../../services/TServicoTipo/TServicoTipoSaveService';
export const useTServicoTipoSaveHook = () => {
const { setResponse } = useResponse();

View file

@ -0,0 +1,4 @@
export default interface GCalculoServicoInterface {
valor_documento?: number;
emolumento_id?: number;
}

View file

@ -0,0 +1,3 @@
export default interface GUsuarioIndexInterface {
urlParams: object
}

View file

@ -0,0 +1,6 @@
export default interface GTBairroSelectInterface {
field?: {
value?: number | string;
onChange?: (value: string | number) => void;
};
}

View file

@ -0,0 +1,7 @@
export default interface TPessoaTableFormSubviewInterface {
params: any;
servico: any;
selectedTPessoa: any;
form: any;
index: number
}

View file

@ -0,0 +1,8 @@
export default interface TPessoaTableFormInterface {
isOpen: boolean;
tipoPessoa: string;
onClose: (item: null, isFormStatus: boolean) => void;
onSave: (data: any) => void;
buttonIsLoading: boolean;
}

View file

@ -0,0 +1,11 @@
import { UseFormReturn } from "react-hook-form";
import { TServicoPedidoFormValues } from "@/packages/servicos/schemas/TServicoPedido/TServicoPedidoFormSchema";
import GConfigInterface from "@/shared/interfaces/GConfigInterface";
export default interface TPessoaTableFormSubviewInterface {
item_index: number,
data: any,
params: GConfigInterface[],
form: UseFormReturn<TServicoPedidoFormValues>;
}

View file

@ -0,0 +1,3 @@
export default interface TServicoTipoIndexInteface {
urlParams: object
}

View file

@ -0,0 +1,6 @@
export default interface TServicoTipoSelectInterface {
field?: {
value?: number | string;
onChange?: (value: string | number) => void;
};
}

View file

@ -0,0 +1,46 @@
import { GCalculoServico } from "@/packages/administrativo/data/GCalculo/GCalculoServicoData";
import GCalculoServicoInterface from "@/packages/administrativo/interfaces/GCalculo/GCalculoServicoInterface";
import TServicoItemPedidoAddInterface from "@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoAddInterface";
import { withClientErrorHandler } from "@/shared/actions/withClientErrorHandler/withClientErrorHandler";
async function executeGCalculoServicoService(payload: GCalculoServicoInterface, data: TServicoItemPedidoAddInterface) {
const response = await GCalculoServico(payload);
if (response.status == 404 || response.status == 400) {
return {
'status': response.status,
'message': 'Erro ao processar dados'
};
}
const item = {
emolumento_id: response.data.emolumento_id,
emolumento_item_id: response.data.emolumento_item_id ?? null,
servico_tipo_id: data.servico_tipo.servico_tipo_id ?? 0,
tipo_item: data.servico_tipo.tipo_item ?? "",
descricao: data.servico_tipo.descricao ?? "",
tabela: data.servico_tipo?.descricao ?? "",
situacao: "F",
qtd: 1,
valor: response.data.valor_total ?? 0,
emolumento: response.data.valor_emolumento ?? 0,
fundesp: response.data.valor_fundos ?? 0,
taxa_judiciaria: response.data.taxa_judiciaria ?? 0,
valor_iss: response.data.valor_iss ?? 0,
pessoa_id: data?.pessoa?.pessoa_id ?? null,
subview: {
servico: data.servico_tipo,
pessoa: data.pessoa,
}
};
return item;
}
export const GCalculoServicoService = withClientErrorHandler(executeGCalculoServicoService);

View file

@ -1,9 +1,10 @@
'use server';
import GUsuarioIndexData from '../../data/GUsuario/GUsuarioIndexData';
import GUsuarioIndexInterface from '../../interfaces/GUsuario/GusuarioIndexInterface';
export default async function GUsuarioIndex() {
const response = await GUsuarioIndexData();
export default async function GUsuarioIndex(data: GUsuarioIndexInterface) {
const response = await GUsuarioIndexData(data);
return response;
}

View file

@ -1,10 +1,10 @@
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
// Função que envolve qualquer ação assíncrona para capturar e tratar erros do cliente
import { TServicoTipoEditData } from '../../_data/TServicoTipo/TServicoTipoEditData';
import { TServicoTipoEditData } from '../../data/TServicoTipo/TServicoTipoEditData';
// Função que remove os dados do tipo de serviço via API
import TServicoTipoInterface from '../../_interfaces/TServicoTipoInterface';
import TServicoTipoInterface from '../../../../app/(protected)/(cadastros)/cadastros/_interfaces/TServicoTipoInterface';
// Interface tipada do tipo de serviço
// Função assíncrona que executa a remoção de um tipo de serviço

View file

@ -1,13 +1,14 @@
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
// Função que envolve qualquer ação assíncrona para capturar e tratar erros do cliente
import { TServicoTipoIndexData } from '../../_data/TServicoTipo/TServicoTipoIndexData';
import { TServicoTipoIndexData } from '../../data/TServicoTipo/TServicoTipoIndexData';
import TServicoTipoIndexInteface from '../../interfaces/TServicoTipo/TServicoTipoIndexInteface';
// Função que retorna os dados da lista de tipos de serviço (chamada à API ou mock)
// Função assíncrona que executa a chamada para buscar os dados dos tipos de serviço
async function executeTServicoTipoIndexService() {
async function executeTServicoTipoIndexService(data: TServicoTipoIndexInteface) {
// Chama a função que retorna os dados dos tipos de serviço
const response = await TServicoTipoIndexData();
const response = await TServicoTipoIndexData(data);
// Retorna a resposta para o chamador
return response;

View file

@ -1,10 +1,10 @@
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
// Função que envolve qualquer ação assíncrona para capturar e tratar erros do cliente
import { TServicoTipoRemoveData } from '../../_data/TServicoTipo/TServicoTipoRemoveData';
import { TServicoTipoRemoveData } from '../../data/TServicoTipo/TServicoTipoRemoveData';
// Função que remove os dados do tipo de serviço via API
import TServicoTipoInterface from '../../_interfaces/TServicoTipoInterface';
import TServicoTipoInterface from '../../../../app/(protected)/(cadastros)/cadastros/_interfaces/TServicoTipoInterface';
// Interface tipada do tipo de serviço
// Função assíncrona que executa a remoção de um tipo de serviço

View file

@ -2,10 +2,10 @@
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
// Função que salva os dados do tipo de serviço via API (ou mock)
import { TServicoTipoSaveData } from '../../_data/TServicoTipo/TServicoTipoSaveData';
import { TServicoTipoSaveData } from '../../data/TServicoTipo/TServicoTipoSaveData';
// Interface tipada do tipo de serviço
import TServicoTipoInterface from '../../_interfaces/TServicoTipoInterface';
import TServicoTipoInterface from '../../../../app/(protected)/(cadastros)/cadastros/_interfaces/TServicoTipoInterface';
// Função assíncrona que executa o salvamento de um tipo de serviço
async function executeTServicoTipoSaveService(data: TServicoTipoInterface) {

View file

@ -0,0 +1,15 @@
interface HandleAddItemInterface {
servicoSelecionado: any;
emolumentoSelecionado: any;
}
export default async function HandleAddItemAction({ servicoSelecionado, emolumentoSelecionado }: HandleAddItemInterface) {
if (!servicoSelecionado || !emolumentoSelecionado) return false;
return {
servico_tipo: servicoSelecionado,
emolumento: emolumentoSelecionado,
};
}

View file

@ -0,0 +1,24 @@
export default function HandleSelectTServicoTipoAction({ servico, emolumento, onOpenPessoaForm, onAddItem }: HandleSelectTServicoTipoInterface) {
if (!servico || !emolumento) {
return {
'status': 422,
'detail': 'Serviço e emolumento devem ser selecionados'
};
};
if (servico?.tipo_pessoa) {
onOpenPessoaForm(servico.tipo_pessoa);
} else {
onAddItem();
}
}

View file

@ -0,0 +1,8 @@
export default interface HandleSelectTServicoTipoInterface {
servico: any;
emolumento: any;
onOpenPessoaForm: (tipoPessoa: string) => void,
onAddItem: () => void
}

View file

@ -0,0 +1,31 @@
import TServicoItemPedidoAddInterface from "@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoAddInterface";
export default function TServicoPedidoItemPreparePayload(data: TServicoItemPedidoAddInterface) {
data.qtd = 1
data.valor_documento = 0
// Verifica dados obrigatórios de serviço e emolumento
if (!data?.emolumento?.emolumento_id || !data?.servico_tipo?.servico_tipo_id) {
return {
status: 400,
message: 'Dados informados inválidos: serviço ou emolumento não informado.'
};
}
// Valida sistema_id (padrão 2, mas ainda assim precisa ser válido)
if (!data?.emolumento.sistema_id || data.servico_tipo.servico_tipo_id <= 0) {
return {
status: 400,
message: 'Sistema inválido ou não informado.'
};
}
return {
sistema_id: data.emolumento.sistema_id,
valor_documento: data.valor_documento,
quantidade: data.qtd,
emolumento_id: data.emolumento.emolumento_id,
};
}

View file

@ -0,0 +1,88 @@
import { useState } from "react";
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import TPessoaCartaoFormInterface from "@/packages/servicos/interfaces/TPessoaCartao/TPessoaCartaoFormInterface";
import { parseNumberInput } from "@/shared/actions/form/parseNumberInput";
import ConfirmacaoSelect from "@/shared/components/confirmacao/ConfirmacaoSelect";
export default function TPessoaCartaoForm({ index, form }: TPessoaCartaoFormInterface) {
const [cartaoAutomatico, setCartaoAutomatico] = useState(true);
return (
<div>
<div className="flex items-center space-x-2 mt-4">
<Switch
id={`cartao-automatico-${index}`}
checked={cartaoAutomatico}
onCheckedChange={(checked) => {
setCartaoAutomatico(checked);
form.setValue(`itens.${index}.cartao_automatico`, checked);
}}
/>
<Label htmlFor={`cartao-automatico-${index}`}>Cartão Automático</Label>
</div>
{!cartaoAutomatico && (
<div className="grid w-full grid-cols-12 gap-4 mt-4">
{/* Gerar selo */}
<div className="col-span-12 sm:col-span-12 md:col-span-4">
<FormField
control={form.control}
name={`itens.${index}.cartao_selar`}
render={({ field }) => (
<FormItem>
<FormLabel>Gerar Selo</FormLabel>
<ConfirmacaoSelect field={field} />
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Nº Cartão */}
<div className="col-span-12 sm:col-span-12 md:col-span-4">
<FormField
control={form.control}
name={`itens.${index}.cartao_numero`}
render={({ field }) => (
<FormItem>
<FormLabel> cartão</FormLabel>
<FormControl>
<Input {...field}
type="number"
onChange={(e) => field.onChange(parseNumberInput(e))}
defaultValue={field.value ?? ""}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Data de Abertura */}
<div className="col-span-12 sm:col-span-12 md:col-span-4">
<FormField
control={form.control}
name={`itens.${index}.cartao_data`}
render={({ field }) => (
<FormItem>
<FormLabel>Data de Abertura</FormLabel>
<FormControl>
<Input {...field}
type="date"
defaultValue={field.value ?? ""}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
)}
</div>
)
}

View file

@ -1,78 +0,0 @@
import { ColumnDef } from '@tanstack/react-table';
import { EllipsisIcon, PencilIcon, Trash2Icon } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import GetCapitalize from '@/shared/actions/text/GetCapitalize';
import { SortableHeader } from '@/shared/components/dataTable/SortableHeader';
import TServicoItemPedidoInterface from '../../interfaces/TServicoItemPedido/TServicoItemPedidoIntefarce';
export default function TServicoItemPedidoColumns(
onEdit: (item: TServicoItemPedidoInterface, isEditingFormStatus: boolean) => void,
onDelete: (item: TServicoItemPedidoInterface, isEditingFormStatus: boolean) => void,
): ColumnDef<TServicoItemPedidoInterface>[] {
return [
// ID
{
accessorKey: 'gramatica_id',
header: ({ column }) => SortableHeader('ID', column),
cell: ({ row }) => Number(row.getValue('gramatica_id')),
enableSorting: true,
},
// Descrição
{
accessorKey: 'palavra',
header: ({ column }) => SortableHeader('Palavra', column),
cell: ({ row }) => GetCapitalize(String(row.getValue('palavra') || '')),
},
// Ações
{
id: 'actions',
header: 'Ações',
cell: ({ row }) => {
const natureza = row.original;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<EllipsisIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="left" align="start">
<DropdownMenuGroup>
<DropdownMenuItem onSelect={() => onEdit(natureza, true)}>
<PencilIcon className="mr-2 h-4 w-4" />
Editar
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="text-red-600"
onSelect={() => onDelete(natureza, true)}
>
<Trash2Icon className="mr-2 h-4 w-4" />
Remover
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
},
enableSorting: false,
enableHiding: false,
},
];
}

View file

@ -1,110 +0,0 @@
'use client';
import { useEffect } from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { ResetFormIfData } from '@/shared/actions/form/ResetFormIfData';
import LoadingButton from '@/shared/components/loadingButton/LoadingButton';
import { useTServicoItemPedidoFormHook } from '../../hooks/TServicoItemPedido/useTServicoItemPedidoFormHook';
import { TServicoItemPedidoFormInterface } from '../../interfaces/TServicoItemPedido/TServicoItemPedidoFormInterface';
/**
* Formulário de cadastro/edição de Natureza
* Baseado nos campos da tabela G_NATUREZA
*/
export default function TServicoItemPedidoForm({
isOpen,
data,
onClose,
onSave,
buttonIsLoading,
}: TServicoItemPedidoFormInterface) {
const form = useTServicoItemPedidoFormHook({});
// Atualiza o formulário quando recebe dados para edição
useEffect(() => {
ResetFormIfData(form, data);
}, [data, form]);
function onError(error: any) {
console.log('Erro no formulário:', error);
}
return (
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) onClose(null, false);
}}
>
<DialogContent className="w-full max-w-full p-6 sm:max-w-3xl md:max-w-2xl lg:max-w-2xl">
<DialogHeader>
<DialogTitle className="text-lg sm:text-xl">Formulário de Gramática</DialogTitle>
<DialogDescription className="text-muted-foreground text-sm">
Formulário de Gramática
</DialogDescription>
</DialogHeader>
{/* Formulário principal */}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSave, onError)} className="space-y-6">
{/* GRID MOBILE FIRST */}
<div className="grid w-full grid-cols-12 gap-4">
{/* Palavra */}
<div className="col-span-12 sm:col-span-6 md:col-span-12">
<FormField
control={form.control}
name="desc_complementar"
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Palavra</FormLabel>
<FormControl>
<Input
{...field}
type="text"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
{/* Rodapé */}
<DialogFooter className="mt-6 flex flex-col justify-end gap-2 sm:flex-row">
<DialogClose asChild>
<Button variant="outline" type="button">
Cancelar
</Button>
</DialogClose>
<LoadingButton
text="Salvar"
textLoading="Salvando..."
type="submit"
loading={buttonIsLoading}
/>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
}

View file

@ -0,0 +1,116 @@
'use client';
import { Minus, Plus } from 'lucide-react';
import React, { memo } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '@/components/ui/table';
import TPessoaTableFormSubview from '@/packages/administrativo/components/TPessoa/TPessoaTableFormSubview';
import TServicoItemPedidoFormTableInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoFormTableInterface';
import GetCapitalize from '@/shared/actions/text/GetCapitalize';
function TServicoItemPedidoFormTableComponent({
data,
form,
params
}: TServicoItemPedidoFormTableInterface) {
return (
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>
Serviço / Tabela
</TableHead>
<TableHead>
Emolumento
</TableHead>
<TableHead>
Tx. Judiciária
</TableHead>
<TableHead>
Fundesp 21%
</TableHead>
<TableHead>
ISS 5%
</TableHead>
<TableHead>
Total
</TableHead>
<TableHead className="text-center">Qtd.</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data?.length ? (
data.map((item, index) => {
return (
<React.Fragment key={`fragment-${index}`}>
{/* Linha principal */}
<TableRow key={`row-${index}`} className="cursor-pointer hover:bg-gray-50">
<TableCell>
<div className="flex items-center gap-3">
<div>
<div className="font-semibold text-gray-900 capitalize">
{GetCapitalize(item.descricao)}
</div>
<div className="text-sm text-gray-500">
{GetCapitalize(item.tabela)}
</div>
</div>
</div>
</TableCell>
<TableCell>R$ {item.emolumento ?? '---'}</TableCell>
<TableCell>R$ {item.taxa_judiciaria ?? '---'}</TableCell>
<TableCell>R$ {item.fundesp ?? '---'}</TableCell>
<TableCell>R$ {item.valor_iss ?? '---'}</TableCell>
<TableCell>R$ {item.valor ?? '---'}</TableCell>
<TableCell>
<div className="flex items-center gap-1 justify-center">
<Button type="button" size="icon" variant="outline" className="bg-white border h-8 w-8 rounded-lg" >
<Minus className="h-4 w-4" />
</Button>
<Input type="number" className="h-8 text-center px-1 w-12" />
<Button type="button" size="icon" variant="outline" className="bg-white border h-8 w-8 rounded-lg" >
<Plus className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
{/* SubView */}
{item.subview && (
<TableRow className="bg-gray-50">
<TableCell colSpan={7} className="p-4">
<TPessoaTableFormSubview
item_index={Number(item.index)}
data={item.subview}
params={params}
form={form}
/>
</TableCell>
</TableRow>
)}
</React.Fragment>
);
})
) : (
<TableRow>
<TableCell colSpan={7} className="text-center py-4">
Nenhum item encontrado.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}
export const TServicoItemPedidoFormTable = memo(TServicoItemPedidoFormTableComponent);

View file

@ -1,161 +0,0 @@
'use client';
import { useCallback, useEffect, useState } from 'react';
import { useTServicoItemPedidoDeleteHook } from '@/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoDeleteHook';
import { useTServicoItemPedidoIndexHook } from '@/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoIndexHook';
import { useTServicoItemPedidoSaveHook } from '@/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoSaveHook';
import TServicoItemPedidoInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoIntefarce';
import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog';
import { useConfirmDialog } from '@/shared/components/confirmDialog/useConfirmDialog';
import Loading from '@/shared/components/loading/loading';
import Header from '@/shared/components/structure/Header';
import TServicoItemPedidoForm from './TServicoItemPedidoForm';
import TServicoItemPedidoTable from './TServicoItemPedidoTable';
export default function TServicoItemPedidoIndex() {
// Controle de estado do botão
const [buttonIsLoading, setButtonIsLoading] = useState(false);
// Hooks para leitura e salvamento
const { TServicoItemPedido, indexTServicoItemPedido } = useTServicoItemPedidoIndexHook();
const { saveTServicoItemPedido } = useTServicoItemPedidoSaveHook();
const { deleteTServicoItemPedido } = useTServicoItemPedidoDeleteHook();
// Estados
const [selectedData, setSelectedData] = useState<TServicoItemPedidoInterface | null>(null);
const [isFormOpen, setIsFormOpen] = useState(false);
// Estado para saber qual item será deletado
const [itemToDelete, setItemToDelete] = useState<TServicoItemPedidoInterface | null>(null);
/**
* Hook do modal de confirmação
*/
const { isOpen: isConfirmOpen, openDialog: openConfirmDialog, handleCancel } = useConfirmDialog();
/**
* Abre o formulário no modo de edição ou criação
*/
const handleOpenForm = useCallback((data: TServicoItemPedidoInterface | null) => {
setSelectedData(data);
setIsFormOpen(true);
}, []);
/**
* Fecha o formulário e limpa o andamento selecionado
*/
const handleCloseForm = useCallback(() => {
setSelectedData(null);
setIsFormOpen(false);
}, []);
/**
* Salva os dados do formulário
*/
const handleSave = useCallback(
async (formData: TServicoItemPedidoInterface) => {
// Coloca o botão em estado de loading
setButtonIsLoading(true);
// Aguarda salvar o registro
await saveTServicoItemPedido(formData);
// Remove o botão em estado de loading
setButtonIsLoading(false);
// Atualiza a lista de dados
indexTServicoItemPedido();
},
[saveTServicoItemPedido, indexTServicoItemPedido, handleCloseForm],
);
/**
* Quando o usuário clica em "remover" na tabela
*/
const handleConfirmDelete = useCallback(
(item: TServicoItemPedidoInterface) => {
// Define o item atual para remoção
setItemToDelete(item);
// Abre o modal de confirmação
openConfirmDialog();
},
[openConfirmDialog],
);
/**
* Executa a exclusão de fato quando o usuário confirma
*/
const handleDelete = useCallback(async () => {
// Protege contra null
if (!itemToDelete) return;
// Executa o Hook de remoção
await deleteTServicoItemPedido(itemToDelete);
// Atualiza a lista
await indexTServicoItemPedido();
// Limpa o item selecionado
setItemToDelete(null);
// Fecha o modal
handleCancel();
}, [itemToDelete, indexTServicoItemPedido, handleCancel]);
/**
* Busca inicial dos dados
*/
useEffect(() => {
indexTServicoItemPedido();
}, []);
/**
* Tela de loading enquanto carrega os dados
*/
if (TServicoItemPedido?.length == 0) {
return <Loading type={2} />;
}
return (
<div>
{/* Cabeçalho */}
<Header
title={'Gramatica'}
description={'Gramatica'}
buttonText={'Nova palavra'}
buttonAction={() => {
handleOpenForm(null);
}}
/>
{/* Tabela de andamentos */}
<TServicoItemPedidoTable data={TServicoItemPedido} onEdit={handleOpenForm} onDelete={handleConfirmDelete} />
{/* Modal de confirmação */}
{isConfirmOpen && (
<ConfirmDialog
isOpen={isConfirmOpen}
title="Confirmar exclusão"
description="Atenção"
message={`Deseja realmente excluir o valor "${itemToDelete?.valor}"?`}
confirmText="Sim, excluir"
cancelText="Cancelar"
onConfirm={handleDelete}
onCancel={handleCancel}
/>
)}
{/* Formulário de criação/edição */}
{isFormOpen && (
<TServicoItemPedidoForm
isOpen={isFormOpen}
data={selectedData}
onClose={handleCloseForm}
onSave={handleSave}
buttonIsLoading={buttonIsLoading}
/>
)}
</div>
);
}

View file

@ -0,0 +1,209 @@
'use client';
import { BookmarkX, IdCardIcon, MoreHorizontalIcon, RotateCcwIcon, TicketIcon } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { ButtonGroup } from '@/components/ui/button-group';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import { useTServicoItemPedidoCancelarHook } from '@/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoCancelarHook';
import TServicoItemPedidoInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoIntefarce';
import TServicoItemPedidoListInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoListInterface';
import FormatMoney from '@/shared/actions/money/FormatMoney';
import { useResponse } from '@/shared/components/response/ResponseContext';
import { ServicosPedidosSituacoesBadge } from '@/shared/components/servicosPedidosSituacoes/ServicosPedidosSituacoesBadge';
import { useTServicoItemPedidoAtivarHook } from '../../hooks/TServicoItemPedido/useTServicoItemPedidoAtivarHook';
export default function TServicoItemPedidoList({ items, openConfirmDialog }: TServicoItemPedidoListInterface) {
const { setResponse } = useResponse();
const { cancelarTServicoItemPedido } = useTServicoItemPedidoCancelarHook()
const { ativarTServicoItemPedido } = useTServicoItemPedidoAtivarHook()
const [localItems, setLocalItems] = useState(items || [])
useEffect(() => {
setLocalItems(items || [])
}, [items])
const handleSituacaoTServicoItemPedido = useCallback(async (item: any) => {
const servicoItemPedido: TServicoItemPedidoInterface = {
servico_itempedido_id: item.servico_itempedido_id
}
let response: any = null
switch (item.situacao) {
case 'C':
response = await ativarTServicoItemPedido(servicoItemPedido)
break
case 'F':
response = await cancelarTServicoItemPedido(servicoItemPedido)
break
default:
setResponse({
status: 422,
error: 'Situação',
detail: 'Situação não tratada'
})
break
}
if (response) {
setLocalItems((prev) =>
prev.map((i) =>
i.servico_itempedido_id === item.servico_itempedido_id ? { ...i, situacao: response.situacao } : i
)
)
}
}, [cancelarTServicoItemPedido, setLocalItems])
return (
<Card className="card-border">
<CardHeader>
<CardTitle className="text-2xl font-semibold">
Itens: {localItems?.length}
</CardTitle>
</CardHeader>
<CardContent>
{/* Altura máxima + scroll vertical */}
<div className="space-y-4 max-h-[60vh] overflow-y-auto pr-1">
{localItems?.map((item) => {
const isCancelado = item.situacao === 'C'
const actionLabel = isCancelado ? 'Ativar Item' : 'Estornar Item'
const confirmTitle = isCancelado ? 'Ativação de Item' : 'Estorno de Item'
const confirmMessage = isCancelado ? `Deseja realmente ativar o item #${item.servico_itempedido_id}?` : `Deseja realmente estornar o item #${item.servico_itempedido_id}?`
const confirmButton = isCancelado ? 'Sim, ativar item' : 'Sim, estornar item'
const actionIcon = isCancelado ? <RotateCcwIcon /> : <BookmarkX />
return (
<div key={item.servico_itempedido_id} className="bg-cart-item border-cart-border rounded-lg border p-4">
{/* Descrição */}
<div className="flex-1">
<h3 className="text-foreground line-clamp-2 text-sm lg:text-base font-bold">
{item.descricao} de {item.nome} - <ServicosPedidosSituacoesBadge situacao={item.situacao} />
</h3>
<h6 className="text-foreground line-clamp-2 text-sm lg:text-base">
# {item.servico_itempedido_id}
</h6>
</div>
{/* Valores (grid compacto) */}
<div className="grid grid-cols-6 gap-3 mt-4">
<div>
<div className="text-xs text-muted-foreground">Emolumento</div>
<div className="font-semibold">{FormatMoney(item.emolumento)}</div>
</div>
<div>
<div className="text-xs text-muted-foreground">Tx. Judiciária</div>
<div className="font-semibold">{FormatMoney(item.taxa_judiciaria)}</div>
</div>
<div>
<div className="text-xs text-muted-foreground">ISS</div>
<div className="font-semibold">{FormatMoney(item.valor_iss)}</div>
</div>
<div>
<div className="text-xs text-muted-foreground">Fundesp</div>
<div className="font-semibold">{FormatMoney(item.fundesp)}</div>
</div>
<div>
<div className="text-xs text-muted-foreground">Total</div>
<div className="font-semibold">{FormatMoney(item.valor)}</div>
</div>
<div className="text-end">
<ButtonGroup>
<ButtonGroup>
<Button variant="outline">
Ações
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" aria-label="More Options">
<MoreHorizontalIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52">
<DropdownMenuGroup>
<DropdownMenuItem className='cursor-pointer'>
<TicketIcon />
Imprimir Etiqueta
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuGroup>
<DropdownMenuItem className='cursor-pointer'>
<IdCardIcon />
Imprimir Cartão
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
className={`cursor-pointer ${isCancelado ? 'text-green-600' : 'text-destructive'}`}
onClick={() =>
openConfirmDialog({
title: confirmTitle,
description: 'Confirmação necessária',
message: confirmMessage,
confirmText: confirmButton,
cancelText: 'Cancelar',
onConfirm: () => {
handleSituacaoTServicoItemPedido(item)
},
})
}
>
{actionIcon} {actionLabel}
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</ButtonGroup>
</div>
</div>
</div>
)
})}
</div>
</CardContent>
</Card>
);
}

View file

@ -1,23 +0,0 @@
'use client';
import { DataTable } from '@/shared/components/dataTable/DataTable';
import TServicoItemPedidoColumns from './TServicoItemPedidoColumns';
import TServicoItemPedidoTableInterface from '../../interfaces/TServicoItemPedido/TServicoItemPedidoTableInterface';
/**
* Componente principal da tabela de Naturezas
*/
export default function TServicoItemPedidoTable({ data, onEdit, onDelete }: TServicoItemPedidoTableInterface) {
const columns = TServicoItemPedidoColumns(onEdit, onDelete);
return (
<div>
<DataTable
data={data}
columns={columns}
filterColumn="palavra"
filterPlaceholder="Buscar por descrição da natureza..."
/>
</div>
);
}

View file

@ -1,5 +1,6 @@
import { ColumnDef } from '@tanstack/react-table';
import { EllipsisIcon, PencilIcon, Trash2Icon } from 'lucide-react';
import { EllipsisIcon, EyeIcon, PencilIcon, Trash2Icon } from 'lucide-react';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import {
@ -10,8 +11,13 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { FormatCPF } from '@/shared/actions/CPF/FormatCPF';
import { FormatDateTime } from '@/shared/actions/dateTime/FormatDateTime';
import GetCapitalize from '@/shared/actions/text/GetCapitalize';
import GetNameInitials from '@/shared/actions/text/GetNameInitials';
import empty from '@/shared/actions/validations/empty';
import { SortableHeader } from '@/shared/components/dataTable/SortableHeader';
import { ServicosPedidosSituacoesBadge } from '@/shared/components/servicosPedidosSituacoes/ServicosPedidosSituacoesBadge';
import TServicoPedidoInterface from '../../interfaces/TServicoPedido/TServicoPedidoInterface';
@ -22,17 +28,75 @@ export default function TServicoPedidoColumns(
return [
// ID
{
accessorKey: 'gramatica_id',
accessorKey: 'servico_pedido_id',
header: ({ column }) => SortableHeader('ID', column),
cell: ({ row }) => Number(row.getValue('gramatica_id')),
cell: ({ row }) => Number(row.getValue('servico_pedido_id')),
enableSorting: true,
},
// Descrição
// Data Pedido
{
accessorKey: 'palavra',
header: ({ column }) => SortableHeader('Palavra', column),
cell: ({ row }) => GetCapitalize(String(row.getValue('palavra') || '')),
accessorKey: 'data_pedido',
header: ({ column }) => SortableHeader('Data', column),
cell: ({ row }) => FormatDateTime(row.getValue('data_pedido')),
},
// Apresentante
{
accessorKey: 'apresentante',
header: ({ column }) => SortableHeader('Apresentante', column),
cell: ({ row }) => {
const data = row.original;
return (
<div className="flex items-center gap-3">
{/* Foto ou Iniciais */}
<div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-gray-200">
<span className="text-sm font-medium text-gray-700">
{GetNameInitials(data.apresentante)}
</span>
</div>
<div>
<div className="font-semibold text-gray-900 capitalize">{data.apresentante || '-'}</div>
<div className="text-sm text-gray-500">
{empty(data.cpfcnpj_apresentante) ? '---' : FormatCPF(data.cpfcnpj_apresentante)}
</div>
</div>
</div>
);
},
sortingFn: (a, b) =>
(a.original.apresentante?.toLowerCase() || '').localeCompare(b.original.apresentante?.toLowerCase() || ''),
},
// Situação
{
accessorKey: 'situacao',
header: ({ column }) => SortableHeader('Situação', column),
cell: ({ row }) => {
return (
<ServicosPedidosSituacoesBadge situacao={row.getValue('situacao') || ''} />
)
},
},
// Valor Pedido
{
accessorKey: 'valor_pedido',
header: ({ column }) => SortableHeader('Total', column),
cell: ({ row }) => {
return (
<div className='font-semibold'>
R$ {row.getValue('valor_pedido') || '---'}
</div>
);
},
},
// Usuário
{
accessorKey: 'login',
header: ({ column }) => SortableHeader('Operador', column),
cell: ({ row }) => GetCapitalize(String(row.getValue('login') || '')),
},
// Ações
@ -40,8 +104,7 @@ export default function TServicoPedidoColumns(
id: 'actions',
header: 'Ações',
cell: ({ row }) => {
const natureza = row.original;
const servicoPedido = row.original;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@ -49,20 +112,23 @@ export default function TServicoPedidoColumns(
<EllipsisIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="left" align="start">
<DropdownMenuGroup>
<DropdownMenuItem onSelect={() => onEdit(natureza, true)}>
<PencilIcon className="mr-2 h-4 w-4" />
Editar
<DropdownMenuItem asChild>
<Link href={`/servicos/balcao/detalhes/${row.getValue('servico_pedido_id')}`}>
<EyeIcon className="mr-2 h-4 w-4" />
Detalhes
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="text-red-600"
onSelect={() => onDelete(natureza, true)}
>
<DropdownMenuItem asChild>
<Link href={`/servicos/balcao/pedido/${row.getValue('servico_pedido_id')}`}>
<PencilIcon className="mr-2 h-4 w-4" />
Editar
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={() => onDelete(servicoPedido, true)}>
<Trash2Icon className="mr-2 h-4 w-4" />
Remover
</DropdownMenuItem>

View file

@ -0,0 +1,268 @@
'use client';
import { BookmarkX, CalendarIcon, ReceiptText, RotateCcwIcon } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import TServicoItemPedidoList from '@/packages/servicos/components/TServicoItemPedido/TServicoItemPedidoList';
import TServicoPedidoDetailsPagamento from '@/packages/servicos/components/TServicoPedido/TServicoPedidoDetailsPagamento';
import { useTServicoItemPedidoIndexHook } from '@/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoIndexHook';
import { useTServicoPedidoShowHook } from '@/packages/servicos/hooks/TServicoPedido/useTServicoPedidoShowHook';
import TServicoPedidoInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoInterface';
import { FormatCPF } from '@/shared/actions/CPF/FormatCPF';
import { FormatDateTime } from '@/shared/actions/dateTime/FormatDateTime';
import GetCapitalize from '@/shared/actions/text/GetCapitalize';
import GetNameInitials from '@/shared/actions/text/GetNameInitials';
import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog';
import { useResponse } from '@/shared/components/response/ResponseContext';
import { useTServicoPedidoAtivarHook } from '../../hooks/TServicoPedido/useTServicoPedidoAtivarHook';
import { useTServicoPedidoCancelarHook } from '../../hooks/TServicoPedido/useTServicoPedidoCancelarHook';
export default function TServicoPedidoDetails({ servico_pedido_id }: TServicoPedidoInterface) {
const { setResponse } = useResponse();
const { ativarTServicoPedido } = useTServicoPedidoAtivarHook()
const { cancelarTServicoPedido } = useTServicoPedidoCancelarHook()
const { TServicoItemPedido, indexTServicoItemPedido } = useTServicoItemPedidoIndexHook()
const { TServicoPedido, setTServicoPedido, showTServicoPedido } = useTServicoPedidoShowHook()
const handleSituacaoTServicoPedido = useCallback(async (pedido: any) => {
const servicoPedido: TServicoPedidoInterface = {
servico_pedido_id: pedido.servico_pedido_id
}
let response: any = null
switch (pedido.situacao) {
case 'C':
response = await ativarTServicoPedido(servicoPedido)
break
case 'F':
response = await cancelarTServicoPedido(servicoPedido)
break
default:
setResponse({
status: 422,
error: 'Situação',
detail: 'Situação não tratada'
})
break
}
if (response) {
pedido.situacao = response.situacao
setTServicoPedido(pedido)
}
}, [cancelarTServicoPedido])
const [confirmDialog, setConfirmDialog] = useState({
isOpen: false,
title: '',
description: '',
message: '',
confirmText: 'Confirmar',
cancelText: 'Cancelar',
onConfirm: () => { },
onCancel: () => { },
})
// 🔹 Função utilitária para abrir o dialog dinamicamente
const openConfirmDialog = ({
title,
description,
message,
confirmText = 'Confirmar',
cancelText = 'Cancelar',
onConfirm,
onCancel,
}) => {
setConfirmDialog({
isOpen: true,
title,
description,
message,
confirmText,
cancelText,
onConfirm: () => {
onConfirm?.()
setConfirmDialog((prev) => ({ ...prev, isOpen: false }))
},
onCancel: () => {
onCancel?.()
setConfirmDialog((prev) => ({ ...prev, isOpen: false }))
},
})
}
const TServicoPedidoShowData = useCallback(async () => {
const servicoPedido: TServicoPedidoInterface = {
servico_pedido_id: servico_pedido_id
}
const response = await showTServicoPedido(servicoPedido)
if (response.servico_pedido_id) {
TServicoPedidoItemIndexData(response.servico_pedido_id)
}
})
const TServicoPedidoItemIndexData = useCallback(async (servico_pedido_id: number) => {
const servicoPedido: TServicoPedidoInterface = {
servico_pedido_id: servico_pedido_id
}
await indexTServicoItemPedido(servicoPedido)
})
const handleCancelServicoPedidoOpenDialog = useCallback((state: boolean) => {
setIsCancelServicoPedidoDialogOpen(state)
}, [])
useEffect(() => {
TServicoPedidoShowData()
}, [])
const isCancelado = TServicoPedido?.situacao === 'C'
const actionLabel = isCancelado ? 'Ativar Pedido' : 'Estornar Pedido'
const actionIcon = isCancelado ? <RotateCcwIcon /> : <BookmarkX />
return (
<div className="relative h-full flex flex-col px-2 sm:px-4 py-2 sm:py-4 md:px-6 container mx-auto">
<h3 className='text-4xl font-bold mb-4'>
Pedido: #{TServicoPedido?.servico_pedido_id}
</h3>
{/* Main */}
<div className="container mx-auto h-full">
<div className="flex flex-col lg:flex-row gap-4">
{/* Left column */}
<div className="flex flex-col flex-auto gap-4">
<TServicoItemPedidoList
items={TServicoItemPedido}
/>
<TServicoPedidoDetailsPagamento
situacao={TServicoPedido?.situacao}
items={TServicoItemPedido}
/>
</div>
{/* Right column (sidebar) */}
<div className="lg:w-[320px] xl:w-[420px] flex flex-col gap-4">
<Card role="presentation" className="card-border">
<CardHeader>
<CardTitle className="text-2xl font-semibold">
Apresentante
</CardTitle>
</CardHeader>
<CardContent>
{/* Header com avatar e link */}
<div className="flex items-center gap-2">
{/* Foto ou Iniciais */}
<div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-gray-200">
<span className="text-sm font-medium text-gray-700">
{GetNameInitials(TServicoPedido?.apresentante)}
</span>
</div>
<div className="min-w-0">
<div className="font-bold truncate">
{GetCapitalize(TServicoPedido?.apresentante)}
</div>
<div className="font-light truncate">
{FormatCPF(String(TServicoPedido?.cpfcnpj_apresentante))}
</div>
</div>
</div>
<Separator className="my-5" />
<div className="flex items-center gap-2">
<CalendarIcon className="opacity-70 size-5" />
<span className="truncate">
{FormatDateTime(TServicoPedido?.data_pedido)}
</span>
</div>
</CardContent>
</Card>
<Card role="presentation" className="card-border">
<CardHeader>
<CardTitle className="text-2xl font-semibold">
Operador
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center gap-2">
{/* Foto ou Iniciais */}
<div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-gray-200">
<span className="text-sm font-medium text-gray-700">
{GetNameInitials(TServicoPedido?.login)}
</span>
</div>
<div className="min-w-0">
<div className="font-bold truncate">
{GetCapitalize(TServicoPedido?.login)}
</div>
<div className="font-light truncate">
{GetCapitalize(TServicoPedido?.funcao)}
</div>
</div>
</div>
</CardContent>
</Card>
<Card className="card-border">
<CardHeader>
<CardTitle className="text-2xl font-semibold">
Controles
</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-2">
<Button
className="w-full cursor-pointer"
variant={`outline`}
onClick={() =>
openConfirmDialog({
title: 'Estorno do pedido',
description: 'Confirmação necessária',
message: 'Deseja continuar com o estorno ?',
confirmText: 'Sim, continuar',
cancelText: 'Cancelar',
onConfirm: () => {
handleSituacaoTServicoPedido(TServicoPedido)
},
})
}>
{actionIcon} {actionLabel}
</Button>
<Button className="w-full cursor-pointer">
<ReceiptText /> Imprimir Recibo
</Button>
</CardContent>
</Card>
</div>
</div>
</div>
{/* Confirma o cancelamento do pedido */}
<ConfirmDialog
isOpen={confirmDialog.isOpen}
title={confirmDialog.title}
description={confirmDialog.description}
message={confirmDialog.message}
confirmText={confirmDialog.confirmText}
cancelText={confirmDialog.cancelText}
onConfirm={confirmDialog.onConfirm}
onCancel={confirmDialog.onCancel}
/>
</div >
)
}

View file

@ -0,0 +1,85 @@
'use client';
import * as React from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import FormatMoney from '@/shared/actions/money/FormatMoney';
import { ServicosPedidosSituacoesBadge } from '@/shared/components/servicosPedidosSituacoes/ServicosPedidosSituacoesBadge';
import TServicoPedidoDetailsPagamentoInterface from '../../interfaces/TServicoPedido/TServicoPedidoDetailsPagamentoInterface';
export default function TServicoPedidoDetailsPagamento({
situacao,
items,
}: TServicoPedidoDetailsPagamentoInterface) {
// Somas por tipo de valor
const { emolumento, taxa_judiciaria, valor_iss, fundesp } = React.useMemo(() => {
return (items ?? []).reduce(
(acc, item) => {
if (item.situacao === 'F') {
acc.emolumento += item.emolumento;
acc.taxa_judiciaria += item.taxa_judiciaria;
acc.valor_iss += item.valor_iss;
acc.fundesp += item.fundesp;
}
return acc;
},
{ emolumento: 0, taxa_judiciaria: 0, valor_iss: 0, fundesp: 0 }
);
}, [items]);
// Total exibido = soma dos quatro componentes
const total = emolumento + taxa_judiciaria + valor_iss + fundesp;
return (
<Card className="card-border">
<CardHeader>
<CardTitle className="text-2xl font-semibold">
Pagamento <ServicosPedidosSituacoesBadge situacao={situacao} />
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Emolumento</span>
<span className="font-medium">{FormatMoney(emolumento)}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Tx. Judiciária</span>
<span className="font-medium">{FormatMoney(taxa_judiciaria)}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-muted-foreground">ISS</span>
<span className="font-medium">{FormatMoney(valor_iss)}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Fundesp</span>
<span className="font-medium">{FormatMoney(fundesp)}</span>
</div>
<Separator className="border-cart-border" />
<div className="flex items-center justify-between text-xl">
<span className="text-muted-foreground font-semibold">Total</span>
<span className="font-semibold">{FormatMoney(total)}</span>
</div>
</div>
</CardContent>
</Card>
);
}

View file

@ -1,110 +1,726 @@
'use client';
import { useEffect } from 'react';
import { CreditCard, Package, UserSquare2 } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
Card,
CardContent,
CardHeader,
CardTitle
} from '@/components/ui/card';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
FormMessage
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { ResetFormIfData } from '@/shared/actions/form/ResetFormIfData';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import GEmolumentoServicoSelect from '@/packages/administrativo/components/GEmolumento/GEmolumentoServicoSelect';
import GUsuarioSelect from '@/packages/administrativo/components/GUsuario/GUsuarioSelect';
import TPessoaTableFormDialog from '@/packages/administrativo/components/TPessoa/TPessoaTableFormDialog';
import TServicoTipoSelect from '@/packages/administrativo/components/TServicoTipo/TServicoTipoSelect';
import TPessoaInterface from '@/packages/administrativo/interfaces/TPessoa/TPessoaInterface';
import HandleSelectTServicoTipoAction from '@/packages/servicos/actions/TServicoPedido/HandleSelectTServicoTipoAction';
import { TServicoItemPedidoFormTable } from '@/packages/servicos/components/TServicoItemPedido/TServicoItemPedidoFormTable';
import { useTServicoItemPedidoCalculoHook } from '@/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoCalculoHook';
import { useTServicoItemPedidoIndexHook } from '@/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoIndexHook';
import { useTServicoItemPedidoLocalAddHook } from '@/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoLocalAddHook';
import { useTServicoPedidoFormHook } from '@/packages/servicos/hooks/TServicoPedido/useTServicoPedidoFormHook';
import { useTServicoPedidoLoadParamsHook } from '@/packages/servicos/hooks/TServicoPedido/useTServicoPedidoLoadParamsHook';
import { useTServicoPedidoSaveHook } from '@/packages/servicos/hooks/TServicoPedido/useTServicoPedidoSaveHook';
import { useTServicoPedidoShowHook } from '@/packages/servicos/hooks/TServicoPedido/useTServicoPedidoShowHook';
import TServicoItemPedidoAddInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoAddInterface';
import { TServicoPedidoFormInterface } from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoFormInterface';
import TServicoPedidoInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoInterface';
import { FormatCPFCNPJForm } from '@/shared/actions/CPF/FormatCPFCNPJForm';
import { UnmaskCPFCNPJForm } from '@/shared/actions/CPF/UnmaskCPFCNPJForm';
import { parseNumberInput } from '@/shared/actions/form/parseNumberInput';
import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog';
import LoadingButton from '@/shared/components/loadingButton/LoadingButton';
import { useResponse } from '@/shared/components/response/ResponseContext';
import {
StepNavigator,
StepNavigatorRef,
StepSection
} from '@/shared/components/step/stepNavigator';
import TipoPagamentoSelect from '@/shared/components/tipoPagamento/TipoPagamentoSelect';
import { useTServicoPedidoFormHook } from '../../hooks/TServicoPedido/useTServicoPedidoFormHook';
import { TServicoPedidoFormInterface } from '../../interfaces/TServicoPedido/TServicoPedidoFormInterface';
/**
* Formulário de cadastro/edição de Natureza
* Baseado nos campos da tabela G_NATUREZA
*/
export default function TServicoPedidoForm({
isOpen,
data,
onClose,
onSave,
buttonIsLoading,
}: TServicoPedidoFormInterface) {
export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedidoFormInterface) {
const router = useRouter();
const form = useTServicoPedidoFormHook({});
const { setValue, reset, watch } = form;
// Atualiza o formulário quando recebe dados para edição
const [isSaving, setIsSaving] = useState(false);
const [isAdding, setIsAdding] = useState(false);
const [isPessoaFormOpen, setIsPessoaFormOpen] = useState(false);
const [isSaveConfirmOpen, setIsSaveConfirmOpen] = useState(false);
const [isCancelDialogOpen, setIsCancelDialogOpen] = useState(false)
const [selectedPessoaTipo, setSelectedPessoaTipo] = useState('');
const [shouldKeepFormOpen, setShouldKeepFormOpen] = useState(false)
const ref = useRef<StepNavigatorRef>(null);
// Controles de formulário
const handleClosePessoaForm = useCallback(() => setIsPessoaFormOpen(false), []);
const handleOpenSaveConfirm = useCallback(() => setIsSaveConfirmOpen(true), []);
const handleCloseSaveConfirm = useCallback(() => setIsSaveConfirmOpen(false), []);
// Hooks
// const playSuccess = useSoundHook("/sounds/success.mp3");
const { setResponse } = useResponse()
const { saveTServicoPedido } = useTServicoPedidoSaveHook();
const { showTServicoPedido } = useTServicoPedidoShowHook();
const { TServicoItemPedidoLocal, localAddTServicoItemPedido, setLocalTServicoItemPedido } = useTServicoItemPedidoLocalAddHook(setValue);
const { addTServicoItemPedido } = useTServicoItemPedidoCalculoHook(setValue);
const { indexTServicoItemPedido } = useTServicoItemPedidoIndexHook();
const { TServicoPedidoParams, loadParamsTServicoPedido } =
useTServicoPedidoLoadParamsHook();
// Acompanha as alterações, nos campos definidos
const selectedServicoTipo = watch('servico_tipo');
const selectedEmolumento = watch('emolumento');
const handleFormError = useCallback((errors: any) => {
console.group("Erros de validação do formulário");
console.log("Campos com erro:", errors);
console.groupEnd();
}, []);
// Envia a requisição para a API
const handleSavePedido = useCallback(async (data: TServicoPedidoInterface) => {
// Ativa o botão de loading
setIsSaving(true);
// Guarda a resposta da APi
const response = await saveTServicoPedido(data);
// Desativa o botão de loading
setIsSaving(false);
// Verifica se devo redirecionar a pagina
if (response?.servico_pedido_id > 0) {
// Toca o som do sistema
// playSuccess()
}
// Verifica se devo redirecionar a pagina
if (response?.servico_pedido_id > 0 && !shouldKeepFormOpen) {
router.replace(`/servicos/balcao/detalhes/${response.servico_pedido_id}`);
}
}, [saveTServicoPedido, shouldKeepFormOpen]);
// Modal de confirmação de serviço
const handleSubmitWithConfirmation = useCallback(() => {
// Envia o formulário
form.handleSubmit(handleSavePedido, handleFormError)();
}, [form, handleSavePedido, handleFormError]);
// Busca os itens do Pedido
const fetchPedidoItens = useCallback(async (id: number) => {
const pedidoItens = {
servico_pedido_id: id
}
// Busca os itens do pedido
const response = await indexTServicoItemPedido(pedidoItens);
// Verifica se os dados foram localizados
if (response?.data?.length) {
// Atualiza os dados dos itens locais
setLocalTServicoItemPedido(response.data);
// Atualiza os itens do formulário
setValue('itens', response.data);
}
}, [indexTServicoItemPedido, setValue, setLocalTServicoItemPedido]);
// Busca o pedido Principal
const fetchPedido = useCallback(async () => {
// Busca o pedido principal
const response = await showTServicoPedido({ servico_pedido_id });
// Verifica se o pedido foi localizado
if (response?.servico_pedido_id) {
// Atualiza os dados do formulário
reset(response);
// Carrega os itens do pedido
fetchPedidoItens(response.servico_pedido_id);
}
}, [servico_pedido_id, showTServicoPedido, reset, fetchPedidoItens]);
const handleAddItemWithPessoa = useCallback(async (selectedTPessoa: TPessoaInterface) => {
// Habilita o loading
setIsAdding(true)
// Constroi um novo item
const newItem = await addTServicoItemPedido({
servico_tipo: selectedServicoTipo,
emolumento: selectedEmolumento,
pessoa: selectedTPessoa
});
// Verifica se existe um novo item
if (!newItem) return;
// Obtem o indice atual
const index = TServicoItemPedidoLocal.length;
// Define a posição do item
newItem.index = index
// Atualiza o estado
localAddTServicoItemPedido(newItem);
// Atualiza os itens do formulário
form.setValue(`itens.${index}`, newItem);
// Desabilita o loading
setIsAdding(false)
}, [
addTServicoItemPedido,
selectedServicoTipo,
selectedEmolumento,
TServicoItemPedidoLocal.length,
localAddTServicoItemPedido,
form,
]);
// Controla o formulário de cancelamento de pedido
const handleOpenCancelDialog = useCallback(
async () => {
// Fecha a confirmação
setIsCancelDialogOpen(true)
}, []
)
// Controle de redirecionamento
const handleConfirmCancel = useCallback(
async () => {
// Redireciona o usuário
router.replace(`/servicos/balcao/`);
}, []
)
// Controle do formulário de cancelamento do Pedido
const handleCloseCancelDialog = useCallback(
async () => {
// Fecha o formulário
setIsCancelDialogOpen(false)
}, []
)
// Controle de itens
const handleAddItemBasic = useCallback(async () => {
setIsAdding(true)
// Prepara e valida os dados de item do pedido
const payload: TServicoItemPedidoAddInterface = {
servico_tipo: selectedServicoTipo,
emolumento: selectedEmolumento
}
// Verifica se os dados foram criados corretamente
if (!payload) return;
// Obtem o resultado da adição do item
const newItem = await addTServicoItemPedido(payload);
// Se tiver um novo item, adiciona o mesmo na tela
if (newItem) localAddTServicoItemPedido(newItem);
setIsAdding(false)
}, [addTServicoItemPedido, selectedServicoTipo, selectedEmolumento, localAddTServicoItemPedido]);
// Habilita o formulário de pessoas
const handleOpenPessoaForm = useCallback((tipoPessoa: string) => {
setSelectedPessoaTipo(tipoPessoa);
setIsPessoaFormOpen(true);
}, []);
// Adiciona o item a tabela e verifica se deve ou não montar a subview da linha da tabela
const handleSelectServicoTipo = useCallback(() => {
const response = HandleSelectTServicoTipoAction({
servico: selectedServicoTipo,
emolumento: selectedEmolumento,
onOpenPessoaForm: handleOpenPessoaForm,
onAddItem: handleAddItemBasic
})
// Verifica se existem erros
if (response?.status) {
setResponse(response)
}
}, [selectedServicoTipo, selectedEmolumento, handleOpenPessoaForm, handleAddItemBasic]);
// Cálculo automático dos totais
const calcularTotais = useCallback(() => {
if (!TServicoItemPedidoLocal || !TServicoItemPedidoLocal.length) {
setValue("valor_pedido", 0);
setValue("valor_pago", 0);
return;
}
const total = TServicoItemPedidoLocal.reduce((acc, item) => {
const valor = Number(item.valor ?? 0);
return acc + valor;
}, 0);
setValue("valor_pedido", total, { shouldDirty: true });
// opcional: manter valor pago igual ao pedido
const valorPagoAtual = watch("valor_pago");
if (!valorPagoAtual || valorPagoAtual === 0) {
setValue("valor_pago", total, { shouldDirty: true });
}
}, [TServicoItemPedidoLocal, setValue, watch]);
// Dispara a busca do pedido
useEffect(() => {
ResetFormIfData(form, data);
}, [data, form]);
function onError(error: any) {
console.log('Erro no formulário:', error);
}
// Se existir pedido_id, busca o pedido
if (servico_pedido_id) fetchPedido();
}, [servico_pedido_id, fetchPedido]);
// Dispara a busca de itens
useEffect(() => {
// Dispara a busca dos itens
setValue('itens', TServicoItemPedidoLocal, { shouldDirty: true });
}, [TServicoItemPedidoLocal, setValue]);
// Dispara a busca de parâmetros
useEffect(() => {
loadParamsTServicoPedido();
}, []);
// Monitora mudanças na lista de itens
useEffect(() => {
calcularTotais();
}, [TServicoItemPedidoLocal, calcularTotais]);
// Memoriza os dados para não renderizar novamente
const sections: StepSection[] = useMemo(() => [
{
key: 'pedido',
id: 'selectPedido',
icon: <Package className="h-4 w-4" />,
title: 'Pedido',
description: 'Dados gerais do pedido.'
},
{
key: 'servicoPedidoItem',
id: 'selectServicoPedidoItem',
icon: <UserSquare2 className="h-4 w-4" />,
title: 'Itens',
description: 'Itens/serviços do pedido.'
},
{
key: 'payment',
id: 'selectPayment',
icon: <CreditCard className="h-4 w-4" />,
title: 'Pagamento',
description: 'Forma e dados de pagamento.'
}
], []);
return (
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) onClose(null, false);
}}
>
<DialogContent className="w-full max-w-full p-6 sm:max-w-3xl md:max-w-2xl lg:max-w-2xl">
<DialogHeader>
<DialogTitle className="text-lg sm:text-xl">Formulário de Gramática</DialogTitle>
<DialogDescription className="text-muted-foreground text-sm">
Formulário de Gramática
</DialogDescription>
</DialogHeader>
{/* Formulário principal */}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSave, onError)} className="space-y-6">
{/* GRID MOBILE FIRST */}
<div className="grid w-full grid-cols-12 gap-4">
{/* Palavra */}
<div className="col-span-12 sm:col-span-6 md:col-span-12">
<FormField
control={form.control}
name="apresentante"
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Palavra</FormLabel>
<FormControl>
<Input
{...field}
type="text"
<div>
<h3 className='text-4xl font-bold mb-4'>Pedido</h3>
<Form {...form}>
<form>
<div className="flex gap-4">
{/* Seção1 */}
<main className="flex-1">
<div className="flex flex-col gap-4">
<Card role="presentation" id="selectPedido" className="scroll-mt-6">
<CardHeader>
<CardTitle><h4 className="text-3xl">Pedido</h4></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-12 gap-4">
{/* Escrevente */}
<div className="col-span-12 md:col-span-12">
<FormField
control={form.control}
name="escrevente_id"
render={({ field }) => (
<FormItem>
<FormLabel className="font-semibold">Escrevente/Tabelião</FormLabel>
<GUsuarioSelect field={field} />
<FormMessage />
</FormItem>
)}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Apresentante */}
<div className="col-span-12 md:col-span-8">
<FormField
control={form.control}
name="apresentante"
render={({ field }) => (
<FormItem>
<FormLabel>Apresentante</FormLabel>
<FormControl><Input {...field} type="text" /></FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* CPF */}
<div className="col-span-12 md:col-span-4">
<FormField
control={form.control}
name="cpfcnpj_apresentante"
render={({ field }) => (
<FormItem>
<FormLabel>CPF/CNPJ</FormLabel>
<FormControl>
<Input {...field}
type="text"
value={FormatCPFCNPJForm(field.value)}
onChange={(e) => {
const raw = UnmaskCPFCNPJForm(e.target.value);
field.onChange(raw);
}}
maxLength={14}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Pessoa Selo */}
<div className="col-span-12 md:col-span-8">
<FormField
control={form.control}
name="selo_pessoa_nome"
render={({ field }) => (
<FormItem>
<FormLabel>Pessoa Selo</FormLabel>
<FormControl><Input {...field} type="text" /></FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Pessoa CPF */}
<div className="col-span-12 md:col-span-4">
<FormField
control={form.control}
name="selo_pessoa_cpfcnpj"
render={({ field }) => (
<FormItem>
<FormLabel>CPF/CNPJ</FormLabel>
<FormControl>
<Input {...field}
type="text"
value={FormatCPFCNPJForm(field.value)}
onChange={(e) => {
const raw = UnmaskCPFCNPJForm(e.target.value);
field.onChange(raw);
}}
maxLength={14}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
</CardContent>
</Card>
{/* Seção 2 */}
<Card role="presentation" id="selectServicoPedidoItem" className="scroll-mt-6">
<CardHeader>
<CardTitle><h4 className="text-3xl">Itens</h4></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-12 gap-4">
<div className="col-span-12 md:col-span-6">
<FormField
control={form.control}
name="servico_tipo"
render={({ field }) => (
<FormItem>
<FormLabel>Serviços</FormLabel>
<TServicoTipoSelect field={field} />
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="col-span-12 md:col-span-6">
<FormField
control={form.control}
name="emolumento"
render={({ field }) => (
<FormItem>
<FormLabel>Emolumentos</FormLabel>
<GEmolumentoServicoSelect sistema_id={2} field={field} />
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="col-span-12 text-end">
<LoadingButton
text={`+ Adicionar`}
textLoading="Adicionando..."
onClick={handleSelectServicoTipo}
loading={isAdding}
/>
</div>
<div className="col-span-12">
<TServicoItemPedidoFormTable
form={form}
params={TServicoPedidoParams}
data={TServicoItemPedidoLocal} />
</div>
</div>
</CardContent>
</Card>
{/* Seção 3 */}
<Card role="presentation" id="selectPayment" className="scroll-mt-6">
<CardHeader>
<CardTitle><h4 className="text-3xl">Pagamento</h4></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-12 gap-4">
<div className="col-span-12 md:col-span-8">
<FormField
control={form.control}
name="pagador_nome"
render={({ field }) => (
<FormItem>
<FormLabel>Requerente</FormLabel>
<FormControl><Input {...field} type="text" /></FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="col-span-12 md:col-span-4">
<FormField
control={form.control}
name="pagador_cpfcnpj"
render={({ field }) => (
<FormItem>
<FormLabel>CPF/CNPJ</FormLabel>
<FormControl>
<Input {...field}
type="text"
value={FormatCPFCNPJForm(field.value)}
onChange={(e) => {
const raw = UnmaskCPFCNPJForm(e.target.value);
field.onChange(raw);
}}
maxLength={14}
/></FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="col-span-12 md:col-span-4">
<FormField
control={form.control}
name="valor_pedido"
render={({ field }) => (
<FormItem>
<FormLabel>Valor do Pedido</FormLabel>
<FormControl>
<Input {...field} type="number"
onChange={(e) => field.onChange(parseNumberInput(e))}
readOnly={true}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="col-span-12 md:col-span-4">
<FormField
control={form.control}
name="valor_pago"
render={({ field }) => (
<FormItem>
<FormLabel>Valor Pago</FormLabel>
<FormControl>
<Input {...field} type="number"
onChange={(e) => field.onChange(parseNumberInput(e))} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="col-span-12 md:col-span-4">
<FormField
control={form.control}
name="tipo_pagamento"
render={({ field }) => (
<FormItem>
<FormLabel>Forma Pagamento</FormLabel>
<TipoPagamentoSelect field={field} />
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
{/* Rodapé */}
<DialogFooter className="mt-6 flex flex-col justify-end gap-2 sm:flex-row">
<DialogClose asChild>
<Button variant="outline" type="button">
Cancelar
</Button>
</DialogClose>
<LoadingButton
text="Salvar"
textLoading="Salvando..."
type="submit"
loading={buttonIsLoading}
/>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
</main>
{/* Sidebar */}
<aside className="hidden w-[360px] lg:block">
<div className="sticky top-4 z-10 max-h-[calc(100vh-2rem)] overflow-auto flex flex-col gap-4">
<Card className="card-border">
<CardContent>
<StepNavigator
ref={ref}
sections={sections}
defaultActive="pedido"
scrollOffset={16}
spyLockMs={600}
/>
</CardContent>
</Card>
<Card className="card-border">
<CardHeader>
<CardTitle className="text-2xl font-semibold">Ações</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="flex items-center space-x-2">
<Switch
id="permanecer_formulario"
checked={shouldKeepFormOpen}
onCheckedChange={(checked) => {
setShouldKeepFormOpen(checked);
}}
/>
<Label htmlFor="permanecer_formulario">Permanecer no formulário</Label>
</div>
<Button
className="w-full"
variant="outline"
type='button'
onClick={handleOpenCancelDialog}>
Cancelar
</Button>
<LoadingButton
text="Salvar"
textLoading="Salvando..."
onClick={handleOpenSaveConfirm}
loading={isSaving}
/>
</CardContent>
</Card>
</div>
</aside>
</div>
</form>
{/* Cofirmação de envio de dados */}
{isSaveConfirmOpen && (
<ConfirmDialog
isOpen={isSaveConfirmOpen}
title="Confirmar pedido"
description="Atenção"
message="Deseja confirmar o pedido?"
confirmText="Sim, confirmar"
cancelText="Cancelar"
onConfirm={handleSubmitWithConfirmation}
onCancel={handleCloseSaveConfirm}
/>
)}
{/* Confirma o cancelamento do pedido */}
{isCancelDialogOpen && (
<ConfirmDialog
isOpen={isCancelDialogOpen}
title="Cancelamento de pedido"
description="Atenção"
message="Deseja cancelar o pedido? Os dados não serão salvos!"
confirmText="Sim, cancelar"
cancelText="Fechar"
onConfirm={handleConfirmCancel}
onCancel={handleCloseCancelDialog}
/>
)}
{/* Modal TPessoa */}
{isPessoaFormOpen && (
<TPessoaTableFormDialog
isOpen={isPessoaFormOpen}
tipoPessoa={selectedPessoaTipo}
onClose={handleClosePessoaForm}
onSave={handleAddItemWithPessoa}
buttonIsLoading={isSaving}
/>
)}
</Form>
</div>
);
}

View file

@ -109,7 +109,7 @@ export default function TServicoPedidoIndex() {
* Busca inicial dos dados
*/
useEffect(() => {
// indexTServicoPedido();
indexTServicoPedido();
}, []);
return (
@ -119,12 +119,13 @@ export default function TServicoPedidoIndex() {
title={'Pedidos'}
description={'Pedidos de Autenticação/Reconhecimento'}
buttonText={'Novo pedido'}
buttonAction={() => {
handleOpenForm(null);
}}
href='/servicos/balcao/pedido'
/>
{/* Tabela de andamentos */}
<TServicoPedidoTable data={TServicoPedido} onEdit={handleOpenForm} onDelete={handleConfirmDelete} />
<TServicoPedidoTable
data={TServicoPedido}
onEdit={handleOpenForm}
onDelete={handleConfirmDelete} />
{/* Modal de confirmação */}
{isConfirmOpen && (
<ConfirmDialog

View file

@ -1,9 +1,10 @@
'use client';
import { Card, CardContent } from '@/components/ui/card';
import TServicoPedidoTableInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoTableInterface';
import { DataTable } from '@/shared/components/dataTable/DataTable';
import TServicoPedidoColumns from './TServicoPedidoColumns';
import TServicoPedidoTableInterface from '../../interfaces/TServicoPedido/TServicoPedidoTableInterface';
/**
* Componente principal da tabela de Naturezas
@ -12,12 +13,16 @@ export default function TServicoPedidoTable({ data, onEdit, onDelete }: TServico
const columns = TServicoPedidoColumns(onEdit, onDelete);
return (
<div>
<DataTable
data={data}
columns={columns}
filterColumn="palavra"
filterPlaceholder="Buscar por descrição da natureza..."
/>
<Card>
<CardContent>
<DataTable
data={data}
columns={columns}
filterColumn="apresentante"
filterPlaceholder="Buscar por apresentante..."
/>
</CardContent>
</Card>
</div>
);
}

View file

@ -0,0 +1,22 @@
import TServicoItemPedidoInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoIntefarce';
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
import API from '@/shared/services/api/Api';
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import ApiResponseInterface from '@/shared/services/api/interfaces/ApiResponseInterface';
async function executeTServicoItemPedidoAtivarData(data: TServicoItemPedidoInterface): Promise<ApiResponseInterface> {
const api = new API();
return api.send({
method: Methods.PUT,
endpoint: `servicos/balcao/t_servico_itempedido/${data.servico_itempedido_id}/ativar`,
body: data,
});
}
export const TServicoItemPedidoAtivarData = withClientErrorHandler(executeTServicoItemPedidoAtivarData);

View file

@ -0,0 +1,22 @@
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
import API from '@/shared/services/api/Api';
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import ApiResponseInterface from '@/shared/services/api/interfaces/ApiResponseInterface';
import TServicoItemPedidoInterface from '../../interfaces/TServicoItemPedido/TServicoItemPedidoIntefarce';
async function executeTServicoItemPedidoCancelarData(data: TServicoItemPedidoInterface): Promise<ApiResponseInterface> {
const api = new API();
return api.send({
method: Methods.PUT,
endpoint: `servicos/balcao/t_servico_itempedido/${data.servico_itempedido_id}/cancelar`,
body: data,
});
}
export const TServicoItemPedidoCancelarData = withClientErrorHandler(executeTServicoItemPedidoCancelarData);

View file

@ -1,15 +1,16 @@
import TServicoItemPedidoInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoIntefarce';
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
import API from '@/shared/services/api/Api';
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import ApiResponseInterface from '@/shared/services/api/interfaces/ApiResponseInterface';
async function executeTServicoItemPedidoIndexData(): Promise<ApiResponseInterface> {
const api = new API();
async function executeTServicoItemPedidoIndexData(data: TServicoItemPedidoInterface): Promise<ApiResponseInterface> {
const api = new API();
return api.send({
method: Methods.GET,
endpoint: `servicos/t_servico_itempedido/`,
endpoint: `servicos/balcao/t_servico_itempedido/pedido/${data.servico_pedido_id}`,
});
}
export const TServicoItemPedidoIndexData = withClientErrorHandler(executeTServicoItemPedidoIndexData);
export const TServicoItemPedidoIndexData = withClientErrorHandler(executeTServicoItemPedidoIndexData);

View file

@ -0,0 +1,16 @@
import TServicoItemPedidoInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoIntefarce';
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
import API from '@/shared/services/api/Api';
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import ApiResponseInterface from '@/shared/services/api/interfaces/ApiResponseInterface';
async function executeTServicoItemPedidoShowData(data: TServicoItemPedidoInterface): Promise<ApiResponseInterface> {
const api = new API();
return api.send({
method: Methods.GET,
endpoint: `servicos/balcao/t_servico_itempedido/pedido/${data.servico_pedido_id}`,
});
}
export const TServicoItemPedidoShowData = withClientErrorHandler(executeTServicoItemPedidoShowData);

View file

@ -0,0 +1,19 @@
import TServicoPedidoInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoInterface';
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
import API from '@/shared/services/api/Api';
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import ApiResponseInterface from '@/shared/services/api/interfaces/ApiResponseInterface';
async function executeTServicoPedidoAtivarData(data: TServicoPedidoInterface): Promise<ApiResponseInterface> {
const api = new API();
return api.send({
method: Methods.PUT,
endpoint: `servicos/balcao/t_servico_pedido/${data.servico_pedido_id}/ativar`,
body: data,
});
}
export const TServicoPedidoAtivarData = withClientErrorHandler(executeTServicoPedidoAtivarData);

View file

@ -0,0 +1,19 @@
import TServicoPedidoInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoInterface';
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
import API from '@/shared/services/api/Api';
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import ApiResponseInterface from '@/shared/services/api/interfaces/ApiResponseInterface';
async function executeTServicoPedidoCancelarData(data: TServicoPedidoInterface): Promise<ApiResponseInterface> {
const api = new API();
return api.send({
method: Methods.PUT,
endpoint: `servicos/balcao/t_servico_pedido/${data.servico_pedido_id}/cancelar`,
body: data,
});
}
export const TServicoPedidoCancelarData = withClientErrorHandler(executeTServicoPedidoCancelarData);

View file

@ -10,7 +10,7 @@ async function executeTServicoPedidoDeleteData(data: TServicoPedidoInterface): P
return api.send({
method: Methods.DELETE,
endpoint: `servico/t_servico_pedido/${data.servico_pedido_id}`,
endpoint: `servicos/balcao/t_servico_pedido/${data.servico_pedido_id}`,
});
}

View file

@ -8,7 +8,7 @@ async function executeTServicoPedidoIndexData(): Promise<ApiResponseInterface> {
return api.send({
method: Methods.GET,
endpoint: `servico/t_servico_pedido/`,
endpoint: `servicos/balcao/t_servico_pedido/`,
});
}

View file

@ -0,0 +1,15 @@
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
import API from '@/shared/services/api/Api';
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import ApiResponseInterface from '@/shared/services/api/interfaces/ApiResponseInterface';
async function executeTServicoPedidoLoadParamsData(): Promise<ApiResponseInterface> {
const api = new API();
return api.send({
method: Methods.GET,
endpoint: `servicos/balcao/t_servico_pedido/load-params`,
});
}
export const TServicoPedidoLoadParamsData = withClientErrorHandler(executeTServicoPedidoLoadParamsData);

View file

@ -15,7 +15,7 @@ async function executeTServicoPedidoSaveData(data: TServicoPedidoInterface): Pro
// Executa a requisição para a API com o método apropriado e envia os dados no corpo
return api.send({
method: isUpdate ? Methods.PUT : Methods.POST, // PUT se atualizar, POST se criar
endpoint: `servico/t_servico_pedido/${data.servico_pedido_id || ''}`, // endpoint dinâmico
endpoint: `servicos/balcao/t_servico_pedido/${data.servico_pedido_id || ''}`, // endpoint dinâmico
body: data, // payload enviado para a API
});
}

View file

@ -0,0 +1,16 @@
import TServicoPedidoInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoInterface';
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
import API from '@/shared/services/api/Api';
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import ApiResponseInterface from '@/shared/services/api/interfaces/ApiResponseInterface';
async function executeTServicoPedidoShowData(data: TServicoPedidoInterface): Promise<ApiResponseInterface> {
const api = new API();
return api.send({
method: Methods.GET,
endpoint: `servicos/balcao/t_servico_pedido/${data.servico_pedido_id}`,
});
}
export const TServicoPedidoShowData = withClientErrorHandler(executeTServicoPedidoShowData);

View file

@ -0,0 +1,33 @@
'use client';
import { useState } from 'react';
import TServicoItemPedidoInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoIntefarce';
import { TServicoItemPedidoAtivarService } from '@/packages/servicos/services/TServicoItemPedido/TServicoItemPedidoAtivarService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useTServicoItemPedidoAtivarHook = () => {
const { setResponse } = useResponse();
const [TServicoItemPedido, setTServicoItemPedido] = useState<TServicoItemPedidoInterface | null>(null);
const [isOpen, setIsOpen] = useState(false);
const ativarTServicoItemPedido = async (data: TServicoItemPedidoInterface) => {
const response = await TServicoItemPedidoAtivarService(data);
setTServicoItemPedido(response.data);
setResponse(response);
setIsOpen(false);
return response.data;
};
return { TServicoItemPedido, ativarTServicoItemPedido, isOpen, setIsOpen };
};

View file

@ -0,0 +1,85 @@
'use client';
import { useState } from 'react';
import { FieldValues, UseFormSetValue } from 'react-hook-form';
import { GCalculoServicoService } from '@/packages/administrativo/services/GCalculo/GCalculoServicoService';
import TServicoPedidoItemPreparePayload from '@/packages/servicos/actions/TServicoPedidoItem/TServicoPedidoItemPreparePayload';
import TServicoItemPedidoAddInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoAddInterface';
import TServicoItemPedidoCalculoResponseInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoCalculoResponseInterface';
import { useResponse } from '@/shared/components/response/ResponseContext';
export function useTServicoItemPedidoCalculoHook<TFormValues extends FieldValues>(setValue?: UseFormSetValue<TFormValues>) {
const { setResponse } = useResponse();
const [TServicoItemPedido, setTServicoItemPedido] = useState<TServicoItemPedidoCalculoResponseInterface[]>([]);
const addTServicoItemPedido = async (data: TServicoItemPedidoAddInterface) => {
// Trata os dados do payload
const payload = TServicoPedidoItemPreparePayload(data);
if (payload.status) {
setResponse(payload)
return;
}
// Realiza a busca do item
const response = await GCalculoServicoService(payload, data);
// Verifico se tenho código de resposta
if (response.status) {
// Exibo a resposta em tela
setResponse(response)
return;
}
// Verifica se tem status de erro
if (response.status) {
return {
'status': response.status,
'message': 'Erro ao processar dados'
};
}
// Atualiza o estado com fallback seguro
setTServicoItemPedido((prev) => {
const safePrev = Array.isArray(prev) ? prev : [];
const novoArray = [...safePrev, response];
if (setValue) {
// Protege contra sobrescrita incorreta do formulário
setValue('itens', novoArray, { shouldDirty: true });
}
return novoArray;
});
return response;
};
return {
TServicoItemPedido,
setTServicoItemPedido,
addTServicoItemPedido,
};
};

View file

@ -0,0 +1,32 @@
'use client';
import { useState } from 'react';
import TServicoItemPedidoInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoIntefarce';
import { TServicoItemPedidoCancelarService } from '@/packages/servicos/services/TServicoItemPedido/TServicoItemPedidoCancelarService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useTServicoItemPedidoCancelarHook = () => {
const { setResponse } = useResponse();
const [TServicoItemPedido, setTServicoItemPedido] = useState<TServicoItemPedidoInterface | null>(null);
const [isOpen, setIsOpen] = useState(false);
const cancelarTServicoItemPedido = async (data: TServicoItemPedidoInterface) => {
const response = await TServicoItemPedidoCancelarService(data);
setTServicoItemPedido(response.data);
setResponse(response);
setIsOpen(false);
return response.data;
};
return { TServicoItemPedido, cancelarTServicoItemPedido, isOpen, setIsOpen };
};

View file

@ -2,23 +2,29 @@
import { useState } from 'react';
import TServicoItemPedidoIndexResponseInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoCalculoResponseInterface';
import TServicoItemPedidoInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoIntefarce';
import { TServicoItemPedidoIndexService } from '@/packages/servicos/services/TServicoItemPedido/TServicoItemPedidoIndexService';
import { useResponse } from '@/shared/components/response/ResponseContext';
import TServicoItemPedidoInterface from '../../interfaces/TServicoItemPedido/TServicoItemPedidoIntefarce';
import { TServicoItemPedidoIndexService } from '../../services/TServicoItemPedido/TServicoItemPedidoIndexService';
export const useTServicoItemPedidoIndexHook = () => {
const { setResponse } = useResponse();
const [TServicoItemPedido, setTServicoItemPedido] = useState<TServicoItemPedidoInterface[]>([]);
const [TServicoItemPedido, setTServicoItemPedido] = useState<TServicoItemPedidoIndexResponseInterface[]>([]);
const indexTServicoItemPedido = async (data: TServicoItemPedidoInterface) => {
const response = await TServicoItemPedidoIndexService(data);
const indexTServicoItemPedido = async () => {
const response = await TServicoItemPedidoIndexService();
// Armazena os dados consultados
setTServicoItemPedido(response.data);
// Define a resposta (toast, modal, feedback, etc.)
setResponse(response);
// Retorno imediato dos valores
return response
};
return {

View file

@ -0,0 +1,41 @@
'use client';
import { useState } from 'react';
import { FieldValues, UseFormSetValue } from 'react-hook-form';
import { default as TServicoItemPedidoAddResponseInterface, default as TServicoItemPedidoIndexResponseInterface } from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoCalculoResponseInterface';
export function useTServicoItemPedidoLocalAddHook<TFormValues extends FieldValues>(setValue?: UseFormSetValue<TFormValues>) {
const [TServicoItemPedidoLocal, setLocalTServicoItemPedido] = useState<TServicoItemPedidoIndexResponseInterface[]>([]);
const localAddTServicoItemPedido = (item: TServicoItemPedidoAddResponseInterface) => {
setLocalTServicoItemPedido((prev) => {
const updated = [...prev, item];
if (setValue) setValue("itens", updated);
return updated;
});
};
const localClearTServicoItemPedido = () => {
setLocalTServicoItemPedido([]);
if (setValue) setValue("itens", []);
};
return {
TServicoItemPedidoLocal,
localAddTServicoItemPedido,
setLocalTServicoItemPedido,
localClearTServicoItemPedido,
};
};

View file

@ -0,0 +1,28 @@
import { useState } from 'react';
import TServicoItemPedidoInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoIntefarce';
import { TServicoItemPedidoShowService } from '@/packages/servicos/services/TServicoItemPedido/TServicoItemPedidoShowService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useTServicoItemPedidoShowHook = () => {
const { setResponse } = useResponse();
const [TServicoItemPedido, setTServicoItemPedido] = useState<TServicoItemPedidoInterface>();
const showTServicoItemPedido = async (data: TServicoItemPedidoInterface) => {
const response = await TServicoItemPedidoShowService(data);
setTServicoItemPedido(response.data);
setResponse(response);
return response.data
};
return { TServicoItemPedido, showTServicoItemPedido };
};

View file

@ -0,0 +1,37 @@
'use client';
import { useState } from 'react';
import TServicoPedidoInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoInterface';
import { TServicoPedidoAtivarService } from '@/packages/servicos/services/TServicoPedido/TServicoPedidoAtivarService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useTServicoPedidoAtivarHook = () => {
const { setResponse } = useResponse();
const [TServicoPedido, setTServicoPedido] = useState<TServicoPedidoInterface | null>(null);
// controla se o formulário está aberto ou fechado
const [isOpen, setIsOpen] = useState(false);
const ativarTServicoPedido = async (data: TServicoPedidoInterface) => {
const response = await TServicoPedidoAtivarService(data);
// Armazena os dados da resposta
setTServicoPedido(response.data);
// Define os dados da resposta (toast, modal, etc.)
setResponse(response);
// Fecha o formulário automaticamente após salvar
setIsOpen(false);
// Retorna os valores de forma imediata
return response.data;
};
return { TServicoPedido, ativarTServicoPedido, isOpen, setIsOpen };
};

View file

@ -0,0 +1,37 @@
'use client';
import { useState } from 'react';
import TServicoPedidoInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoInterface';
import { TServicoPedidoCancelarService } from '@/packages/servicos/services/TServicoPedido/TServicoPedidoCancelarService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useTServicoPedidoCancelarHook = () => {
const { setResponse } = useResponse();
const [TServicoPedido, setTServicoPedido] = useState<TServicoPedidoInterface | null>(null);
// controla se o formulário está aberto ou fechado
const [isOpen, setIsOpen] = useState(false);
const cancelarTServicoPedido = async (data: TServicoPedidoInterface) => {
const response = await TServicoPedidoCancelarService(data);
// Armazena os dados da resposta
setTServicoPedido(response.data);
// Define os dados da resposta (toast, modal, etc.)
setResponse(response);
// Fecha o formulário automaticamente após salvar
setIsOpen(false);
// Retorna os valores de forma imediata
return response.data;
};
return { TServicoPedido, cancelarTServicoPedido, isOpen, setIsOpen };
};

View file

@ -1,13 +1,26 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { TServicoPedidoFormValues, TServicoPedidoSchema } from '../../schemas/TServicoPedido/TServicoPedidoSchema';
import { TServicoPedidoFormSchema, TServicoPedidoFormValues } from '@/packages/servicos/schemas/TServicoPedido/TServicoPedidoFormSchema';
export function useTServicoPedidoFormHook(defaults?: Partial<TServicoPedidoFormValues>) {
return useForm<TServicoPedidoFormValues>({
resolver: zodResolver(TServicoPedidoSchema),
resolver: zodResolver(TServicoPedidoFormSchema),
defaultValues: {
servico_pedido_id: 0,
escrevente_id: 0,
apresentante: "",
cpfcnpj_apresentante: "",
selo_pessoa_nome: "",
selo_pessoa_cpfcnpj: "",
servico_tipo: null,
emolumento: null,
itens: [],
pagador_nome: "",
pagador_cpfcnpj: "",
valor_pedido: 0,
valor_pago: 0,
situacao: "F",
tipo_pagamento: null,
...defaults,
},
});

Some files were not shown because too many files have changed in this diff Show more