[MVPTN-37] feat(Item): Finaliza o vinculo de pessoa ao item de pedido
This commit is contained in:
parent
b85cd6aeb9
commit
6fe6c86b5d
13 changed files with 506 additions and 131 deletions
51
package-lock.json
generated
51
package-lock.json
generated
|
|
@ -22,7 +22,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-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
|
|
@ -2132,12 +2132,12 @@
|
|||
}
|
||||
},
|
||||
"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==",
|
||||
"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.3"
|
||||
"@radix-ui/react-primitive": "2.1.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
|
|
@ -2154,6 +2154,47 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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": "*",
|
||||
"@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-slot": {
|
||||
"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"
|
||||
},
|
||||
"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-slot": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,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-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
Frame,
|
||||
GalleryVerticalEnd,
|
||||
HouseIcon,
|
||||
SquareMousePointer,
|
||||
SquareTerminal,
|
||||
UsersIcon
|
||||
} from 'lucide-react';
|
||||
|
|
@ -46,7 +47,7 @@ const data = {
|
|||
{
|
||||
title: 'Servicos',
|
||||
url: '#',
|
||||
icon: UsersIcon,
|
||||
icon: SquareMousePointer,
|
||||
isActive: false,
|
||||
items: [
|
||||
{
|
||||
|
|
|
|||
193
src/components/ui/item.tsx
Normal file
193
src/components/ui/item.tsx
Normal 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,
|
||||
}
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ 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';
|
||||
|
|
@ -13,8 +14,26 @@ import TPessoaInterface from '../../interfaces/TPessoa/TPessoaInterface';
|
|||
/**
|
||||
* Função para criar a definição das colunas da tabela
|
||||
*/
|
||||
export function TPessoaTableFormColumnsDialog(): ColumnDef<TPessoaInterface>[] {
|
||||
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',
|
||||
|
|
|
|||
|
|
@ -10,19 +10,17 @@ import {
|
|||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { useTPessoaFisicaIndexHook } from '@/packages/administrativo/hooks/TPessoa/TPessoaFisica/useTPessoaFisicaIndexHook';
|
||||
import { useTPessoaRepresentanteFormHook } from '@/packages/administrativo/hooks/TPessoaRepresentante/useTPessoaRepresentanteFormHook';
|
||||
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 { useTPessoaJuridicaIndexHook } from '../../hooks/TPessoa/TPessoaJuridica/useTPessoaJuridicaIndexHook';
|
||||
import { TPessoaTableFormColumnsDialog } from './TPessoaTableFormColumnsDialog';
|
||||
|
||||
|
||||
export default function TPessoaTableFormDialog({
|
||||
isOpen,
|
||||
tipoPessoa,
|
||||
|
|
@ -34,9 +32,9 @@ export default function TPessoaTableFormDialog({
|
|||
const { tPessoaFisica, fetchTPessoaFisica } = useTPessoaFisicaIndexHook();
|
||||
const { tPessoaJuridica, fetchTPessoaJuridica } = useTPessoaJuridicaIndexHook();
|
||||
const [pessoas, setPessoas] = useState<any>()
|
||||
const [selectedTPessoa, setSelectedTPessoa] = useState<TPessoaInterface | null>(null);
|
||||
|
||||
const form = useTPessoaRepresentanteFormHook();
|
||||
|
||||
// Executa o Hook de Acordo com o tipo de pessoa informado
|
||||
const loadData = async (tipoPessoa: string) => {
|
||||
|
||||
switch (tipoPessoa) {
|
||||
|
|
@ -55,29 +53,34 @@ export default function TPessoaTableFormDialog({
|
|||
|
||||
};
|
||||
|
||||
// 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();
|
||||
const columns = TPessoaTableFormColumnsDialog(setSelectedTPessoa);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
|
|
@ -91,37 +94,34 @@ export default function TPessoaTableFormDialog({
|
|||
<DialogTitle>Pessoa</DialogTitle>
|
||||
<DialogDescription>Busque a pessoa desejada</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSave)} className="space-y-6">
|
||||
<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="Salvar"
|
||||
textLoading="Aguarde..."
|
||||
loading={buttonIsLoading}
|
||||
type="submit"
|
||||
/>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
<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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { TPessoaRepresentanteFormValues } from '../../schemas/TPessoaRepresentante/TPessoaRepresentanteSchema';
|
||||
|
||||
export default interface TPessoaTableFormInterface {
|
||||
isOpen: boolean;
|
||||
tipoPessoa: string;
|
||||
onClose: (item: null, isFormStatus: boolean) => void;
|
||||
onSave: (data: TPessoaRepresentanteFormValues) => void;
|
||||
onSave: (data: any) => void;
|
||||
buttonIsLoading: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,13 +83,10 @@ export default function TServicoItemPedidoFormColumns(): ColumnDef<TServicoItemP
|
|||
const value = Number(row.getValue('quantidade') ?? 1);
|
||||
const min = 0;
|
||||
const max = 999;
|
||||
|
||||
const updateData = (table.options.meta as TableMeta | undefined)?.updateData;
|
||||
|
||||
const setNext = (next: number) => {
|
||||
if (updateData) updateData(row.index, 'quantidade', next);
|
||||
};
|
||||
|
||||
const dec = () => setNext(Math.max(min, value - 1));
|
||||
const inc = () => setNext(Math.min(max, value + 1));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { CreditCard, Package, UserSquare2 } from 'lucide-react';
|
||||
import { CreditCard, Package, TrashIcon, UserSquare2 } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
|
@ -16,6 +16,7 @@ import {
|
|||
FormMessage
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Item, ItemActions, ItemContent, ItemDescription, ItemTitle } from '@/components/ui/item';
|
||||
import GEmolumentoServicoSelect from '@/packages/administrativo/components/GEmolumento/GEmolumentoServicoSelect';
|
||||
import GUsuarioSelect from '@/packages/administrativo/components/GUsuario/GUsuarioSelect';
|
||||
import TPessoaTableFormDialog from '@/packages/administrativo/components/TPessoa/TPessoaTableFormDialog';
|
||||
|
|
@ -62,7 +63,6 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido
|
|||
* Fecha o formulário e limpa o andamento selecionado
|
||||
*/
|
||||
const handleCloseForm = useCallback(() => {
|
||||
console.log('handleCloseForm');
|
||||
setIsFormOpen(false);
|
||||
}, []);
|
||||
|
||||
|
|
@ -123,28 +123,101 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido
|
|||
|
||||
const handleAddItem = async () => {
|
||||
|
||||
const servicoEEmolumento = {
|
||||
|
||||
servico_tipo: servicoAtual,
|
||||
emolumento: emolumentoAtual,
|
||||
|
||||
};
|
||||
|
||||
// Adiciona o item remotamente (se for necessário)
|
||||
const newItem = await addTServicoItemPedido(servicoEEmolumento);
|
||||
|
||||
// Garante que o item seja adicionado no estado local (para a tabela renderizar)
|
||||
if (newItem) localAddTServicoItemPedido(newItem);
|
||||
|
||||
};
|
||||
|
||||
const handleAddItemAndTPessoa = async (selectedTPessoa: any) => {
|
||||
// Monta o payload principal com serviço e emolumento
|
||||
const servicoEEmolumento = {
|
||||
servico_tipo: servicoAtual,
|
||||
emolumento: emolumentoAtual,
|
||||
};
|
||||
|
||||
// Adiciona o item remotamente (se for necessário)
|
||||
const newItem = await addTServicoItemPedido(servicoEEmolumento);
|
||||
|
||||
// Atribui dados principais da pessoa
|
||||
newItem.pessoa_id = selectedTPessoa.pessoa_id;
|
||||
|
||||
// Cria a subview (conteúdo dinâmico a ser renderizado abaixo da linha)
|
||||
newItem.subview = (
|
||||
|
||||
<div className="p-5">
|
||||
|
||||
<Item variant="outline">
|
||||
|
||||
<ItemContent>
|
||||
|
||||
<ItemTitle>
|
||||
|
||||
{selectedTPessoa.cpf_cnpj} - {selectedTPessoa.nome}
|
||||
|
||||
</ItemTitle>
|
||||
|
||||
<ItemDescription>
|
||||
|
||||
{selectedTPessoa.email}
|
||||
|
||||
</ItemDescription>
|
||||
|
||||
</ItemContent>
|
||||
|
||||
<ItemActions>
|
||||
|
||||
<Button variant="outline" size="sm">
|
||||
|
||||
<TrashIcon />
|
||||
|
||||
</Button>
|
||||
|
||||
</ItemActions>
|
||||
|
||||
</Item>
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
|
||||
// Adiciona o item no estado local (para renderizar na tabela)
|
||||
if (newItem) {
|
||||
localAddTServicoItemPedido(newItem);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSelectTServicoTipo = async () => {
|
||||
|
||||
if (servicoAtual && emolumentoAtual) {
|
||||
|
||||
// const servicoEEmolumento = {
|
||||
// Verifica se deve selecionar a pessoa
|
||||
if (servicoAtual.tipo_pessoa) {
|
||||
|
||||
// servico_tipo: servicoAtual,
|
||||
// emolumento: emolumentoAtual,
|
||||
setServicoTipoPessoa(servicoAtual.tipo_pessoa)
|
||||
|
||||
// };
|
||||
setIsFormOpen(true);
|
||||
|
||||
// // Adiciona o item remotamente (se for necessário)
|
||||
// const newItem = await addTServicoItemPedido(servicoEEmolumento);
|
||||
}
|
||||
else {
|
||||
|
||||
// // Garante que o item seja adicionado no estado local (para a tabela renderizar)
|
||||
// if (newItem) localAddTServicoItemPedido(newItem);
|
||||
handleAddItem()
|
||||
|
||||
setServicoTipoPessoa(servicoAtual.tipo_pessoa)
|
||||
|
||||
setIsFormOpen(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
const sections: StepSection[] = [
|
||||
{ key: 'pedido', id: 'selectPedido', icon: <Package className="h-4 w-4" />, title: 'Pedido', description: 'Dados gerais do pedido.' },
|
||||
|
|
@ -368,7 +441,7 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido
|
|||
/>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-12 md:col-span-12 text-end">
|
||||
<Button type="button" onClick={handleAddItem}>
|
||||
<Button type="button" onClick={handleSelectTServicoTipo}>
|
||||
+ Adicionar
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -496,17 +569,17 @@ export default function TServicoPedidoForm({ servico_pedido_id }: TServicoPedido
|
|||
</main>
|
||||
</div>
|
||||
</form>
|
||||
{/* Formulário de criação/edição */}
|
||||
{isFormOpen && (
|
||||
<TPessoaTableFormDialog
|
||||
isOpen={isFormOpen}
|
||||
tipoPessoa={servicoTipoPessoa}
|
||||
onClose={handleCloseForm}
|
||||
onSave={handleAddItemAndTPessoa}
|
||||
buttonIsLoading={buttonIsLoading}
|
||||
/>
|
||||
)}
|
||||
</Form>
|
||||
{/* Formulário de criação/edição */}
|
||||
{isFormOpen && (
|
||||
<TPessoaTableFormDialog
|
||||
isOpen={isFormOpen}
|
||||
tipoPessoa={servicoTipoPessoa}
|
||||
onClose={handleCloseForm}
|
||||
onSave={handleSave}
|
||||
buttonIsLoading={buttonIsLoading}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -8,19 +8,19 @@ export const TServicoPedidoSchema = z.object({
|
|||
valor_pedido: z.number().optional(),
|
||||
valor_pago: z.number().optional(),
|
||||
usuario_id: z.number().optional(),
|
||||
data_pedido: z.string().optional(),
|
||||
data_pedido: z.union([z.string(), z.null()]),
|
||||
mensalista_livrocaixa_id: z.union([z.number(), z.null()]),
|
||||
observacao: z.union([z.string(), z.null()]),
|
||||
escrevente_id: z.number(),
|
||||
situacao: z.string().optional(),
|
||||
estornado: z.string().optional(),
|
||||
estornado: z.union([z.string(), z.null()]),
|
||||
nfse_id: z.union([z.number(), z.null()]),
|
||||
apresentante: z.string().optional(),
|
||||
cpfcnpj_apresentante: z.string().optional(),
|
||||
selo_pessoa_nome: z.string().optional(),
|
||||
selo_pessoa_cpfcnpj: z.string().optional(),
|
||||
login: z.string().optional(),
|
||||
funcao: z.string().optional(),
|
||||
funcao: z.union([z.string(), z.null()]),
|
||||
itens: z.array(TServicoItemPedidoSchema).default([]),
|
||||
servico_tipo_id: z.object().optional(),
|
||||
emolumento_id: z.object().optional(),
|
||||
|
|
|
|||
|
|
@ -35,7 +35,19 @@ import {
|
|||
|
||||
import DataTableInterface from './interfaces/DataTableInterface';
|
||||
|
||||
export function DataTable<TData>({
|
||||
/**
|
||||
* DataTable genérico com suporte a subvisões dinâmicas (subtabelas ou detalhes).
|
||||
* O conteúdo extra pode ser definido dinamicamente por linha, em `row.original.subview`.
|
||||
*
|
||||
* Exemplo de item:
|
||||
* {
|
||||
* id: 1,
|
||||
* descricao: 'Item principal',
|
||||
* valor: 100,
|
||||
* subview: <CustomComponente detalhes={...} />,
|
||||
* }
|
||||
*/
|
||||
export function DataTable<TData extends { subview?: React.ReactNode | (() => React.ReactNode) }>({
|
||||
data,
|
||||
columns,
|
||||
filterColumn,
|
||||
|
|
@ -44,7 +56,6 @@ export function DataTable<TData>({
|
|||
onDelete,
|
||||
onRowClick,
|
||||
}: DataTableInterface<TData>) {
|
||||
// Garante que data sempre seja array
|
||||
const safeData = Array.isArray(data) ? data : [];
|
||||
|
||||
// Estados internos da tabela
|
||||
|
|
@ -53,7 +64,7 @@ export function DataTable<TData>({
|
|||
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
|
||||
|
||||
// Configuração da tabela
|
||||
// Cria a tabela
|
||||
const table = useReactTable({
|
||||
data: safeData,
|
||||
columns: [
|
||||
|
|
@ -66,12 +77,20 @@ export function DataTable<TData>({
|
|||
cell: ({ row }: any) => (
|
||||
<div className="flex gap-2">
|
||||
{onEdit && (
|
||||
<Button variant="ghost" size="sm" onClick={() => onEdit(row.original)}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onEdit(row.original)}
|
||||
>
|
||||
Editar
|
||||
</Button>
|
||||
)}
|
||||
{onDelete && (
|
||||
<Button variant="destructive" size="sm" onClick={() => onDelete(row.original)}>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => onDelete(row.original)}
|
||||
>
|
||||
Excluir
|
||||
</Button>
|
||||
)}
|
||||
|
|
@ -81,12 +100,7 @@ export function DataTable<TData>({
|
|||
]
|
||||
: []),
|
||||
],
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
},
|
||||
state: { sorting, columnFilters, columnVisibility, rowSelection },
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
|
|
@ -99,16 +113,22 @@ export function DataTable<TData>({
|
|||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Filtros e colunas */}
|
||||
{/* 🔍 Filtros e colunas */}
|
||||
<div className="flex items-center gap-2">
|
||||
{filterColumn && (
|
||||
<Input
|
||||
placeholder={filterPlaceholder}
|
||||
value={(table.getColumn(filterColumn)?.getFilterValue() as string) ?? ''}
|
||||
onChange={(e) => table.getColumn(filterColumn)?.setFilterValue(e.target.value)}
|
||||
value={
|
||||
(table.getColumn(filterColumn)?.getFilterValue() as string) ?? ''
|
||||
}
|
||||
onChange={(e) =>
|
||||
table.getColumn(filterColumn)?.setFilterValue(e.target.value)
|
||||
}
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Menu de colunas visíveis */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto cursor-pointer">
|
||||
|
|
@ -133,9 +153,11 @@ export function DataTable<TData>({
|
|||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
{/* Tabela */}
|
||||
|
||||
{/* 🧱 Tabela principal */}
|
||||
<div className="overflow-hidden rounded-md border">
|
||||
<Table>
|
||||
{/* Cabeçalho */}
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
|
|
@ -143,30 +165,64 @@ export function DataTable<TData>({
|
|||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
|
||||
{/* Corpo */}
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
className={onRowClick ? 'hover:bg-muted/50 cursor-pointer' : ''}
|
||||
onClick={() => onRowClick?.(row.original)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
table.getRowModel().rows.map((row) => {
|
||||
const subview =
|
||||
typeof row.original.subview === 'function'
|
||||
? row.original.subview()
|
||||
: row.original.subview;
|
||||
|
||||
return (
|
||||
<React.Fragment key={row.id}>
|
||||
{/* Linha principal */}
|
||||
<TableRow
|
||||
className={
|
||||
onRowClick ? 'hover:bg-muted/50 cursor-pointer' : ''
|
||||
}
|
||||
onClick={() => onRowClick?.(row.original)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
|
||||
{/* Subview dinâmica (qualquer conteúdo) */}
|
||||
{subview && (
|
||||
<TableRow className="bg-muted/10">
|
||||
<TableCell
|
||||
colSpan={row.getVisibleCells().length}
|
||||
className="p-0"
|
||||
>
|
||||
{subview}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={table.getAllColumns().length} className="h-24 text-center">
|
||||
<TableCell
|
||||
colSpan={table.getAllColumns().length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
Nenhum resultado encontrado.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
@ -174,10 +230,12 @@ export function DataTable<TData>({
|
|||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
{/* Paginação */}
|
||||
|
||||
{/* 📄 Paginação */}
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<span className="text-muted-foreground text-sm">
|
||||
Página {table.getState().pagination.pageIndex + 1} de {table.getPageCount()}
|
||||
Página {table.getState().pagination.pageIndex + 1} de{' '}
|
||||
{table.getPageCount()}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
|
|
@ -185,8 +243,6 @@ export function DataTable<TData>({
|
|||
size="sm"
|
||||
onClick={() => table.setPageIndex(0)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
aria-label="Primeira página"
|
||||
className="cursor-pointer"
|
||||
type="button"
|
||||
>
|
||||
Primeira
|
||||
|
|
@ -196,8 +252,6 @@ export function DataTable<TData>({
|
|||
size="sm"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
aria-label="Página anterior"
|
||||
className="cursor-pointer"
|
||||
type="button"
|
||||
>
|
||||
<ChevronLeftIcon className="h-4 w-4" />
|
||||
|
|
@ -208,8 +262,6 @@ export function DataTable<TData>({
|
|||
size="sm"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
aria-label="Próxima página"
|
||||
className="cursor-pointer"
|
||||
type="button"
|
||||
>
|
||||
Próxima
|
||||
|
|
@ -218,10 +270,10 @@ export function DataTable<TData>({
|
|||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
onClick={() =>
|
||||
table.setPageIndex(table.getPageCount() - 1)
|
||||
}
|
||||
disabled={!table.getCanNextPage()}
|
||||
aria-label="Última página"
|
||||
className="cursor-pointer"
|
||||
type="button"
|
||||
>
|
||||
Última
|
||||
|
|
|
|||
|
|
@ -41,4 +41,4 @@ const LoadingButton = forwardRef<HTMLButtonElement, LoadingButtonProps>(
|
|||
|
||||
LoadingButton.displayName = 'LoadingButton';
|
||||
|
||||
export default LoadingButton;
|
||||
export default LoadingButton;
|
||||
Loading…
Add table
Reference in a new issue