refactor(TServicoPedido): Refatoração da tela, otimizando e separando as responsabilidades

This commit is contained in:
Keven 2025-12-10 18:10:45 -03:00
parent 790dbe2df3
commit 32937c9501
16 changed files with 655 additions and 672 deletions

View file

@ -5,3 +5,21 @@ NEXT_PUBLIC_ORIUS_APP_STATE=GO
NEXT_PUBLIC_ORIUS_APP_API_URL=<http://localhost:8000/>
NEXT_PUBLIC_ORIUS_APP_API_PREFIX=api/v1/
NEXT_PUBLIC_ORIUS_APP_API_CONTENT_TYPE=application/json
## Modo Debug
Abra Run → Add Configuration… → Attach to Node.js
Configure:
{
"name": "Attach Next.js (9230)",
"type": "node",
"request": "attach",
"port": 9230,
"restart": true,
"smartStep": true,
"skipFiles": ["<node_internals>/**"]
}
npm run dev:debug

View file

@ -12,9 +12,6 @@ const nextConfig = {
eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true },
// Removido o experimental.runtime (incompatível no Next 15)
// O runtime agora é definido por rota ou via middleware (Edge/Node.js),
// mas por padrão, tudo roda em Node.js quando "output: standalone" está ativo.
};
module.exports = nextConfig;

206
package-lock.json generated
View file

@ -63,6 +63,7 @@
"@types/react-dom": "^19",
"@typescript-eslint/eslint-plugin": "^8.46.1",
"@typescript-eslint/parser": "^8.46.1",
"cross-env": "^10.1.0",
"eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
@ -400,6 +401,13 @@
"tslib": "^2.4.0"
}
},
"node_modules/@epic-web/invariant": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
"dev": true,
"license": "MIT"
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
@ -4254,6 +4262,24 @@
"react": ">= 16.8.0"
}
},
"node_modules/cross-env": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
"integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@epic-web/invariant": "^1.0.0",
"cross-spawn": "^7.0.6"
},
"bin": {
"cross-env": "dist/bin/cross-env.js",
"cross-env-shell": "dist/bin/cross-env-shell.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -4273,6 +4299,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"devOptional": true,
"license": "MIT"
},
"node_modules/d3-array": {
@ -4396,127 +4423,6 @@
"node": ">=12"
}
},
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"license": "ISC",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"license": "ISC",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"license": "ISC",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"license": "ISC",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@ -4612,12 +4518,6 @@
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
"license": "MIT"
},
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
"license": "MIT"
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -4690,16 +4590,6 @@
"node": ">=0.10.0"
}
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@ -5615,15 +5505,6 @@
"dev": true,
"license": "Apache-2.0"
},
"node_modules/fast-equals": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz",
"integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@ -6187,15 +6068,6 @@
"node": ">=12"
}
},
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@ -7082,12 +6954,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@ -8009,21 +7875,6 @@
}
}
},
"node_modules/react-smooth": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
"integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
"license": "MIT",
"dependencies": {
"fast-equals": "^5.0.1",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-style-singleton": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
@ -9000,9 +8851,6 @@
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",

View file

@ -4,7 +4,8 @@
"version": "25.9.1",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"dev": "next dev",
"dev:debug": "cross-env NEXT_USE_TURBOPACK=0 NODE_OPTIONS=\"--inspect=9230\" next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
@ -65,6 +66,7 @@
"@types/react-dom": "^19",
"@typescript-eslint/eslint-plugin": "^8.46.1",
"@typescript-eslint/parser": "^8.46.1",
"cross-env": "^10.1.0",
"eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",

View file

@ -2,48 +2,15 @@
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: {},
};
if (data?.pessoa?.pessoa_id) {
item.subview = {
servico: data.servico_tipo,
pessoa: data.pessoa,
};
}
return item;
return response;
}
export const GCalculoServicoService = withClientErrorHandler(executeGCalculoServicoService);

View file

