diff --git a/src/features/dashboard/header/Profile.tsx b/src/features/dashboard/header/Profile.tsx index 57c2c35..b8f4aee 100644 --- a/src/features/dashboard/header/Profile.tsx +++ b/src/features/dashboard/header/Profile.tsx @@ -9,14 +9,17 @@ import { Button, IconButton, } from '@mui/material'; -import * as dropdownData from './data'; - import { IconMail } from '@tabler/icons-react'; import { Stack } from '@mui/system'; -import Image from 'next/image'; + +import { useAuthStore } from '../../login/store/useAuthStore'; +import { useAuth } from '../../login/hooks/useAuth'; const Profile = () => { const [anchorEl2, setAnchorEl2] = useState(null); + const user = useAuthStore((s) => s.user); + const { logout } = useAuth(); + const handleClick2 = (event: any) => { setAnchorEl2(event.currentTarget); }; @@ -39,13 +42,14 @@ const Profile = () => { onClick={handleClick2} > + > + {user?.nome?.[0] || user?.userName?.[0] || 'U'} + {/* ------------------------------------------- */} {/* Message Dropdown */} @@ -68,20 +72,20 @@ const Profile = () => { User Profile + sx={{ width: 95, height: 95, bgcolor: 'primary.main' }} + > + {user?.nome?.[0] || user?.userName?.[0] || 'U'} + - Mathew Anderson + {user?.nome || user?.userName || 'Usuário'} - Designer + {user?.nomeFilial || 'Sem filial'} { gap={1} > - info@modernize.com + {user?.rca ? `RCA: ${user.rca}` : 'Sem e-mail'} - - {dropdownData.profile.map((profile) => ( - - - - - - - - - - {profile.title} - - - {profile.subtitle} - - - - - - - ))} - - - - - Unlimited
- Access -
- -
- unlimited -
-
diff --git a/src/features/login/authForms/AuthLogin.test.tsx b/src/features/login/authForms/AuthLogin.test.tsx deleted file mode 100644 index 0e342f6..0000000 --- a/src/features/login/authForms/AuthLogin.test.tsx +++ /dev/null @@ -1,210 +0,0 @@ -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import AuthLogin from './AuthLogin'; -import { useAuth } from '../hooks/useAuth'; - -// Mock do hook useAuth -jest.mock('../hooks/useAuth'); - -const createWrapper = () => { - const queryClient = new QueryClient({ - defaultOptions: { - queries: { retry: false }, - mutations: { retry: false }, - }, - }); - - return ({ children }: { children: React.ReactNode }) => ( - {children} - ); -}; - -describe('AuthLogin Component', () => { - const mockLoginMutation = { - mutate: jest.fn(), - isPending: false, - isError: false, - isSuccess: false, - error: null, - }; - - beforeEach(() => { - jest.clearAllMocks(); - (useAuth as jest.Mock).mockReturnValue({ - loginMutation: mockLoginMutation, - }); - }); - - describe('Renderização', () => { - it('deve renderizar formulário de login', () => { - render(, { wrapper: createWrapper() }); - - expect(screen.getByLabelText(/usuário/i)).toBeInTheDocument(); - expect(screen.getByLabelText(/senha/i)).toBeInTheDocument(); - expect( - screen.getByRole('button', { name: /sign in/i }) - ).toBeInTheDocument(); - }); - - it('deve renderizar título quando fornecido', () => { - render(, { wrapper: createWrapper() }); - expect(screen.getByText('Bem-vindo')).toBeInTheDocument(); - }); - - it('deve renderizar checkbox "Manter-me conectado"', () => { - render(, { wrapper: createWrapper() }); - expect(screen.getByText(/manter-me conectado/i)).toBeInTheDocument(); - }); - - it('deve renderizar link "Esqueceu sua senha"', () => { - render(, { wrapper: createWrapper() }); - const link = screen.getByText(/esqueceu sua senha/i); - expect(link).toBeInTheDocument(); - }); - }); - - describe('Validação', () => { - it('deve validar campos obrigatórios', async () => { - render(, { wrapper: createWrapper() }); - - const submitButton = screen.getByRole('button', { name: /sign in/i }); - fireEvent.click(submitButton); - - await waitFor(() => { - expect(screen.getByText(/usuário é obrigatório/i)).toBeInTheDocument(); - }); - }); - - it('deve validar senha mínima', async () => { - render(, { wrapper: createWrapper() }); - - const usernameInput = screen.getByLabelText(/usuário/i); - const passwordInput = screen.getByLabelText(/senha/i); - const submitButton = screen.getByRole('button', { name: /sign in/i }); - - fireEvent.change(usernameInput, { target: { value: 'testuser' } }); - fireEvent.change(passwordInput, { target: { value: '123' } }); - fireEvent.click(submitButton); - - await waitFor(() => { - expect( - screen.getByText(/senha deve ter no mínimo 4 caracteres/i) - ).toBeInTheDocument(); - }); - }); - }); - - describe('Submissão', () => { - it('deve submeter formulário com credenciais válidas', async () => { - render(, { wrapper: createWrapper() }); - - const usernameInput = screen.getByLabelText(/usuário/i); - const passwordInput = screen.getByLabelText(/senha/i); - const submitButton = screen.getByRole('button', { name: /sign in/i }); - - fireEvent.change(usernameInput, { target: { value: 'testuser' } }); - fireEvent.change(passwordInput, { target: { value: 'password123' } }); - fireEvent.click(submitButton); - - await waitFor(() => { - expect(mockLoginMutation.mutate).toHaveBeenCalledWith({ - username: 'testuser', - password: 'password123', - }); - }); - }); - }); - - describe('Estados de Loading e Erro', () => { - it('deve desabilitar botão durante loading', () => { - const loadingMutation = { - ...mockLoginMutation, - isPending: true, - }; - - (useAuth as jest.Mock).mockReturnValue({ - loginMutation: loadingMutation, - }); - - render(, { wrapper: createWrapper() }); - - const submitButton = screen.getByRole('button', { name: /logging in/i }); - expect(submitButton).toBeDisabled(); - }); - - it('deve mostrar mensagem de erro quando login falha', () => { - const errorMutation = { - ...mockLoginMutation, - isError: true, - error: { - response: { - data: { message: 'Credenciais inválidas' }, - }, - }, - }; - - (useAuth as jest.Mock).mockReturnValue({ - loginMutation: errorMutation, - }); - - render(, { wrapper: createWrapper() }); - - expect(screen.getByText(/credenciais inválidas/i)).toBeInTheDocument(); - }); - - // 🐛 TESTE QUE REVELA BUG: Erro não limpa durante nova tentativa - it('🐛 BUG: deve esconder erro durante nova tentativa de login', () => { - const errorAndLoadingMutation = { - ...mockLoginMutation, - isError: true, - isPending: true, // Está tentando novamente - error: { - response: { - data: { message: 'Credenciais inválidas' }, - }, - }, - }; - - (useAuth as jest.Mock).mockReturnValue({ - loginMutation: errorAndLoadingMutation, - }); - - render(, { wrapper: createWrapper() }); - - // ❌ ESTE TESTE VAI FALHAR - erro ainda aparece durante loading! - expect( - screen.queryByText(/credenciais inválidas/i) - ).not.toBeInTheDocument(); - }); - }); - - describe('🐛 Bugs Identificados', () => { - // 🐛 BUG: Link "Esqueceu senha" vai para home - it('🐛 BUG: link "Esqueceu senha" deve ir para /forgot-password', () => { - render(, { wrapper: createWrapper() }); - - const link = screen.getByText(/esqueceu sua senha/i).closest('a'); - - // ❌ ESTE TESTE VAI FALHAR - href é "/" - expect(link).toHaveAttribute('href', '/forgot-password'); - }); - - // 🐛 BUG: Checkbox não funciona - it('🐛 BUG: checkbox "Manter-me conectado" deve ser controlado', () => { - render(, { wrapper: createWrapper() }); - - const checkbox = screen.getByRole('checkbox', { - name: /manter-me conectado/i, - }); - - // Checkbox está sempre marcado - expect(checkbox).toBeChecked(); - - // Tenta desmarcar - fireEvent.click(checkbox); - - // ❌ ESTE TESTE VAI FALHAR - checkbox não muda de estado! - expect(checkbox).not.toBeChecked(); - }); - }); -}); diff --git a/src/features/login/authForms/AuthLogin.tsx b/src/features/login/authForms/AuthLogin.tsx index 09dea27..345bb46 100644 --- a/src/features/login/authForms/AuthLogin.tsx +++ b/src/features/login/authForms/AuthLogin.tsx @@ -2,8 +2,6 @@ import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Divider from '@mui/material/Divider'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import FormGroup from '@mui/material/FormGroup'; import Stack from '@mui/material/Stack'; import { TextField, Alert } from '@mui/material'; import Typography from '@mui/material/Typography'; @@ -12,7 +10,6 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { loginSchema, LoginInput, AuthLoginProps } from '../interfaces/types'; import { useAuth } from '../hooks/useAuth'; -import CustomCheckbox from '../components/forms/theme-elements/CustomCheckbox'; import CustomFormLabel from '../components/forms/theme-elements/CustomFormLabel'; import AuthSocialButtons from './AuthSocialButtons'; @@ -45,7 +42,7 @@ const AuthLogin = ({ title, subtitle, subtext }: AuthLoginProps) => { {subtext} - + { /> - - } - label="Manter-me conectado" - sx={{ whiteSpace: 'nowrap' }} - /> - { type="submit" disabled={loginMutation.isPending} > - {loginMutation.isPending ? 'Logging in...' : 'Sign In'} + {loginMutation.isPending ? 'Entrando...' : 'Entrar'} diff --git a/src/features/login/components/AuthInitializer.tsx b/src/features/login/components/AuthInitializer.tsx index 8bc8a95..93dcfd6 100644 --- a/src/features/login/components/AuthInitializer.tsx +++ b/src/features/login/components/AuthInitializer.tsx @@ -1,4 +1,5 @@ 'use client'; +import { Box, CircularProgress, Typography } from '@mui/material'; import { useEffect, useRef, useState } from 'react'; import { useAuthStore } from '../store/useAuthStore'; @@ -34,12 +35,56 @@ export function AuthInitializer({ children }: { children: React.ReactNode }) { if (isChecking) { return ( -
-
-
-

Validando acesso...

-
-
+ + + + theme.palette.grey[200], + }} + size={48} + thickness={4} + value={100} + /> + theme.palette.primary.main, + animationDuration: '550ms', + position: 'absolute', + left: 0, + [`& .MuiCircularProgress-circle`]: { + strokeLinecap: 'round', + }, + }} + size={48} + thickness={4} + /> + + + Validando acesso... + + + ); } diff --git a/src/features/orders/api/order.service.ts b/src/features/orders/api/order.service.ts index 7975011..d21f0ea 100644 --- a/src/features/orders/api/order.service.ts +++ b/src/features/orders/api/order.service.ts @@ -94,16 +94,11 @@ export const orderService = { * @returns {Promise} Array de pedidos que correspondem aos filtros */ findOrders: async (filters: OrderFilters): Promise => { - try { - const cleanParams = orderApiParamsSchema.parse(filters); - const response = await ordersApi.get('/api/v1/orders/find', { - params: cleanParams, - }); - return unwrapApiData(response, ordersResponseSchema, []); - } catch (error) { - console.error('Erro ao buscar pedidos:', error); - return []; - } + const cleanParams = orderApiParamsSchema.parse(filters); + const response = await ordersApi.get('/api/v1/orders/find', { + params: cleanParams, + }); + return unwrapApiData(response, ordersResponseSchema, []); }, /** @@ -113,13 +108,8 @@ export const orderService = { * @returns {Promise} O pedido com o ID especificado, ou null se não encontrado */ findById: async (id: number): Promise => { - try { - const response = await ordersApi.get(`/orders/${id}`); - return unwrapApiData(response, orderResponseSchema, null); - } catch (error) { - console.error(`Erro ao buscar pedido ${id}:`, error); - return null; - } + const response = await ordersApi.get(`/orders/${id}`); + return unwrapApiData(response, orderResponseSchema, null); }, /** @@ -128,13 +118,8 @@ export const orderService = { * @returns {Promise} Array de todas as lojas, ou array vazio se nenhuma for encontrada */ findStores: async (): Promise => { - try { - const response = await ordersApi.get('/api/v1/data-consult/stores'); - return unwrapApiData(response, storesResponseSchema, []); - } catch (error) { - console.error('Erro ao buscar lojas:', error); - return []; - } + const response = await ordersApi.get('/api/v1/data-consult/stores'); + return unwrapApiData(response, storesResponseSchema, []); }, /** @@ -148,35 +133,21 @@ export const orderService = { name: string ): Promise> => { if (!name || name.trim().length < 2) return []; - try { - const response = await ordersApi.get( - `/api/v1/clientes/${encodeURIComponent(name)}` - ); - return unwrapApiData(response, customersResponseSchema, []); - } catch (error) { - console.error('Erro ao buscar clientes:', error); - return []; - } + + const response = await ordersApi.get( + `/api/v1/clientes/${encodeURIComponent(name)}` + ); + return unwrapApiData(response, customersResponseSchema, []); }, - findsellers: async (): Promise => { - try { - const response = await ordersApi.get('/api/v1/data-consult/sellers'); - return unwrapApiData(response, sellersResponseSchema, []); - } catch (error) { - console.error('Erro ao buscar vendedores:', error); - return []; - } + findSellers: async (): Promise => { + const response = await ordersApi.get('/api/v1/data-consult/sellers'); + return unwrapApiData(response, sellersResponseSchema, []); }, findOrderItems: async (orderId: number): Promise => { - try { - const response = await ordersApi.get(`/api/v1/orders/itens/${orderId}`); - return unwrapApiData(response, orderItemsResponseSchema, []); - } catch (error) { - console.error(`Erro ao buscar itens do pedido ${orderId}:`, error); - return []; - } + const response = await ordersApi.get(`/api/v1/orders/itens/${orderId}`); + return unwrapApiData(response, orderItemsResponseSchema, []); }, @@ -184,42 +155,27 @@ export const orderService = { orderId: number, includeCompletedDeliveries: boolean = true ): Promise => { - try { - const response = await ordersApi.get( - `/api/v1/orders/delivery/${orderId}`, - { - params: { includeCompletedDeliveries }, - } - ); - return unwrapApiData(response, shipmentResponseSchema, []); - } catch (error) { - console.error(`Erro ao buscar entregas do pedido ${orderId}:`, error); - return []; - } + const response = await ordersApi.get( + `/api/v1/orders/delivery/${orderId}`, + { + params: { includeCompletedDeliveries }, + } + ); + return unwrapApiData(response, shipmentResponseSchema, []); }, findCargoMovement: async (orderId: number): Promise => { - try { - const response = await ordersApi.get( - `/api/v1/orders/transfer/${orderId}` - ); - return unwrapApiData(response, cargoMovementResponseSchema, []); - } catch (error) { - console.error(`Erro ao buscar movimentação de carga do pedido ${orderId}:`, error); - return []; - } + const response = await ordersApi.get( + `/api/v1/orders/transfer/${orderId}` + ); + return unwrapApiData(response, cargoMovementResponseSchema, []); }, findCuttingItems: async (orderId: number): Promise => { - try { - const response = await ordersApi.get( - `/api/v1/orders/cut-itens/${orderId}` - ); - return unwrapApiData(response, cuttingItemResponseSchema, []); - } catch (error) { - console.error(`Erro ao buscar itens de corte do pedido ${orderId}:`, error); - return []; - } + const response = await ordersApi.get( + `/api/v1/orders/cut-itens/${orderId}` + ); + return unwrapApiData(response, cuttingItemResponseSchema, []); }, diff --git a/src/features/orders/components/OrderTable.stories.tsx b/src/features/orders/components/OrderTable.stories.tsx deleted file mode 100644 index cfb70ff..0000000 --- a/src/features/orders/components/OrderTable.stories.tsx +++ /dev/null @@ -1,381 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/nextjs-vite'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import Box from '@mui/material/Box'; -import Paper from '@mui/material/Paper'; -import { DataGridPremium } from '@mui/x-data-grid-premium'; -import { ThemeProvider, createTheme } from '@mui/material/styles'; -import CssBaseline from '@mui/material/CssBaseline'; -import { LicenseInfo } from '@mui/x-license'; - -// Mock license for DataGrid Premium (for Storybook demo only) -LicenseInfo.setLicenseKey( - 'e0d9bb8070ce0054c9d9ecb6e82cb58fTz0wLEU9MzI0NzIxNDQwMDAwMDAsUz1wcmVtaXVtLExNPXBlcnBldHVhbCxLVj0y' -); - -// Mock data que simula os dados normalizados -const mockOrders = [ - { - id: '1', - orderId: 123456, - createDate: '2026-01-15T10:00:00', - customerName: 'Maria Silva', - status: 'Faturado', - orderType: 'Venda', - amount: 1250.5, - invoiceNumber: 'NF-001234', - sellerName: 'João Vendedor', - deliveryType: 'Entrega', - totalWeight: 15.5, - fatUserName: 'Admin', - deliveryLocal: 'São Paulo - SP', - masterDeliveryLocal: 'Região Sul', - deliveryPriority: 'Alta', - paymentName: 'Boleto', - partnerName: 'Parceiro ABC', - codusur2Name: 'Rep. Regional', - releaseUserName: 'Supervisor', - driver: 'Carlos Motorista', - carDescription: 'Sprinter', - carrier: 'Transportadora XYZ', - schedulerDelivery: '15/01/2026', - fatUserDescription: 'Faturamento', - emitenteNome: 'Empresa LTDA', - }, - { - id: '2', - orderId: 123457, - createDate: '2026-01-14T14:30:00', - customerName: 'João Santos', - status: 'Pendente', - orderType: 'Venda', - amount: 3450.0, - invoiceNumber: '', - sellerName: 'Ana Vendedora', - deliveryType: 'Retirada', - totalWeight: 8.2, - fatUserName: '', - deliveryLocal: 'Rio de Janeiro - RJ', - masterDeliveryLocal: 'Região Sudeste', - deliveryPriority: 'Normal', - paymentName: 'Cartão de Crédito', - partnerName: '', - codusur2Name: '', - releaseUserName: '', - driver: '', - carDescription: '', - carrier: '', - schedulerDelivery: '', - fatUserDescription: '', - emitenteNome: 'Empresa LTDA', - }, - { - id: '3', - orderId: 123458, - createDate: '2026-01-13T09:15:00', - customerName: 'Pedro Oliveira', - status: 'Entregue', - orderType: 'Venda', - amount: 890.75, - invoiceNumber: 'NF-001235', - sellerName: 'João Vendedor', - deliveryType: 'Entrega', - totalWeight: 5.0, - fatUserName: 'Admin', - deliveryLocal: 'Belo Horizonte - MG', - masterDeliveryLocal: 'Região Sudeste', - deliveryPriority: 'Baixa', - paymentName: 'PIX', - partnerName: 'Parceiro DEF', - codusur2Name: 'Rep. Nacional', - releaseUserName: 'Gerente', - driver: 'Paulo Motorista', - carDescription: 'Fiorino', - carrier: 'Transportadora ABC', - schedulerDelivery: '13/01/2026', - fatUserDescription: 'Faturamento Automático', - emitenteNome: 'Outra Empresa LTDA', - }, -]; - -// Colunas simplificadas para a demo -const demoColumns = [ - { field: 'orderId', headerName: 'Pedido', width: 100 }, - { field: 'createDate', headerName: 'Data Criação', width: 150 }, - { field: 'customerName', headerName: 'Cliente', width: 180 }, - { field: 'status', headerName: 'Status', width: 120 }, - { field: 'orderType', headerName: 'Tipo', width: 100 }, - { - field: 'amount', - headerName: 'Valor', - width: 120, - valueFormatter: (value: number) => - new Intl.NumberFormat('pt-BR', { - style: 'currency', - currency: 'BRL', - }).format(value), - }, - { field: 'invoiceNumber', headerName: 'Nota Fiscal', width: 130 }, - { field: 'sellerName', headerName: 'Vendedor', width: 150 }, - { field: 'deliveryType', headerName: 'Entrega', width: 100 }, - { field: 'deliveryLocal', headerName: 'Local Entrega', width: 180 }, -]; - -const theme = createTheme({ - palette: { - mode: 'light', - }, -}); - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - refetchOnWindowFocus: false, - }, - }, -}); - -// Componente wrapper para o Storybook -const OrderTableDemo = ({ - rows = mockOrders, - loading = false, - emptyState = false, -}: { - rows?: typeof mockOrders; - loading?: boolean; - emptyState?: boolean; -}) => { - const displayRows = emptyState ? [] : rows; - - return ( - - - - - - - - - - - ); -}; - -const meta: Meta = { - title: 'Features/Orders/OrderTable', - component: OrderTableDemo, - parameters: { - layout: 'fullscreen', - docs: { - description: { - component: ` -## OrderTable - -Tabela de pedidos com as seguintes funcionalidades: - -- **Paginação**: Suporta 10, 25 ou 50 itens por página -- **Ordenação**: Por qualquer coluna clicando no header -- **Seleção**: Clique em uma linha para ver detalhes -- **Responsividade**: Adapta altura para mobile/desktop - `, - }, - }, - }, - tags: ['autodocs'], -}; - -export default meta; -type Story = StoryObj; - -/** - * Estado padrão da tabela com dados de pedidos. - */ -export const Default: Story = { - args: { - rows: [ - { - id: '1', - orderId: 1232236, - createDate: '2026-01-15T10:00:00', - customerName: 'Maria Silva', - status: 'Faturado', - orderType: 'Venda', - amount: 1250.5, - invoiceNumber: 'NF-001234', - sellerName: 'João Vendedor', - deliveryType: 'Entrega', - totalWeight: 15.5, - fatUserName: 'Admin', - deliveryLocal: 'São Paulo - SP', - masterDeliveryLocal: 'Região Sul', - deliveryPriority: 'Alta', - paymentName: 'Boleto', - partnerName: 'Parceiro ABC', - codusur2Name: 'Rep. Regional', - releaseUserName: 'Supervisor', - driver: 'Carlos Motorista', - carDescription: 'Sprinter', - carrier: 'Transportadora XYZ', - schedulerDelivery: '15/01/2026', - fatUserDescription: 'Faturamento', - emitenteNome: 'Empresa LTDA', - }, - { - id: '2', - orderId: 123457, - createDate: '2026-01-14T14:30:00', - customerName: 'João Santos', - status: 'Pendente', - orderType: 'Venda', - amount: 3450, - invoiceNumber: '', - sellerName: 'Ana Vendedora', - deliveryType: 'Retirada', - totalWeight: 8.2, - fatUserName: '', - deliveryLocal: 'Rio de Janeiro - RJ', - masterDeliveryLocal: 'Região Sudeste', - deliveryPriority: 'Normal', - paymentName: 'Cartão de Crédito', - partnerName: '', - codusur2Name: '', - releaseUserName: '', - driver: '', - carDescription: '', - carrier: '', - schedulerDelivery: '', - fatUserDescription: '', - emitenteNome: 'Empresa LTDA', - }, - { - id: '3', - orderId: 123458, - createDate: '2026-01-13T09:15:00', - customerName: 'Pedro Oliveira', - status: 'Entregue', - orderType: 'Venda', - amount: 890.75, - invoiceNumber: 'NF-001235', - sellerName: 'João Vendedor', - deliveryType: 'Entrega', - totalWeight: 5, - fatUserName: 'Admin', - deliveryLocal: 'Belo Horizonte - MG', - masterDeliveryLocal: 'Região Sudeste', - deliveryPriority: 'Baixa', - paymentName: 'PIX', - partnerName: 'Parceiro DEF', - codusur2Name: 'Rep. Nacional', - releaseUserName: 'Gerente', - driver: 'Paulo Motorista', - carDescription: 'Fiorino', - carrier: 'Transportadora ABC', - schedulerDelivery: '13/01/2026', - fatUserDescription: 'Faturamento Automático', - emitenteNome: 'Outra Empresa LTDA', - }, - ], - loading: false, - emptyState: false, - }, -}; - -/** - * Estado de carregamento enquanto busca pedidos da API. - */ -export const Loading: Story = { - args: { - rows: [], - loading: true, - emptyState: false, - }, -}; - -/** - * Estado vazio quando não há pedidos para exibir. - */ -export const Empty: Story = { - args: { - rows: [], - loading: false, - emptyState: true, - }, -}; - -/** - * Tabela com muitos pedidos para demonstrar a paginação. - */ -export const ManyOrders: Story = { - args: { - rows: Array.from({ length: 50 }, (_, i) => ({ - ...mockOrders[i % 3], - id: String(i + 1), - orderId: 123456 + i, - customerName: `Cliente ${i + 1}`, - amount: Math.random() * 10000, - })), - loading: false, - emptyState: false, - }, -}; diff --git a/src/features/orders/components/OrderTable.tsx b/src/features/orders/components/OrderTable.tsx index d0dcbc0..ef18916 100644 --- a/src/features/orders/components/OrderTable.tsx +++ b/src/features/orders/components/OrderTable.tsx @@ -135,7 +135,7 @@ export const OrderTable = () => { border: 'none', }, '& .MuiDataGrid-columnHeaders': { - backgroundColor: 'grey.50', + backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.900' : 'grey.50', fontWeight: 600, fontSize: '0.75rem', borderBottom: '2px solid', @@ -180,7 +180,7 @@ export const OrderTable = () => { }, }, '& .MuiDataGrid-row:nth-of-type(even)': { - backgroundColor: 'grey.50', + backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.800' : 'grey.50', '&:hover': { backgroundColor: 'action.hover', }, @@ -226,10 +226,10 @@ export const OrderTable = () => { fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', minHeight: '48px !important', fontSize: '0.75rem', - backgroundColor: 'grey.50', + backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.900' : 'grey.50', }, '& .MuiDataGrid-aggregationColumnHeader': { - backgroundColor: 'grey.100', + backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.800' : 'grey.100', fontWeight: 600, fontSize: '0.75rem', borderBottom: '2px solid', @@ -239,7 +239,7 @@ export const OrderTable = () => { fontWeight: 600, }, '& .MuiDataGrid-aggregationRow': { - backgroundColor: 'grey.100', + backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.800' : 'grey.100', borderTop: '2px solid', borderColor: 'divider', minHeight: '40px !important', @@ -267,13 +267,13 @@ export const OrderTable = () => { opacity: 1, }, '& .MuiDataGrid-pinnedColumnHeaders': { - backgroundColor: 'grey.50', + backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.900' : 'grey.50', }, '& .MuiDataGrid-pinnedColumns': { backgroundColor: 'background.paper', }, '& .MuiDataGrid-pinnedColumnHeader': { - backgroundColor: 'grey.50', + backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.900' : 'grey.50', fontWeight: 600, fontSize: '0.75rem', borderBottom: '2px solid', diff --git a/src/features/orders/components/OrderTableColumns.tsx b/src/features/orders/components/OrderTableColumns.tsx index c9fe13e..1ac8d3f 100644 --- a/src/features/orders/components/OrderTableColumns.tsx +++ b/src/features/orders/components/OrderTableColumns.tsx @@ -14,431 +14,423 @@ import { getPriorityChipProps, } from '../utils/tableHelpers'; + + +const CELL_FONT_SIZE = '0.75rem'; +const CAPTION_FONT_SIZE = '0.6875rem'; + +const CHIP_STYLES = { + fontSize: CAPTION_FONT_SIZE, + height: 22, + fontWeight: 300, +} as const; + +const CHIP_PRIORITY_STYLES = { + ...CHIP_STYLES, + maxWidth: '100%', + '& .MuiChip-label': { + px: 1, + py: 0, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, +} as const; + + + +interface CellTextProps { + value: unknown; + secondary?: boolean; + fontWeight?: number; +} + + +const CellText = ({ value, secondary = false, fontWeight }: CellTextProps) => ( + + {String(value ?? '-')} + +); + +interface CellNumericProps { + value: unknown; + formatter?: (val: number) => string; + secondary?: boolean; + fontWeight?: number; +} + + +const CellNumeric = ({ + value, + formatter, + secondary = false, + fontWeight, +}: CellNumericProps) => ( + + {formatter ? formatter(value as number) : String(value ?? '-')} + +); + +interface CellDateProps { + value: unknown; + showTime?: boolean; + time?: string; +} + +const CellDate = ({ value, showTime = false, time }: CellDateProps) => { + const dateStr = formatDate(value as string | undefined); + + if (!showTime && !time) { + return ( + + {dateStr} + + ); + } + + const timeStr = time || formatDateTime(value as string | undefined); + + return ( + + + {dateStr} + + {timeStr && ( + + {timeStr} + + )} + + ); +}; + + + interface CreateOrderColumnsOptions { storesMap?: Map; } -export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridColDef[] => { +export const createOrderColumns = ( + options?: CreateOrderColumnsOptions +): GridColDef[] => { const storesMap = options?.storesMap; return [ - { - field: 'orderId', - headerName: 'Pedido', - width: 120, - minWidth: 100, - headerAlign: 'right', - align: 'right', - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, - { - field: 'createDate', - headerName: 'Data', - width: 160, - minWidth: 140, - renderCell: (params: Readonly) => { - const dateTime = formatDateTime(params.value); - return ( - - - {formatDate(params.value)} - - {dateTime && ( - - {dateTime} - - )} - - ); - }, - }, - { - field: 'customerName', - headerName: 'Cliente', - width: 300, - minWidth: 250, - flex: 1, - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, - { - field: 'storeId', - headerName: 'Filial', - width: 200, - minWidth: 180, - renderCell: (params: Readonly) => { - const storeId = String(params.value); - const storeName = storesMap?.get(storeId) || storeId; - return ( - - {storeName} - - ); - }, - }, - { - field: 'store', - headerName: 'Supervisor', - width: 200, - minWidth: 180, - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, - { - field: 'status', - headerName: 'Situação', - width: 180, - minWidth: 160, - renderCell: (params: Readonly) => { - const status = params.value as string; - const chipProps = getStatusChipProps(status); - return ( - - - - ); + { + field: 'orderId', + headerName: 'Pedido', + width: 120, + minWidth: 100, + headerAlign: 'right', + align: 'right', + renderCell: (params: Readonly) => ( + + ), + }, + { + field: 'createDate', + headerName: 'Data', + width: 160, + minWidth: 140, + renderCell: (params: Readonly) => ( + + ), }, - }, - { - field: 'orderType', - headerName: 'Tipo', - width: 140, - minWidth: 120, - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, - { - field: 'amount', - headerName: 'Valor Total', - width: 130, - minWidth: 120, - type: 'number', - aggregable: true, - headerAlign: 'right', - align: 'right', - valueFormatter: (value) => formatCurrency(value as number), - renderCell: (params: Readonly) => ( - - {formatCurrency(params.value)} - - ), - }, - { - field: 'invoiceNumber', - headerName: 'Nota Fiscal', - width: 120, - minWidth: 110, - headerAlign: 'right', - align: 'right', - renderCell: (params: Readonly) => ( - - {params.value && params.value !== '-' ? params.value : '-'} - - ), - }, - { - field: 'billingId', - headerName: 'Cobrança', - width: 120, - minWidth: 110, - renderCell: (params: Readonly) => ( - - {params.value || '-'} - - ), - }, - { - field: 'sellerName', - headerName: 'Vendedor', - width: 200, - minWidth: 180, - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, - { - field: 'deliveryType', - headerName: 'Tipo de Entrega', - width: 160, - minWidth: 140, - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, - { - field: 'totalWeight', - headerName: 'Peso (kg)', - width: 100, - minWidth: 90, - type: 'number', - aggregable: true, - headerAlign: 'right', - align: 'right', - valueFormatter: (value) => formatNumber(value as number), - renderCell: (params: Readonly) => ( - - {formatNumber(params.value)} - - ), - }, - { - field: 'fatUserName', - headerName: 'Usuário Faturou', - width: 160, - minWidth: 140, - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, - { - field: 'customerId', - headerName: 'Código Cliente', - width: 120, - minWidth: 110, - headerAlign: 'right', - align: 'right', - renderCell: (params: Readonly) => ( - - {params.value || '-'} - - ), - }, - { - field: 'deliveryDate', - headerName: 'Data Entrega', - width: 130, - minWidth: 120, - renderCell: (params: Readonly) => ( - - {params.value ? formatDate(params.value) : '-'} - - ), - }, - { - field: 'deliveryLocal', - headerName: 'Local Entrega', - width: 180, - minWidth: 160, - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, - { - field: 'deliveryPriority', - headerName: 'Prioridade', - width: 120, - minWidth: 110, - renderCell: (params: Readonly) => { - const priority = params.value; - const chipProps = getPriorityChipProps(priority); - if (!chipProps) { + + { + field: 'customerName', + headerName: 'Cliente', + width: 300, + minWidth: 250, + flex: 1, + renderCell: (params: Readonly) => ( + + ), + }, + { + field: 'customerId', + headerName: 'Código Cliente', + width: 120, + minWidth: 110, + headerAlign: 'right', + align: 'right', + renderCell: (params: Readonly) => ( + + ), + }, + + + { + field: 'storeId', + headerName: 'Filial', + width: 200, + minWidth: 180, + renderCell: (params: Readonly) => { + const storeId = String(params.value); + const storeName = storesMap?.get(storeId) || storeId; + return ; + }, + }, + { + field: 'store', + headerName: 'Supervisor', + width: 200, + minWidth: 180, + renderCell: (params: Readonly) => ( + + ), + }, + + + { + field: 'status', + headerName: 'Situação', + width: 180, + minWidth: 160, + renderCell: (params: Readonly) => { + const status = params.value as string; + const chipProps = getStatusChipProps(status); + return ( - - - - + + + ); - } - - return ( - - - - ); + }, }, - }, - { - field: 'invoiceDate', - headerName: 'Data Faturamento', - width: 150, - minWidth: 140, - renderCell: (params: Readonly) => { - const dateStr = formatDate(params.value); - const timeStr = params.row.invoiceTime; - - return ( - - {dateStr} - {timeStr && ( - - {timeStr} - - )} - - ); + { + field: 'orderType', + headerName: 'Tipo', + width: 140, + minWidth: 120, + renderCell: (params: Readonly) => ( + + ), }, - }, - { - field: 'invoiceTime', - headerName: 'Hora Faturamento', - width: 120, - minWidth: 110, - renderCell: (params: Readonly) => ( - - {params.value || '-'} - - ), - }, - { - field: 'confirmDeliveryDate', - headerName: 'Data Confirmação Entrega', - width: 150, - minWidth: 140, - renderCell: (params: Readonly) => ( - - {params.value ? formatDate(params.value) : '-'} - - ), - }, - { - field: 'paymentName', - headerName: 'Pagamento', - width: 140, - minWidth: 130, - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, - { - field: 'sellerId', - headerName: 'RCA', - width: 100, - minWidth: 90, - headerAlign: 'right', - align: 'right', - renderCell: (params: Readonly) => ( - - {params.value || '-'} - - ), - }, - { - field: 'partnerName', - headerName: 'Parceiro', - width: 180, - minWidth: 160, - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, - { - field: 'codusur2Name', - headerName: 'RCA 2', - width: 180, - minWidth: 160, - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, - { - field: 'schedulerDelivery', - headerName: 'Agendamento', - width: 160, - minWidth: 140, - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, - { - field: 'emitenteNome', - headerName: 'Emitente', - width: 180, - minWidth: 160, - renderCell: (params: Readonly) => ( - - {params.value} - - ), - }, -]; + + + { + field: 'amount', + headerName: 'Valor Total', + width: 130, + minWidth: 120, + type: 'number', + aggregable: true, + headerAlign: 'right', + align: 'right', + valueFormatter: (value) => formatCurrency(value as number), + renderCell: (params: Readonly) => ( + + ), + }, + { + field: 'totalWeight', + headerName: 'Peso (kg)', + width: 100, + minWidth: 90, + type: 'number', + aggregable: true, + headerAlign: 'right', + align: 'right', + valueFormatter: (value) => formatNumber(value as number), + renderCell: (params: Readonly) => ( + + ), + }, + + + { + field: 'invoiceNumber', + headerName: 'Nota Fiscal', + width: 120, + minWidth: 110, + headerAlign: 'right', + align: 'right', + renderCell: (params: Readonly) => { + const value = params.value && params.value !== '-' ? params.value : '-'; + return ; + }, + }, + { + field: 'invoiceDate', + headerName: 'Data Faturamento', + width: 150, + minWidth: 140, + renderCell: (params: Readonly) => ( + + ), + }, + { + field: 'fatUserName', + headerName: 'Usuário Faturou', + width: 160, + minWidth: 140, + renderCell: (params: Readonly) => ( + + ), + }, + + + { + field: 'billingId', + headerName: 'Cobrança', + width: 120, + minWidth: 110, + renderCell: (params: Readonly) => ( + + ), + }, + { + field: 'paymentName', + headerName: 'Pagamento', + width: 140, + minWidth: 130, + renderCell: (params: Readonly) => ( + + ), + }, + + + { + field: 'sellerName', + headerName: 'Vendedor', + width: 200, + minWidth: 180, + renderCell: (params: Readonly) => ( + + ), + }, + { + field: 'sellerId', + headerName: 'RCA', + width: 100, + minWidth: 90, + headerAlign: 'right', + align: 'right', + renderCell: (params: Readonly) => ( + + ), + }, + { + field: 'codusur2Name', + headerName: 'RCA 2', + width: 180, + minWidth: 160, + renderCell: (params: Readonly) => ( + + ), + }, + + + { + field: 'deliveryType', + headerName: 'Tipo de Entrega', + width: 160, + minWidth: 140, + renderCell: (params: Readonly) => ( + + ), + }, + { + field: 'deliveryDate', + headerName: 'Data Entrega', + width: 130, + minWidth: 120, + renderCell: (params: Readonly) => ( + + ), + }, + { + field: 'deliveryLocal', + headerName: 'Local Entrega', + width: 180, + minWidth: 160, + renderCell: (params: Readonly) => ( + + ), + }, + { + field: 'deliveryPriority', + headerName: 'Prioridade', + width: 120, + minWidth: 110, + renderCell: (params: Readonly) => { + const priority = params.value; + const chipProps = getPriorityChipProps(priority); + + if (!chipProps) { + return ; + } + + return ( + + + + ); + }, + }, + { + field: 'confirmDeliveryDate', + headerName: 'Data Confirmação Entrega', + width: 150, + minWidth: 140, + renderCell: (params: Readonly) => ( + + ), + }, + { + field: 'schedulerDelivery', + headerName: 'Agendamento', + width: 160, + minWidth: 140, + renderCell: (params: Readonly) => ( + + ), + }, + + + { + field: 'partnerName', + headerName: 'Parceiro', + width: 180, + minWidth: 160, + renderCell: (params: Readonly) => ( + + ), + }, + { + field: 'emitenteNome', + headerName: 'Emitente', + width: 180, + minWidth: 160, + renderCell: (params: Readonly) => ( + + ), + }, + ]; }; diff --git a/src/features/orders/components/SearchBar.tsx b/src/features/orders/components/SearchBar.tsx index fcd49bb..626bfe5 100644 --- a/src/features/orders/components/SearchBar.tsx +++ b/src/features/orders/components/SearchBar.tsx @@ -2,6 +2,7 @@ import { useState, useCallback, useEffect } from 'react'; import { useOrderFilters } from '../hooks/useOrderFilters'; +import { useOrders } from '../hooks/useOrders'; import { Box, TextField, @@ -73,7 +74,7 @@ export const SearchBar = () => { const [customerSearchTerm, setCustomerSearchTerm] = useState(''); const customers = useCustomers(customerSearchTerm); const [showAdvancedFilters, setShowAdvancedFilters] = useState(false); - const [isSearching, setIsSearching] = useState(false); + const { isFetching } = useOrders(); const [touchedFields, setTouchedFields] = useState<{ createDateIni?: boolean; createDateEnd?: boolean; @@ -141,13 +142,10 @@ export const SearchBar = () => { return; } - setIsSearching(true); setUrlFilters({ ...localFilters, searchTriggered: true, }); - // Reset loading state after a short delay - setTimeout(() => setIsSearching(false), 500); }, [localFilters, setUrlFilters, validateDates]); const handleKeyDown = useCallback( @@ -230,7 +228,7 @@ export const SearchBar = () => { {/* Autocomplete do MUI para Cliente */} - + { {/* Campos de Data */} - + { - {/* Botões de Ação */} + {/* Botão Mais Filtros - inline com filtros primários */} - + + + + + + {/* Botões de Ação - nova linha abaixo */} + + @@ -433,27 +456,28 @@ export const SearchBar = () => { @@ -464,25 +488,6 @@ export const SearchBar = () => { {/* --- Advanced Filters (Collapsible) --- */} - - - - - {/* Autocomplete do MUI para Múltiplas Filiais (codfilial) */} diff --git a/src/features/orders/components/tabs/InformationPanelColumns.tsx b/src/features/orders/components/tabs/InformationPanelColumns.tsx index 6601da4..03a2416 100644 --- a/src/features/orders/components/tabs/InformationPanelColumns.tsx +++ b/src/features/orders/components/tabs/InformationPanelColumns.tsx @@ -1,5 +1,6 @@ import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid-premium'; import Chip from '@mui/material/Chip'; +import Box from '@mui/material/Box'; import { formatCurrency, formatDate, @@ -7,6 +8,19 @@ import { getStatusLabel, } from '../../utils/orderFormatters'; +const TextCell = ({ value }: { value: string | null | undefined }) => ( + + {value ?? ''} + +); + export const createInformationPanelColumns = (): GridColDef[] => [ { field: 'customerName', @@ -14,6 +28,7 @@ export const createInformationPanelColumns = (): GridColDef[] => [ minWidth: 180, flex: 1.5, description: 'Nome do cliente do pedido', + renderCell: (params: GridRenderCellParams) => , }, { field: 'storeId', @@ -55,6 +70,7 @@ export const createInformationPanelColumns = (): GridColDef[] => [ minWidth: 100, flex: 1, description: 'Forma de pagamento utilizada', + renderCell: (params: GridRenderCellParams) => , }, { field: 'billingName', @@ -62,6 +78,7 @@ export const createInformationPanelColumns = (): GridColDef[] => [ minWidth: 100, flex: 1, description: 'Condição de pagamento', + renderCell: (params: GridRenderCellParams) => , }, { field: 'amount', @@ -78,6 +95,7 @@ export const createInformationPanelColumns = (): GridColDef[] => [ minWidth: 100, flex: 1, description: 'Tipo de entrega selecionado', + renderCell: (params: GridRenderCellParams) => , }, { field: 'deliveryLocal', @@ -85,5 +103,7 @@ export const createInformationPanelColumns = (): GridColDef[] => [ minWidth: 120, flex: 1.2, description: 'Local de entrega do pedido', + renderCell: (params: GridRenderCellParams) => , }, ]; + diff --git a/src/features/orders/hooks/useSellers.ts b/src/features/orders/hooks/useSellers.ts index d6832e7..166edd7 100644 --- a/src/features/orders/hooks/useSellers.ts +++ b/src/features/orders/hooks/useSellers.ts @@ -4,7 +4,7 @@ import { orderService } from '../api/order.service'; export function useSellers() { const query = useQuery({ queryKey: ['sellers'], - queryFn: () => orderService.findsellers(), + queryFn: () => orderService.findSellers(), staleTime: 1000 * 60 * 30, // 30 minutes retry: 1, retryOnMount: false, diff --git a/src/features/profile/components/ProfilePage.tsx b/src/features/profile/components/ProfilePage.tsx index e835adf..b8061c5 100644 --- a/src/features/profile/components/ProfilePage.tsx +++ b/src/features/profile/components/ProfilePage.tsx @@ -130,27 +130,17 @@ export default function ProfilePage() { {displayData.sectorId !== undefined && ( - Setor ID + Setor {displayData.sectorId} )} - {displayData.sectorManagerId !== undefined && ( - - - Gerente de Setor ID - - - {displayData.sectorManagerId} - - - )} {displayData.supervisorId !== undefined && ( - Supervisor ID + Supervisor {displayData.supervisorId}