[MVPTN-37] feat(Pedido): Ajusta o forumlário para utilizar o endpoint para salvar o pedido
This commit is contained in:
parent
e50818e52a
commit
06d55ec125
13 changed files with 419 additions and 355 deletions
29
package-lock.json
generated
29
package-lock.json
generated
|
|
@ -124,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",
|
||||
|
|
@ -2972,6 +2973,7 @@
|
|||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
|
|
@ -2982,6 +2984,7 @@
|
|||
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
|
|
@ -2998,6 +3001,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",
|
||||
|
|
@ -3028,6 +3032,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",
|
||||
|
|
@ -3502,6 +3507,7 @@
|
|||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -3854,6 +3860,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.9",
|
||||
"caniuse-lite": "^1.0.30001746",
|
||||
|
|
@ -4658,6 +4665,7 @@
|
|||
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
|
|
@ -4718,6 +4726,7 @@
|
|||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
|
|
@ -4844,6 +4853,7 @@
|
|||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
|
|
@ -6998,6 +7008,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",
|
||||
|
|
@ -7394,6 +7405,7 @@
|
|||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
|
|
@ -7551,6 +7563,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"
|
||||
}
|
||||
|
|
@ -7560,6 +7573,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"
|
||||
},
|
||||
|
|
@ -7572,6 +7586,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"
|
||||
},
|
||||
|
|
@ -7587,7 +7602,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",
|
||||
|
|
@ -7600,6 +7616,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"
|
||||
|
|
@ -7718,7 +7735,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",
|
||||
|
|
@ -8478,6 +8496,7 @@
|
|||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
|
@ -8489,7 +8508,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",
|
||||
|
|
@ -8643,6 +8663,7 @@
|
|||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
|
@ -8708,6 +8729,7 @@
|
|||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"napi-postinstall": "^0.3.0"
|
||||
},
|
||||
|
|
@ -8994,6 +9016,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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export default function TServicoItemPedidoList({ items }: TServicoItemPedidoList
|
|||
<CardContent>
|
||||
{/* Altura máxima + scroll vertical */}
|
||||
<div className="space-y-4 max-h-[60vh] overflow-y-auto pr-1">
|
||||
{items.map((item) => (
|
||||
{items?.map((item) => (
|
||||
<div
|
||||
key={item.servico_itempedido_id}
|
||||
className="bg-cart-item border-cart-border flex items-start gap-4 rounded-lg border p-4"
|
||||
|
|
|
|||
|
|
@ -7,16 +7,16 @@ import { useCallback, useEffect } 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 { useTServicoItemPedidoIndexHook } from '../../hooks/TServicoItemPedido/useTServicoItemPedidoIndexHook';
|
||||
import { useTServicoPedidoShowHook } from '../../hooks/TServicoPedido/useTServicoPedidoShowHook';
|
||||
import TServicoItemPedidoList from '../TServicoItemPedido/TServicoItemPedidoList';
|
||||
import TServicoPedidoDetailsPagamento from './TServicoPedidoDetailsPagamento';
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import { CreditCard, Package, UserSquare2 } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
|
@ -14,239 +16,73 @@ import {
|
|||
FormMessage
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { cn } from '@/lib/utils';
|
||||
import GUsuarioSelect from '@/packages/administrativo/components/GUsuario/GUsuarioSelect';
|
||||
import TServicoTipoSelect from '@/packages/administrativo/components/TServicoTipo/TServicoTipoSelect';
|
||||
import { useTServicoPedidoFormHook } from '@/packages/servicos/hooks/TServicoPedido/useTServicoPedidoFormHook';
|
||||
import { ResetFormIfData } from '@/shared/actions/form/ResetFormIfData';
|
||||
import { useTServicoPedidoSaveHook } from '@/packages/servicos/hooks/TServicoPedido/useTServicoPedidoSaveHook';
|
||||
import TServicoPedidoInterface from '@/packages/servicos/interfaces/TServicoPedido/TServicoPedidoInterface';
|
||||
import { parseNumberInput } from '@/shared/actions/form/parseNumberInput';
|
||||
import LoadingButton from '@/shared/components/loadingButton/LoadingButton';
|
||||
import { StepNavigator, StepNavigatorRef, StepSection } from '@/shared/components/step/stepNavigator';
|
||||
import TipoPagamentoSelect from '@/shared/components/tipoPagamento/TipoPagamentoSelect';
|
||||
|
||||
import TServicoItemPedidoFormTable from '../TServicoItemPedido/TServicoItemPedidoFormTable';
|
||||
|
||||
type StepKey = 'pedido' | 'servicoPedidoItem' | 'payment';
|
||||
|
||||
const SECTION_ORDER: { key: StepKey; id: string }[] = [
|
||||
{ key: 'pedido', id: 'selectPedido' },
|
||||
{ key: 'servicoPedidoItem', id: 'selectServicoPedidoItem' },
|
||||
{ key: 'payment', id: 'selectPayment' },
|
||||
];
|
||||
|
||||
const SCROLL_OFFSET = 16;
|
||||
const SPY_LOCK_MS = 600;
|
||||
|
||||
export default function TServicoPedidoForm() {
|
||||
const data = {};
|
||||
|
||||
// Controle de rotas
|
||||
const router = useRouter();
|
||||
|
||||
// Controle de estado do botão
|
||||
const [buttonIsLoading, setButtonIsLoading] = useState(false);
|
||||
const { TServicoPedido, saveTServicoPedido } = useTServicoPedidoSaveHook()
|
||||
|
||||
const form = useTServicoPedidoFormHook({});
|
||||
|
||||
const [active, setActive] = React.useState<StepKey>('pedido');
|
||||
const sections: StepSection[] = [
|
||||
{ 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.' },
|
||||
];
|
||||
|
||||
const ref = React.useRef<StepNavigatorRef>(null);
|
||||
|
||||
// submit e error do RHF
|
||||
function onSubmit(values: any) {
|
||||
console.log('Submit:', values);
|
||||
}
|
||||
const onSave = useCallback(
|
||||
|
||||
async (formData: TServicoPedidoInterface) => {
|
||||
|
||||
console.log(formData);
|
||||
|
||||
// Coloca o botão em estado de loading
|
||||
setButtonIsLoading(true);
|
||||
|
||||
// Aguarda salvar o registro
|
||||
const response = await saveTServicoPedido(formData);
|
||||
|
||||
// Verifica se pode redirecionar o usuário
|
||||
if (response.servico_pedido_id > 0) {
|
||||
|
||||
// Redirecionamento de página
|
||||
router.replace('/servicos/balcao/detalhes/' + response.servico_pedido_id)
|
||||
}
|
||||
|
||||
// Remove o botão em estado de loading
|
||||
setButtonIsLoading(false);
|
||||
|
||||
},
|
||||
[saveTServicoPedido, TServicoPedido],
|
||||
);
|
||||
|
||||
function onError(error: any) {
|
||||
console.log('Erro no formulário:', error);
|
||||
}
|
||||
|
||||
// === Bloqueio do scroll-spy durante rolagem programática ===
|
||||
const spyLockedRef = React.useRef(false);
|
||||
const spyTimerRef = React.useRef<number | null>(null);
|
||||
|
||||
const lockSpy = React.useCallback(() => {
|
||||
spyLockedRef.current = true;
|
||||
if (spyTimerRef.current) window.clearTimeout(spyTimerRef.current);
|
||||
spyTimerRef.current = window.setTimeout(() => {
|
||||
spyLockedRef.current = false;
|
||||
}, SPY_LOCK_MS) as unknown as number;
|
||||
}, []);
|
||||
|
||||
const scrollToSection = React.useCallback(
|
||||
(id: string) => {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
lockSpy();
|
||||
const y = el.getBoundingClientRect().top + window.scrollY - SCROLL_OFFSET;
|
||||
window.scrollTo({ top: y, behavior: 'smooth' });
|
||||
},
|
||||
[lockSpy]
|
||||
);
|
||||
|
||||
// Scroll spy
|
||||
React.useEffect(() => {
|
||||
const handler = () => {
|
||||
if (spyLockedRef.current) return;
|
||||
|
||||
let current: StepKey = 'pedido';
|
||||
let best = Number.POSITIVE_INFINITY;
|
||||
|
||||
for (const { key, id } of SECTION_ORDER) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) continue;
|
||||
const dist = Math.abs(el.getBoundingClientRect().top - SCROLL_OFFSET - 8);
|
||||
if (dist < best) {
|
||||
best = dist;
|
||||
current = key;
|
||||
}
|
||||
}
|
||||
if (current !== active) setActive(current);
|
||||
};
|
||||
|
||||
handler();
|
||||
window.addEventListener('scroll', handler, { passive: true });
|
||||
return () => window.removeEventListener('scroll', handler);
|
||||
}, [active]);
|
||||
|
||||
// Atualiza o formulário quando recebe dados para edição
|
||||
React.useEffect(() => {
|
||||
ResetFormIfData(form, data);
|
||||
}, [data, form]);
|
||||
|
||||
const dataItens = [
|
||||
{
|
||||
'emolumento': 4.99,
|
||||
'fundesp': 1.21,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 0.25,
|
||||
'valor': 6.45,
|
||||
'servico': 'Autenticação Original',
|
||||
'tabela': 'Autenticação por página'
|
||||
},
|
||||
{
|
||||
'emolumento': 3.50,
|
||||
'fundesp': 0.85,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 0.18,
|
||||
'valor': 4.53,
|
||||
'servico': 'Autenticação de Cópia',
|
||||
'tabela': 'Autenticação por página'
|
||||
},
|
||||
{
|
||||
'emolumento': 5.90,
|
||||
'fundesp': 1.20,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 0.30,
|
||||
'valor': 7.40,
|
||||
'servico': 'Reconhecimento de Firma (Semelhança)',
|
||||
'tabela': 'Reconhecimento de firma'
|
||||
},
|
||||
{
|
||||
'emolumento': 8.50,
|
||||
'fundesp': 1.75,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 0.43,
|
||||
'valor': 10.68,
|
||||
'servico': 'Reconhecimento de Firma (Autenticidade)',
|
||||
'tabela': 'Reconhecimento de firma'
|
||||
},
|
||||
{
|
||||
'emolumento': 12.00,
|
||||
'fundesp': 2.50,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 0.60,
|
||||
'valor': 15.10,
|
||||
'servico': 'Abertura de Firma',
|
||||
'tabela': 'Ficha de assinatura'
|
||||
},
|
||||
{
|
||||
'emolumento': 18.00,
|
||||
'fundesp': 3.60,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 0.90,
|
||||
'valor': 22.50,
|
||||
'servico': 'Procuração Particular',
|
||||
'tabela': 'Procuração por folha'
|
||||
},
|
||||
{
|
||||
'emolumento': 35.00,
|
||||
'fundesp': 7.00,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 1.75,
|
||||
'valor': 43.75,
|
||||
'servico': 'Procuração Pública',
|
||||
'tabela': 'Procuração (tabela geral)'
|
||||
},
|
||||
{
|
||||
'emolumento': 20.00,
|
||||
'fundesp': 4.00,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 1.00,
|
||||
'valor': 25.00,
|
||||
'servico': 'Certidão Simples',
|
||||
'tabela': 'Certidões'
|
||||
},
|
||||
{
|
||||
'emolumento': 45.00,
|
||||
'fundesp': 9.00,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 2.25,
|
||||
'valor': 56.25,
|
||||
'servico': 'Certidão em Inteiro Teor',
|
||||
'tabela': 'Certidões'
|
||||
},
|
||||
{
|
||||
'emolumento': 80.00,
|
||||
'fundesp': 16.00,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 4.00,
|
||||
'valor': 100.00,
|
||||
'servico': 'Ata Notarial',
|
||||
'tabela': 'Ata por página'
|
||||
},
|
||||
{
|
||||
'emolumento': 7.50,
|
||||
'fundesp': 1.50,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 0.38,
|
||||
'valor': 9.38,
|
||||
'servico': 'Testemunha em Documento',
|
||||
'tabela': 'Outras declarações'
|
||||
},
|
||||
{
|
||||
'emolumento': 6.20,
|
||||
'fundesp': 1.24,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 0.31,
|
||||
'valor': 7.75,
|
||||
'servico': 'Autenticação de Documento Eletrônico',
|
||||
'tabela': 'Autenticação digital'
|
||||
},
|
||||
{
|
||||
'emolumento': 25.00,
|
||||
'fundesp': 5.00,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 1.25,
|
||||
'valor': 31.25,
|
||||
'servico': 'Reconhecimento de Sinal Público',
|
||||
'tabela': 'Sinal público'
|
||||
},
|
||||
{
|
||||
'emolumento': 10.00,
|
||||
'fundesp': 2.00,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 0.50,
|
||||
'valor': 12.50,
|
||||
'servico': 'Certificação de Cópia Digital',
|
||||
'tabela': 'Certificação digital'
|
||||
},
|
||||
{
|
||||
'emolumento': 9.90,
|
||||
'fundesp': 1.98,
|
||||
'taxa_judiciaria': 0,
|
||||
'valor_iss': 0.50,
|
||||
'valor': 12.38,
|
||||
'servico': 'Arquivamento de Documento',
|
||||
'tabela': 'Arquivamento'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className='text-4xl font-bold mb-4'>
|
||||
Pedido
|
||||
</h3>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit, onError)}>
|
||||
<form onSubmit={form.handleSubmit(onSave, onError)}>
|
||||
<div className="flex gap-4">
|
||||
{/* Sidebar - Sticky */}
|
||||
<aside className="hidden w-[360px] lg:block">
|
||||
|
|
@ -254,35 +90,13 @@ export default function TServicoPedidoForm() {
|
|||
<Card role="presentation" className="card-border">
|
||||
<CardContent>
|
||||
<nav className="flex flex-col gap-2">
|
||||
<StepLink
|
||||
active={active === 'pedido'}
|
||||
onClick={() => {
|
||||
setActive('pedido');
|
||||
scrollToSection('selectPedido');
|
||||
}}
|
||||
icon={<Package className="h-4 w-4" />}
|
||||
title="Pedido"
|
||||
description="Dados gerais do pedido."
|
||||
/>
|
||||
<StepLink
|
||||
active={active === 'servicoPedidoItem'}
|
||||
onClick={() => {
|
||||
setActive('servicoPedidoItem');
|
||||
scrollToSection('selectServicoPedidoItem');
|
||||
}}
|
||||
icon={<UserSquare2 className="h-4 w-4" />}
|
||||
title="Itens"
|
||||
description="Itens/serviços do pedido."
|
||||
/>
|
||||
<StepLink
|
||||
active={active === 'payment'}
|
||||
onClick={() => {
|
||||
setActive('payment');
|
||||
scrollToSection('selectPayment');
|
||||
}}
|
||||
icon={<CreditCard className="h-4 w-4" />}
|
||||
title="Pagamento"
|
||||
description="Forma e dados de pagamento."
|
||||
<StepNavigator
|
||||
ref={ref}
|
||||
sections={sections}
|
||||
defaultActive="pedido"
|
||||
onChange={(k) => console.log('active =>', k)}
|
||||
scrollOffset={16}
|
||||
spyLockMs={600}
|
||||
/>
|
||||
</nav>
|
||||
</CardContent>
|
||||
|
|
@ -306,7 +120,7 @@ export default function TServicoPedidoForm() {
|
|||
<div className="col-span-12 sm:col-span-6 md:col-span-12">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="apresentante"
|
||||
name="escrevente_id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel className='font-semibold'>Escrevente/Tabelião</FormLabel>
|
||||
|
|
@ -391,53 +205,6 @@ export default function TServicoPedidoForm() {
|
|||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Seção: Itens */}
|
||||
<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 w-full grid-cols-12 gap-4">
|
||||
<div className="col-span-12 sm:col-span-12 md:col-span-12">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="apresentante"
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Serviços</FormLabel>
|
||||
<TServicoTipoSelect field={field} />
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-12 md:col-span-12">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="apresentante"
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Emolumentos</FormLabel>
|
||||
<TServicoTipoSelect field={field} />
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-12 md:col-span-12">
|
||||
<TServicoItemPedidoFormTable
|
||||
data={dataItens}
|
||||
onEdit={() => { }}
|
||||
onDelete={() => { }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Seção: Pagamento */}
|
||||
<Card role="presentation" id="selectPayment" className="scroll-mt-6">
|
||||
<CardHeader>
|
||||
|
|
@ -470,7 +237,7 @@ export default function TServicoPedidoForm() {
|
|||
<div className="col-span-12 sm:col-span-12 md:col-span-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="apresentante"
|
||||
name="cpfcnpj_apresentante"
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>CPF/CNPJ Requerente</FormLabel>
|
||||
|
|
@ -485,10 +252,48 @@ export default function TServicoPedidoForm() {
|
|||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6 md:col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="valor_pedido"
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Valor do Pedido</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
onChange={(e) => field.onChange(parseNumberInput(e))}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6 md:col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="valor_pago"
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Valor Pago</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
onChange={(e) => field.onChange(parseNumberInput(e))}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-12 md:col-span-12">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="apresentante"
|
||||
name="tipo_pagamento"
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Forma Pagamento</FormLabel>
|
||||
|
|
@ -502,9 +307,12 @@ export default function TServicoPedidoForm() {
|
|||
<Button type="button" variant="outline">
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
Confirmar
|
||||
</Button>
|
||||
<LoadingButton
|
||||
text="Salvar"
|
||||
textLoading="Salvando..."
|
||||
type="submit"
|
||||
loading={buttonIsLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
@ -516,48 +324,4 @@ export default function TServicoPedidoForm() {
|
|||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ---------- Components auxiliares ---------- */
|
||||
|
||||
function StepLink({
|
||||
active,
|
||||
onClick,
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
active?: boolean;
|
||||
onClick?: () => void;
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'group w-full cursor-pointer rounded-xl p-4 text-left hover:bg-gray-100 dark:hover:bg-gray-700',
|
||||
active && 'ring-1 ring-primary/60'
|
||||
)}
|
||||
aria-current={active ? 'step' : undefined}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
'flex h-9 w-9 items-center justify-center rounded-full',
|
||||
'bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-gray-100',
|
||||
'group-hover:bg-white dark:group-hover:bg-gray-800'
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
<span className="flex flex-1 flex-col">
|
||||
<span className="font-bold">{title}</span>
|
||||
<span className="text-sm text-muted-foreground">{description}</span>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,19 @@ export function useTServicoPedidoFormHook(defaults?: Partial<TServicoPedidoFormV
|
|||
resolver: zodResolver(TServicoPedidoSchema),
|
||||
defaultValues: {
|
||||
servico_pedido_id: 0,
|
||||
valor_pedido: 0,
|
||||
valor_pago: 0,
|
||||
data_pedido: "",
|
||||
observacao: "",
|
||||
escrevente_id: 0,
|
||||
situacao: "",
|
||||
estornado: "",
|
||||
apresentante: "",
|
||||
cpfcnpj_apresentante: "",
|
||||
selo_pessoa_nome: "",
|
||||
selo_pessoa_cpfcnpj: "",
|
||||
login: "",
|
||||
funcao: "",
|
||||
...defaults,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
export default interface TServicoInterface {
|
||||
servico_pedido_id: number;
|
||||
escrevente_id: number;
|
||||
apre
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
export default interface TServicoPedidoInterface {
|
||||
servico_pedido_id?: number;
|
||||
valor_pedido?: number;
|
||||
|
|
@ -9,8 +10,8 @@ export default interface TServicoPedidoInterface {
|
|||
escrevente_id?: number;
|
||||
situacao?: string;
|
||||
estornado?: string;
|
||||
apresentante?: string;
|
||||
nfse_id?: number;
|
||||
apresentante?: string;
|
||||
cpfcnpj_apresentante?: string;
|
||||
selo_pessoa_nome?: string;
|
||||
selo_pessoa_cpfcnpj?: string;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import z from "zod";
|
||||
|
||||
|
||||
export const TServicoPedidoSchema = z.object({
|
||||
servico_pedido_id: z.number().optional(),
|
||||
valor_pedido: z.number().optional(),
|
||||
|
|
@ -11,12 +12,13 @@ export const TServicoPedidoSchema = z.object({
|
|||
escrevente_id: z.number().optional(),
|
||||
situacao: z.string().optional(),
|
||||
estornado: z.string().optional(),
|
||||
apresentante: z.string().optional(),
|
||||
nfse_id: z.number().optional(),
|
||||
apresentante: z.string().optional(),
|
||||
cpfcnpj_apresentante: z.string().optional(),
|
||||
login: z.string().optional(),
|
||||
selo_pessoa_nome: z.string().optional(),
|
||||
selo_pessoa_cpfcnpj: z.string().optional(),
|
||||
login: z.string().optional(),
|
||||
funcao: z.string().optional()
|
||||
});
|
||||
|
||||
export type TServicoPedidoFormValues = z.infer<typeof TServicoPedidoSchema>;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import React, { createContext, useContext, useState, ReactNode } from 'react';
|
||||
import React, { createContext, ReactNode, useContext, useState } from 'react';
|
||||
|
||||
interface ResponseState {
|
||||
message?: string;
|
||||
|
|
|
|||
256
src/shared/components/step/stepNavigator.tsx
Normal file
256
src/shared/components/step/stepNavigator.tsx
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils'; // ajuste o caminho conforme seu projeto
|
||||
|
||||
/** =========================
|
||||
* Constantes (com defaults)
|
||||
* ========================= */
|
||||
const DEFAULT_SCROLL_OFFSET = 16;
|
||||
const DEFAULT_SPY_LOCK_MS = 600;
|
||||
|
||||
/** ===========
|
||||
* Tipos
|
||||
* =========== */
|
||||
export type StepKey = string;
|
||||
|
||||
export type StepSection = {
|
||||
/** chave lógica do step (ex.: 'pedido') */
|
||||
key: StepKey;
|
||||
/** id do elemento alvo no DOM (ex.: 'selectPedido') */
|
||||
id: string;
|
||||
/** ícone (lucide ou outro) */
|
||||
icon: React.ReactNode;
|
||||
/** título do step */
|
||||
title: string;
|
||||
/** descrição curta do step */
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type StepNavigatorProps = {
|
||||
/** Lista de seções em ordem */
|
||||
sections: StepSection[];
|
||||
/** Ativo controlado externamente */
|
||||
active?: StepKey;
|
||||
/** Ativo padrão (modo não-controlado) */
|
||||
defaultActive?: StepKey;
|
||||
/** Callback ao mudar de seção (por scroll ou clique) */
|
||||
onChange?: (key: StepKey) => void;
|
||||
|
||||
/** Offset do topo ao calcular posicionamento (ex.: header fixo) */
|
||||
scrollOffset?: number;
|
||||
/** Tempo de bloqueio do spy após scroll programático */
|
||||
spyLockMs?: number;
|
||||
|
||||
/** Classe extra no container */
|
||||
className?: string;
|
||||
/** Se `false`, desabilita o scroll-spy */
|
||||
enableScrollSpy?: boolean;
|
||||
};
|
||||
|
||||
/** =================
|
||||
* Hook: useScrollSpy
|
||||
* ================= */
|
||||
function useScrollSpy(opts: {
|
||||
sections: StepSection[];
|
||||
active: StepKey | undefined;
|
||||
setActive: (key: StepKey) => void;
|
||||
scrollOffset: number;
|
||||
enable: boolean;
|
||||
lockMs: number;
|
||||
}) {
|
||||
const { sections, active, setActive, scrollOffset, enable, lockMs } = opts;
|
||||
|
||||
// bloqueio do spy durante rolagem programática
|
||||
const spyLockedRef = React.useRef(false);
|
||||
const spyTimerRef = React.useRef<number | null>(null);
|
||||
|
||||
const lockSpy = React.useCallback(() => {
|
||||
spyLockedRef.current = true;
|
||||
if (spyTimerRef.current) window.clearTimeout(spyTimerRef.current);
|
||||
spyTimerRef.current = window.setTimeout(() => {
|
||||
spyLockedRef.current = false;
|
||||
}, lockMs) as unknown as number;
|
||||
}, [lockMs]);
|
||||
|
||||
const scrollToId = React.useCallback(
|
||||
(id: string) => {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
lockSpy();
|
||||
const y = el.getBoundingClientRect().top + window.scrollY - scrollOffset;
|
||||
window.scrollTo({ top: y, behavior: 'smooth' });
|
||||
},
|
||||
[lockSpy, scrollOffset]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!enable) return;
|
||||
|
||||
const handler = () => {
|
||||
if (spyLockedRef.current) return;
|
||||
|
||||
let current: StepKey | undefined = undefined;
|
||||
let best = Number.POSITIVE_INFINITY;
|
||||
|
||||
for (const { key, id } of sections) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) continue;
|
||||
const dist = Math.abs(el.getBoundingClientRect().top - scrollOffset - 8);
|
||||
if (dist < best) {
|
||||
best = dist;
|
||||
current = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (current && current !== active) {
|
||||
setActive(current);
|
||||
}
|
||||
};
|
||||
|
||||
handler(); // avalia logo ao montar
|
||||
window.addEventListener('scroll', handler, { passive: true });
|
||||
return () => window.removeEventListener('scroll', handler);
|
||||
}, [enable, sections, active, scrollOffset, setActive]);
|
||||
|
||||
return { scrollToId, lockSpy };
|
||||
}
|
||||
|
||||
/** ==================
|
||||
* Subcomponente: StepLink
|
||||
* ================== */
|
||||
function StepLink({
|
||||
active,
|
||||
onClick,
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
active?: boolean;
|
||||
onClick?: () => void;
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description?: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'group w-full cursor-pointer rounded-xl p-4 text-left hover:bg-gray-100 dark:hover:bg-gray-700',
|
||||
active && 'ring-1 ring-primary/60'
|
||||
)}
|
||||
aria-current={active ? 'step' : undefined}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
'flex h-9 w-9 items-center justify-center rounded-full',
|
||||
'bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-gray-100',
|
||||
'group-hover:bg-white dark:group-hover:bg-gray-800'
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
<span className="flex flex-1 flex-col">
|
||||
<span className="font-bold">{title}</span>
|
||||
{description ? (
|
||||
<span className="text-sm text-muted-foreground">{description}</span>
|
||||
) : null}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/** ===================
|
||||
* Componente principal
|
||||
* =================== */
|
||||
export type StepNavigatorRef = {
|
||||
/** Rola até a seção pela key ou id */
|
||||
scrollTo: (target: StepKey | { id: string }) => void;
|
||||
};
|
||||
|
||||
export const StepNavigator = React.forwardRef<StepNavigatorRef, StepNavigatorProps>(
|
||||
(
|
||||
{
|
||||
sections,
|
||||
active: activeProp,
|
||||
defaultActive,
|
||||
onChange,
|
||||
scrollOffset = DEFAULT_SCROLL_OFFSET,
|
||||
spyLockMs = DEFAULT_SPY_LOCK_MS,
|
||||
className,
|
||||
enableScrollSpy = true,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
// estado controlado/ não-controlado
|
||||
const [activeState, setActiveState] = React.useState<StepKey | undefined>(
|
||||
activeProp ?? defaultActive ?? sections[0]?.key
|
||||
);
|
||||
|
||||
// sincroniza quando for controlado
|
||||
React.useEffect(() => {
|
||||
if (activeProp !== undefined) setActiveState(activeProp);
|
||||
}, [activeProp]);
|
||||
|
||||
const setActive = React.useCallback(
|
||||
(key: StepKey) => {
|
||||
if (activeProp === undefined) {
|
||||
setActiveState(key);
|
||||
}
|
||||
onChange?.(key);
|
||||
},
|
||||
[activeProp, onChange]
|
||||
);
|
||||
|
||||
const { scrollToId, lockSpy } = useScrollSpy({
|
||||
sections,
|
||||
active: activeState,
|
||||
setActive,
|
||||
scrollOffset,
|
||||
enable: enableScrollSpy,
|
||||
lockMs: spyLockMs,
|
||||
});
|
||||
|
||||
const scrollTo = React.useCallback(
|
||||
(target: StepKey | { id: string }) => {
|
||||
const id =
|
||||
typeof target === 'string'
|
||||
? sections.find((s) => s.key === target)?.id
|
||||
: target.id;
|
||||
if (!id) return;
|
||||
scrollToId(id);
|
||||
},
|
||||
[sections, scrollToId]
|
||||
);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({ scrollTo }), [scrollTo]);
|
||||
|
||||
return (
|
||||
<nav className={cn('flex flex-col gap-2', className)} aria-label="Steps">
|
||||
<ol className="flex flex-col gap-2">
|
||||
{sections.map((s) => (
|
||||
<li key={s.key}>
|
||||
<StepLink
|
||||
active={activeState === s.key}
|
||||
icon={s.icon}
|
||||
title={s.title}
|
||||
description={s.description}
|
||||
onClick={() => {
|
||||
// ao clicar, trava o spy por um curto período e rola
|
||||
lockSpy();
|
||||
setActive(s.key);
|
||||
scrollTo({ id: s.id });
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
);
|
||||
StepNavigator.displayName = 'StepNavigator';
|
||||
Loading…
Add table
Reference in a new issue