@ -1,6 +1,6 @@
import HandleSelectTServicoTipoInterface from './HandleSelectTServicoTipoInterface';
export default function HandleSelectTServicoTipoAction({
export default function handleServicoTipoPessoaSelection({
servico,
emolumento,
onOpenPessoaForm,

View file

@ -0,0 +1,30 @@
export default function TServicoPedidoItemLocalPrepare(data, response) {
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,
};
if (data?.pessoa?.pessoa_id) {
item.subview = {
servico: data.servico_tipo,
pessoa: data.pessoa,
};
}
return item
}

View file

@ -1,6 +1,7 @@
import TServicoItemPedidoAddInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoAddInterface';
export default function TServicoPedidoItemPreparePayload(data: TServicoItemPedidoAddInterface) {
data.qtd = 1;
data.valor_documento = 0;

View file

@ -2,6 +2,7 @@
import { Minus, Plus } from 'lucide-react';
import React, { memo } from 'react';
import { useWatch } from 'react-hook-form';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -17,12 +18,13 @@ import TPessoaTableFormSubview from '@/packages/administrativo/components/TPesso
import TServicoItemPedidoFormTableInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoFormTableInterface';
import GetCapitalize from '@/shared/actions/text/GetCapitalize';
function TServicoItemPedidoFormTableComponent({
data,
form,
params,
}: TServicoItemPedidoFormTableInterface) {
console.log(data);
function TServicoItemPedidoFormTableComponent({ form, params, handleChangeQtd }: TServicoItemPedidoFormTableInterface) {
const { control } = form;
// Sempre “ouve” o campo itens do formulário
const data = useWatch({ control, name: 'itens' }) || [];
return (
<div className="rounded-md border">
<Table>
@ -37,66 +39,71 @@ function TServicoItemPedidoFormTableComponent({
<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>
{data.length ? (
data.map((item, index) => (
<React.Fragment key={`fragment-${index}`}>
<TableRow 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>
</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 justify-center gap-1">
<Button
type="button"
size="icon"
variant="outline"
className="h-8 w-8 rounded-lg border bg-white"
>
<Minus className="h-4 w-4" />
</Button>
<Input type="number" className="h-8 w-12 px-1 text-center" />
<Button
type="button"
size="icon"
variant="outline"
className="h-8 w-8 rounded-lg border bg-white"
>
<Plus className="h-4 w-4" />
</Button>
</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 justify-center gap-1">
<Button
type="button"
size="icon"
variant="outline"
className="h-8 w-8 rounded-lg border bg-white"
onClick={() => handleChangeQtd(index, -1)}
>
<Minus className="h-4 w-4" />
</Button>
<Input
type="number"
className="h-8 w-12 px-1 text-center"
value={item.qtd ?? 1}
readOnly
/>
<Button
type="button"
size="icon"
variant="outline"
className="h-8 w-8 rounded-lg border bg-white"
onClick={() => handleChangeQtd(index, 1)}
>
<Plus className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
{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>
{/* 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>
);
})
)}
</React.Fragment>
))
) : (
<TableRow>
<TableCell colSpan={7} className="py-4 text-center">

View file

@ -1,8 +1,8 @@
'use client';
import { CreditCard, Package, UserSquare2 } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { CreditCardIcon, PackageIcon, UserSquare2Icon } from 'lucide-react';
import { useMemo } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@ -21,295 +21,23 @@ import GEmolumentoServicoSelect from '@/packages/administrativo/components/GEmol
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 useTServicoPedidoFormControllerHook from '@/packages/servicos/hooks/TServicoPedido/useTServicoPedidoFormControllerHook';
import { TServicoPedidoFormInterface } from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoFormInterface';
import TServicoPedidoInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoInterface';
import { TServicoPedidoFormValues } from '@/packages/servicos/schemas/TServicoPedido/TServicoPedidoFormSchema';
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,
StepSection
} from '@/shared/components/step/stepNavigator';
import TipoPagamentoSelect from '@/shared/components/tipoPagamento/TipoPagamentoSelect';
import { SituacoesEnum } from '@/shared/enums/SituacoesEnum';
export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedidoFormInterface) {
const router = useRouter();
const form = useTServicoPedidoFormHook({});
const { setValue, reset, watch } = form;
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: TServicoPedidoFormValues) => {
// Ativa o botão de loading
setIsSaving(true);
// Converte o tipo do formulário (Zod) para o tipo da API (Interface)
const payload: TServicoPedidoInterface = {
...data,
situacao: data.situacao as unknown as SituacoesEnum,
};
const response = await saveTServicoPedido(payload);
setIsSaving(false);
// 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(() => {
// 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]);
const tServicoPedidoController = useTServicoPedidoFormControllerHook(servico_pedido_id)
const { form } = tServicoPedidoController
// Memoriza os dados para não renderizar novamente
const sections: StepSection[] = useMemo(
@ -317,21 +45,21 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido
{
key: 'pedido',
id: 'selectPedido',
icon: <Package className="h-4 w-4" />,
icon: <PackageIcon className="h-4 w-4" />,
title: 'Pedido',
description: 'Dados gerais do pedido.',
},
{
key: 'servicoPedidoItem',
id: 'selectServicoPedidoItem',
icon: <UserSquare2 className="h-4 w-4" />,
icon: <UserSquare2Icon className="h-4 w-4" />,
title: 'Itens',
description: 'Itens/serviços do pedido.',
},
{
key: 'payment',
id: 'selectPayment',
icon: <CreditCard className="h-4 w-4" />,
icon: <CreditCardIcon className="h-4 w-4" />,
title: 'Pagamento',
description: 'Forma e dados de pagamento.',
},
@ -496,15 +224,16 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido
<LoadingButton
text={`+ Adicionar`}
textLoading="Adicionando..."
onClick={handleSelectServicoTipo}
loading={isAdding}
onClick={tServicoPedidoController.handleSelectServicoTipo}
loading={tServicoPedidoController.isAdding}
/>
</div>
<div className="col-span-12">
<TServicoItemPedidoFormTable
form={form}
params={TServicoPedidoParams}
data={TServicoItemPedidoLocal}
params={tServicoPedidoController.TServicoPedidoParams}
data={tServicoPedidoController.TServicoItemPedidoLocal}
handleChangeQtd={tServicoPedidoController.handleChangeQtd}
/>
</div>
</div>
@ -563,20 +292,23 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido
<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>
)}
render={({ field }) => {
const valorPedido = form.watch('valor_pedido');
return (
<FormItem>
<FormLabel>Valor do Pedido</FormLabel>
<FormControl>
<Input
{...field}
type="number"
value={valorPedido ?? 0}
readOnly
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
<div className="col-span-12 md:col-span-4">
@ -590,7 +322,7 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido
<Input
{...field}
type="number"
onChange={(e) => field.onChange(parseNumberInput(e))}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormMessage />
@ -623,7 +355,7 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido
<Card className="card-border">
<CardContent>
<StepNavigator
ref={ref}
ref={tServicoPedidoController.ref}
sections={sections}
defaultActive="pedido"
scrollOffset={16}
@ -639,9 +371,9 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido
<div className="flex items-center space-x-2">
<Switch
id="permanecer_formulario"
checked={shouldKeepFormOpen}
checked={tServicoPedidoController.shouldKeepFormOpen}
onCheckedChange={(checked) => {
setShouldKeepFormOpen(checked);
tServicoPedidoController.setShouldKeepFormOpen(checked);
}}
/>
<Label htmlFor="permanecer_formulario">Permanecer no formulário</Label>
@ -650,15 +382,15 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido
className="w-full"
variant="outline"
type="button"
onClick={handleOpenCancelDialog}
onClick={tServicoPedidoController.handleOpenCancelDialog}
>
Cancelar
</Button>
<LoadingButton
text="Salvar"
textLoading="Salvando..."
onClick={handleOpenSaveConfirm}
loading={isSaving}
onClick={tServicoPedidoController.handleOpenSaveConfirm}
loading={tServicoPedidoController.isSaving}
/>
</CardContent>
</Card>
@ -668,41 +400,41 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido
</form>
{/* Cofirmação de envio de dados */}
{isSaveConfirmOpen && (
{tServicoPedidoController.isSaveConfirmOpen && (
<ConfirmDialog
isOpen={isSaveConfirmOpen}
isOpen={tServicoPedidoController.isSaveConfirmOpen}
title="Confirmar pedido"
description="Atenção"
message="Deseja confirmar o pedido?"
confirmText="Sim, confirmar"
cancelText="Cancelar"
onConfirm={handleSubmitWithConfirmation}
onCancel={handleCloseSaveConfirm}
onConfirm={tServicoPedidoController.handleSubmitWithConfirmation}
onCancel={tServicoPedidoController.handleCloseSaveConfirm}
/>
)}
{/* Confirma o cancelamento do pedido */}
{isCancelDialogOpen && (
{tServicoPedidoController.isCancelDialogOpen && (
<ConfirmDialog
isOpen={isCancelDialogOpen}
isOpen={tServicoPedidoController.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}
onConfirm={tServicoPedidoController.handleConfirmCancel}
onCancel={tServicoPedidoController.handleCloseCancelDialog}
/>
)}
{/* Modal TPessoa */}
{isPessoaFormOpen && (
{tServicoPedidoController.isPessoaFormOpen && (
<TPessoaTableFormDialog
isOpen={isPessoaFormOpen}
tipoPessoa={selectedPessoaTipo}
onClose={handleClosePessoaForm}
onSave={handleAddItemWithPessoa}
buttonIsLoading={isSaving}
isOpen={tServicoPedidoController.isPessoaFormOpen}
tipoPessoa={tServicoPedidoController.selectedPessoaTipo}
onClose={tServicoPedidoController.handleClosePessoaForm}
onSave={tServicoPedidoController.handleAddItemWithPessoa}
buttonIsLoading={tServicoPedidoController.isSaving}
/>
)}
</Form>

View file

@ -1,14 +1,16 @@
'use client';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { FieldValues, Path, PathValue, UseFormSetValue } from 'react-hook-form';
import { GCalculoServicoService } from '@/packages/administrativo/services/GCalculo/GCalculoServicoService';
import TServicoPedidoItemLocalPrepare from '@/packages/servicos/actions/TServicoPedidoItem/TServicoPedidoItemLocalPrepare';
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>,
) {
@ -17,8 +19,9 @@ export function useTServicoItemPedidoCalculoHook<TFormValues extends FieldValues
const [TServicoItemPedido, setTServicoItemPedido] = useState<
TServicoItemPedidoCalculoResponseInterface[]
>([]);
const [shouldSync, setShouldSync] = useState(false);
const addTServicoItemPedido = async (data: TServicoItemPedidoAddInterface) => {
const calculoTServicoItemPedido = async (data: TServicoItemPedidoAddInterface) => {
const payload = TServicoPedidoItemPreparePayload(data);
if (payload.status) {
@ -26,39 +29,41 @@ export function useTServicoItemPedidoCalculoHook<TFormValues extends FieldValues
return;
}
const response = await GCalculoServicoService(payload, data);
const response = await GCalculoServicoService(payload);
if (response.status) {
if (response.status != 200) {
setResponse(response);
return;
}
setTServicoItemPedido((prev) => {
const safePrev = Array.isArray(prev) ? prev : [];
const itemLocal = TServicoPedidoItemLocalPrepare(data, response);
const novoItem = response;
if (!itemLocal) return;
if (!novoItem) return safePrev;
// Atualiza apenas o estado local
setTServicoItemPedido((prev) => [...prev, itemLocal]);
const novoArray = [...safePrev, novoItem];
// Marca para sincronizar com o formulário
setShouldSync(true);
if (setValue) {
setValue(
'itens' as Path<TFormValues>,
novoArray as unknown as PathValue<TFormValues, Path<TFormValues>>,
{ shouldDirty: true },
);
}
return novoArray;
});
return response;
return itemLocal;
};
// Efeito seguro para sincronizar com o react-hook-form após o render
useEffect(() => {
if (setValue && shouldSync) {
setValue(
'itens' as Path<TFormValues>,
TServicoItemPedido as unknown as PathValue<TFormValues, Path<TFormValues>>,
{ shouldDirty: true },
);
setShouldSync(false);
}
}, [setValue, shouldSync, TServicoItemPedido]);
return {
TServicoItemPedido,
setTServicoItemPedido,
addTServicoItemPedido,
calculoTServicoItemPedido,
};
}

View file

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

View file

@ -0,0 +1,55 @@
'use client';
import { useCallback, useEffect, useState } from 'react';
import { FieldValues, Path, PathValue, UseFormSetValue } from 'react-hook-form';
import TServicoItemPedidoCalculoResponseInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoCalculoResponseInterface';
export function useTServicoItemPedidoLocalHandleHook<TFormValues extends FieldValues>(
setValue?: UseFormSetValue<TFormValues>,
) {
const [TServicoItemPedidoLocal, setLocalTServicoItemPedido] = useState<
TServicoItemPedidoCalculoResponseInterface[]
>([]);
// Estado auxiliar para indicar que o form deve ser atualizado
const [shouldSync, setShouldSync] = useState(false);
// Adiciona item localmente (apenas atualiza o state)
const localAddTServicoItemPedido = (item: TServicoItemPedidoCalculoResponseInterface) => {
setLocalTServicoItemPedido((prev) => [...prev, item]);
setShouldSync(true);
};
// Remove item por índice
const localRemoveTServicoItemPedido = useCallback((index: number) => {
setLocalTServicoItemPedido((prev) => prev.filter((_, i) => i !== index));
setShouldSync(true);
}, []);
// Limpa os itens
const localClearTServicoItemPedido = () => {
setLocalTServicoItemPedido([]);
setShouldSync(true);
};
// Efeito responsável por sincronizar o formulário depois da atualização
useEffect(() => {
if (setValue && shouldSync) {
setValue(
'itens' as Path<TFormValues>,
TServicoItemPedidoLocal as unknown as PathValue<TFormValues, Path<TFormValues>>,
{ shouldDirty: true },
);
setShouldSync(false);
}
}, [setValue, shouldSync, TServicoItemPedidoLocal]);
return {
TServicoItemPedidoLocal,
localAddTServicoItemPedido,
localRemoveTServicoItemPedido,
setLocalTServicoItemPedido,
localClearTServicoItemPedido,
};
}

View file

@ -0,0 +1,363 @@
'use client';
import { useRouter } from 'next/navigation';
import { useCallback, useEffect, useRef, useState } from 'react';
import TPessoaInterface from '@/packages/administrativo/interfaces/TPessoa/TPessoaInterface';
import { useTServicoItemPedidoCalculoHook } from '@/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoCalculoHook';
import { useTServicoItemPedidoIndexHook } from '@/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoIndexHook';
import { useTServicoItemPedidoLocalHandleHook } from '@/packages/servicos/hooks/TServicoItemPedido/useTServicoItemPedidoLocalHandleHook';
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 TServicoPedidoInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoInterface';
import { TServicoPedidoFormValues } from '@/packages/servicos/schemas/TServicoPedido/TServicoPedidoFormSchema';
import { useResponse } from '@/shared/components/response/ResponseContext';
import { StepNavigatorRef } from '@/shared/components/step/stepNavigator';
import { SituacoesEnum } from '@/shared/enums/SituacoesEnum';
import { TipoPessoaEnum } from '@/shared/enums/TipoPessoaEnum';
export default function useTServicoPedidoFormControllerHook(servico_pedido_id?: number) {
const router = useRouter();
const form = useTServicoPedidoFormHook({});
const { setValue, reset, watch } = form;
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, localRemoveTServicoItemPedido, setLocalTServicoItemPedido } = useTServicoItemPedidoLocalHandleHook(setValue);
const { calculoTServicoItemPedido } = 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: TServicoPedidoFormValues) => {
// Ativa o botão de loading
setIsSaving(true);
// Converte o tipo do formulário (Zod) para o tipo da API (Interface)
const payload: TServicoPedidoInterface = {
...data,
situacao: data.situacao as unknown as SituacoesEnum,
};
const response = await saveTServicoPedido(payload);
// 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) => {
handleAddItem(selectedTPessoa)
}, [selectedEmolumento, selectedServicoTipo]);
// 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);
}, []);
const handleRemoveLocalItem = useCallback(async (index: number) => {
localRemoveTServicoItemPedido(index)
}, []);
// Controle de itens
const handleAddItem = useCallback(async (selectedTPessoa?: TPessoaInterface) => {
setIsAdding(true);
// Prepara e valida os dados de item do pedido
const payload = {
servico_tipo: selectedServicoTipo,
emolumento: selectedEmolumento,
};
// Se existir pessoa, adiciona ao payload
if (selectedTPessoa) {
payload.pessoa = selectedTPessoa;
}
// Obtem o resultado da adição do item
const item = await calculoTServicoItemPedido(payload);
// Verifica se foi realizado o calculo
if (!item) {
setResponse({
status: 422,
detail: 'Não foi localizado item para o serviço',
})
setIsAdding(false);
return;
}
// Define índice e adiciona localmente
const index = TServicoItemPedidoLocal.length;
// Guarda o indice
item.index = index
// Adiciona o item calculo localmente
localAddTServicoItemPedido(item)
// Define os itens
form.setValue(`itens.${index}`, item);
setIsAdding(false);
}, [calculoTServicoItemPedido, 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 tipoPessoa = [TipoPessoaEnum.FISICA, TipoPessoaEnum.JURIDICA]
// Verifica se o emolumento e o tipo de serviço foram selecionados
if (!selectedServicoTipo || !selectedEmolumento) {
setResponse({
status: 422,
detail: 'Serviço e emolumento devem ser selecionados',
})
return;
}
// Verifica se deve selecionar pessoas
switch (tipoPessoa.includes(selectedServicoTipo.tipo_pessoa)) {
// Habilita o formulário
case true:
handleOpenPessoaForm(selectedServicoTipo.tipo_pessoa);
break;
// Adiciona direto
default:
handleAddItem();
break;
}
}, [selectedServicoTipo, selectedEmolumento, handleOpenPessoaForm, handleAddItem]);
const calcularTotais = useCallback(() => {
const itens = form.getValues('itens') || [];
if (!itens.length) {
setValue('valor_pedido', 0, { shouldDirty: true });
setValue('valor_pago', 0, { shouldDirty: true });
return;
}
const total = itens.reduce((acc, item) => acc + Number(item.valor ?? 0), 0);
// Atualiza sempre o valor do pedido
setValue('valor_pedido', total, { shouldDirty: true });
// Atualiza o valor pago apenas se estiver vazio, null, undefined ou 0
const valorPagoAtual = Number(form.getValues('valor_pago')) || 0;
if (!valorPagoAtual) {
setValue('valor_pago', total, { shouldDirty: true });
}
}, [form, setValue]);
// Incremente ou decrementa a quantidade
const handleChangeQtd = useCallback(
(index: number, delta: number) => {
const currentItens = form.getValues('itens') || [];
const currentItem = currentItens[index];
if (!currentItem) return;
const currentQtd = Number(currentItem.qtd ?? 1);
const newQtd = currentQtd + delta;
// Se quantidade for 0 ou menor, remove o item
if (newQtd <= 0) {
const filteredItens = currentItens.filter((_, i) => i !== index);
form.setValue('itens', filteredItens, { shouldDirty: true });
localRemoveTServicoItemPedido(index);
calcularTotais();
return;
}
const multiplier = newQtd / currentQtd;
const updatedItem = {
...currentItem,
qtd: newQtd,
emolumento: (Number(currentItem.emolumento) * multiplier).toFixed(2),
taxa_judiciaria: (Number(currentItem.taxa_judiciaria) * multiplier).toFixed(2),
fundesp: (Number(currentItem.fundesp) * multiplier).toFixed(2),
valor_iss: (Number(currentItem.valor_iss) * multiplier).toFixed(2),
valor: (Number(currentItem.valor) * multiplier).toFixed(2),
};
const updatedItens = [...currentItens];
updatedItens[index] = updatedItem;
form.setValue('itens', updatedItens, { shouldDirty: true });
calcularTotais();
},
[form, localRemoveTServicoItemPedido, calcularTotais],
);
// Dispara a busca do pedido
useEffect(() => {
// 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]);
return {
form,
ref,
isSaving,
isAdding,
isPessoaFormOpen,
isSaveConfirmOpen,
isCancelDialogOpen,
shouldKeepFormOpen,
selectedPessoaTipo,
TServicoItemPedidoLocal,
TServicoPedidoParams,
// setters diretos
setIsSaveConfirmOpen,
setIsCancelDialogOpen,
setShouldKeepFormOpen,
// handlers principais
handleSavePedido,
handleSelectServicoTipo,
handleSubmitWithConfirmation,
handleCloseSaveConfirm,
handleOpenCancelDialog,
handleConfirmCancel,
handleCloseCancelDialog,
handleClosePessoaForm,
handleAddItemWithPessoa,
handleOpenSaveConfirm,
handleChangeQtd
};
}

View file

@ -1,12 +1,13 @@
import { UseFormReturn } from 'react-hook-form';
import TServicoItemPedidoAddResponseInterface from '@/packages/servicos/interfaces/TServicoItemPedido/TServicoItemPedidoCalculoResponseInterface';
import { TServicoPedidoFormValues } from '@/packages/servicos/schemas/TServicoPedido/TServicoPedidoFormSchema';
import GConfigInterface from '@/shared/interfaces/GConfigInterface';
import { TServicoPedidoFormValues } from '../../schemas/TServicoPedido/TServicoPedidoFormSchema';
export default interface TServicoItemPedidoFormTableInterface {
data: TServicoItemPedidoAddResponseInterface[];
form: UseFormReturn<TServicoPedidoFormValues>;
params: GConfigInterface[];
handleChangeQtd: any;
}

View file

@ -0,0 +1,4 @@
export const TipoPessoaEnum = {
FISICA: "F",
JURIDICA: "J"
} as const