feat: Implement order management, user profile, and authentication features.
This commit is contained in:
parent
02aaae0cd3
commit
df12e81c1c
|
|
@ -9,14 +9,17 @@ import {
|
||||||
Button,
|
Button,
|
||||||
IconButton,
|
IconButton,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import * as dropdownData from './data';
|
|
||||||
|
|
||||||
import { IconMail } from '@tabler/icons-react';
|
import { IconMail } from '@tabler/icons-react';
|
||||||
import { Stack } from '@mui/system';
|
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 Profile = () => {
|
||||||
const [anchorEl2, setAnchorEl2] = useState(null);
|
const [anchorEl2, setAnchorEl2] = useState(null);
|
||||||
|
const user = useAuthStore((s) => s.user);
|
||||||
|
const { logout } = useAuth();
|
||||||
|
|
||||||
const handleClick2 = (event: any) => {
|
const handleClick2 = (event: any) => {
|
||||||
setAnchorEl2(event.currentTarget);
|
setAnchorEl2(event.currentTarget);
|
||||||
};
|
};
|
||||||
|
|
@ -39,13 +42,14 @@ const Profile = () => {
|
||||||
onClick={handleClick2}
|
onClick={handleClick2}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={'/images/profile/user-1.jpg'}
|
|
||||||
alt={'ProfileImg'}
|
|
||||||
sx={{
|
sx={{
|
||||||
width: 35,
|
width: 35,
|
||||||
height: 35,
|
height: 35,
|
||||||
|
bgcolor: 'primary.main',
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{user?.nome?.[0] || user?.userName?.[0] || 'U'}
|
||||||
|
</Avatar>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{/* ------------------------------------------- */}
|
{/* ------------------------------------------- */}
|
||||||
{/* Message Dropdown */}
|
{/* Message Dropdown */}
|
||||||
|
|
@ -68,20 +72,20 @@ const Profile = () => {
|
||||||
<Typography variant="h5">User Profile</Typography>
|
<Typography variant="h5">User Profile</Typography>
|
||||||
<Stack direction="row" py={3} spacing={2} alignItems="center">
|
<Stack direction="row" py={3} spacing={2} alignItems="center">
|
||||||
<Avatar
|
<Avatar
|
||||||
src={'/images/profile/user-1.jpg'}
|
sx={{ width: 95, height: 95, bgcolor: 'primary.main' }}
|
||||||
alt={'ProfileImg'}
|
>
|
||||||
sx={{ width: 95, height: 95 }}
|
{user?.nome?.[0] || user?.userName?.[0] || 'U'}
|
||||||
/>
|
</Avatar>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
variant="subtitle2"
|
variant="subtitle2"
|
||||||
color="textPrimary"
|
color="textPrimary"
|
||||||
fontWeight={600}
|
fontWeight={600}
|
||||||
>
|
>
|
||||||
Mathew Anderson
|
{user?.nome || user?.userName || 'Usuário'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="subtitle2" color="textSecondary">
|
<Typography variant="subtitle2" color="textSecondary">
|
||||||
Designer
|
{user?.nomeFilial || 'Sem filial'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
variant="subtitle2"
|
variant="subtitle2"
|
||||||
|
|
@ -91,100 +95,18 @@ const Profile = () => {
|
||||||
gap={1}
|
gap={1}
|
||||||
>
|
>
|
||||||
<IconMail width={15} height={15} />
|
<IconMail width={15} height={15} />
|
||||||
info@modernize.com
|
{user?.rca ? `RCA: ${user.rca}` : 'Sem e-mail'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Divider />
|
|
||||||
{dropdownData.profile.map((profile) => (
|
|
||||||
<Box key={profile.title}>
|
|
||||||
<Box sx={{ py: 2, px: 0 }} className="hover-text-primary">
|
|
||||||
<Link href={profile.href}>
|
|
||||||
<Stack direction="row" spacing={2}>
|
|
||||||
<Box
|
|
||||||
width="45px"
|
|
||||||
height="45px"
|
|
||||||
bgcolor="primary.light"
|
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
flexShrink="0"
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
src={profile.icon}
|
|
||||||
alt={profile.icon}
|
|
||||||
sx={{
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
borderRadius: 0,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="subtitle2"
|
|
||||||
fontWeight={600}
|
|
||||||
color="textPrimary"
|
|
||||||
className="text-hover"
|
|
||||||
noWrap
|
|
||||||
sx={{
|
|
||||||
width: '240px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{profile.title}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
color="textSecondary"
|
|
||||||
variant="subtitle2"
|
|
||||||
sx={{
|
|
||||||
width: '240px',
|
|
||||||
}}
|
|
||||||
noWrap
|
|
||||||
>
|
|
||||||
{profile.subtitle}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Link>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
<Box mt={2}>
|
<Box mt={2}>
|
||||||
<Box
|
|
||||||
bgcolor="primary.light"
|
|
||||||
p={3}
|
|
||||||
mb={3}
|
|
||||||
overflow="hidden"
|
|
||||||
position="relative"
|
|
||||||
>
|
|
||||||
<Box display="flex" justifyContent="space-between">
|
|
||||||
<Box>
|
|
||||||
<Typography variant="h5" mb={2}>
|
|
||||||
Unlimited <br />
|
|
||||||
Access
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" color="primary">
|
|
||||||
Upgrade
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Image
|
|
||||||
src={'/images/backgrounds/unlimited-bg.png'}
|
|
||||||
width={150}
|
|
||||||
height={183}
|
|
||||||
style={{ height: 'auto', width: 'auto' }}
|
|
||||||
alt="unlimited"
|
|
||||||
className="signup-bg"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Button
|
<Button
|
||||||
href="/auth/auth1/login"
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
component={Link}
|
onClick={logout}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
Logout
|
Sair
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
|
||||||
|
|
@ -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 }) => (
|
|
||||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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(<AuthLogin title="Login" />, { 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(<AuthLogin title="Bem-vindo" />, { wrapper: createWrapper() });
|
|
||||||
expect(screen.getByText('Bem-vindo')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('deve renderizar checkbox "Manter-me conectado"', () => {
|
|
||||||
render(<AuthLogin />, { wrapper: createWrapper() });
|
|
||||||
expect(screen.getByText(/manter-me conectado/i)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('deve renderizar link "Esqueceu sua senha"', () => {
|
|
||||||
render(<AuthLogin />, { wrapper: createWrapper() });
|
|
||||||
const link = screen.getByText(/esqueceu sua senha/i);
|
|
||||||
expect(link).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Validação', () => {
|
|
||||||
it('deve validar campos obrigatórios', async () => {
|
|
||||||
render(<AuthLogin />, { 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(<AuthLogin />, { 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(<AuthLogin />, { 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(<AuthLogin />, { 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(<AuthLogin />, { 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(<AuthLogin />, { 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(<AuthLogin />, { 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(<AuthLogin />, { 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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Divider from '@mui/material/Divider';
|
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 Stack from '@mui/material/Stack';
|
||||||
import { TextField, Alert } from '@mui/material';
|
import { TextField, Alert } from '@mui/material';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
@ -12,7 +10,6 @@ import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { loginSchema, LoginInput, AuthLoginProps } from '../interfaces/types';
|
import { loginSchema, LoginInput, AuthLoginProps } from '../interfaces/types';
|
||||||
import { useAuth } from '../hooks/useAuth';
|
import { useAuth } from '../hooks/useAuth';
|
||||||
import CustomCheckbox from '../components/forms/theme-elements/CustomCheckbox';
|
|
||||||
import CustomFormLabel from '../components/forms/theme-elements/CustomFormLabel';
|
import CustomFormLabel from '../components/forms/theme-elements/CustomFormLabel';
|
||||||
import AuthSocialButtons from './AuthSocialButtons';
|
import AuthSocialButtons from './AuthSocialButtons';
|
||||||
|
|
||||||
|
|
@ -45,7 +42,7 @@ const AuthLogin = ({ title, subtitle, subtext }: AuthLoginProps) => {
|
||||||
|
|
||||||
{subtext}
|
{subtext}
|
||||||
|
|
||||||
<AuthSocialButtons title="Sign in with" />
|
<AuthSocialButtons title="Entrar com" />
|
||||||
<Box mt={4} mb={2}>
|
<Box mt={4} mb={2}>
|
||||||
<Divider>
|
<Divider>
|
||||||
<Typography
|
<Typography
|
||||||
|
|
@ -106,20 +103,12 @@ const AuthLogin = ({ title, subtitle, subtext }: AuthLoginProps) => {
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack
|
<Stack
|
||||||
justifyContent="space-between"
|
justifyContent="flex-end"
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
my={2}
|
my={2}
|
||||||
spacing={1}
|
spacing={1}
|
||||||
flexWrap="wrap"
|
|
||||||
>
|
>
|
||||||
<FormGroup>
|
|
||||||
<FormControlLabel
|
|
||||||
control={<CustomCheckbox defaultChecked />}
|
|
||||||
label="Manter-me conectado"
|
|
||||||
sx={{ whiteSpace: 'nowrap' }}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<Typography
|
<Typography
|
||||||
fontWeight="500"
|
fontWeight="500"
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -145,7 +134,7 @@ const AuthLogin = ({ title, subtitle, subtext }: AuthLoginProps) => {
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loginMutation.isPending}
|
disabled={loginMutation.isPending}
|
||||||
>
|
>
|
||||||
{loginMutation.isPending ? 'Logging in...' : 'Sign In'}
|
{loginMutation.isPending ? 'Entrando...' : 'Entrar'}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
import { Box, CircularProgress, Typography } from '@mui/material';
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { useAuthStore } from '../store/useAuthStore';
|
import { useAuthStore } from '../store/useAuthStore';
|
||||||
|
|
@ -34,12 +35,56 @@ export function AuthInitializer({ children }: { children: React.ReactNode }) {
|
||||||
|
|
||||||
if (isChecking) {
|
if (isChecking) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-screen items-center justify-center bg-background">
|
<Box
|
||||||
<div className="animate-pulse flex flex-col items-center gap-4">
|
sx={{
|
||||||
<div className="h-12 w-12 rounded-full bg-primary/20" />
|
display: 'flex',
|
||||||
<p className="text-sm text-muted-foreground">Validando acesso...</p>
|
height: '100vh',
|
||||||
</div>
|
width: '100vw',
|
||||||
</div>
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
bgcolor: 'background.default',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 2,
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ position: 'relative', display: 'flex' }}>
|
||||||
|
<CircularProgress
|
||||||
|
variant="determinate"
|
||||||
|
sx={{
|
||||||
|
color: (theme) => theme.palette.grey[200],
|
||||||
|
}}
|
||||||
|
size={48}
|
||||||
|
thickness={4}
|
||||||
|
value={100}
|
||||||
|
/>
|
||||||
|
<CircularProgress
|
||||||
|
variant="indeterminate"
|
||||||
|
disableShrink
|
||||||
|
sx={{
|
||||||
|
color: (theme) => theme.palette.primary.main,
|
||||||
|
animationDuration: '550ms',
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
[`& .MuiCircularProgress-circle`]: {
|
||||||
|
strokeLinecap: 'round',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
size={48}
|
||||||
|
thickness={4}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
Validando acesso...
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,16 +94,11 @@ export const orderService = {
|
||||||
* @returns {Promise<Order[]>} Array de pedidos que correspondem aos filtros
|
* @returns {Promise<Order[]>} Array de pedidos que correspondem aos filtros
|
||||||
*/
|
*/
|
||||||
findOrders: async (filters: OrderFilters): Promise<Order[]> => {
|
findOrders: async (filters: OrderFilters): Promise<Order[]> => {
|
||||||
try {
|
|
||||||
const cleanParams = orderApiParamsSchema.parse(filters);
|
const cleanParams = orderApiParamsSchema.parse(filters);
|
||||||
const response = await ordersApi.get('/api/v1/orders/find', {
|
const response = await ordersApi.get('/api/v1/orders/find', {
|
||||||
params: cleanParams,
|
params: cleanParams,
|
||||||
});
|
});
|
||||||
return unwrapApiData(response, ordersResponseSchema, []);
|
return unwrapApiData(response, ordersResponseSchema, []);
|
||||||
} catch (error) {
|
|
||||||
console.error('Erro ao buscar pedidos:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -113,13 +108,8 @@ export const orderService = {
|
||||||
* @returns {Promise<Order | null>} O pedido com o ID especificado, ou null se não encontrado
|
* @returns {Promise<Order | null>} O pedido com o ID especificado, ou null se não encontrado
|
||||||
*/
|
*/
|
||||||
findById: async (id: number): Promise<Order | null> => {
|
findById: async (id: number): Promise<Order | null> => {
|
||||||
try {
|
|
||||||
const response = await ordersApi.get(`/orders/${id}`);
|
const response = await ordersApi.get(`/orders/${id}`);
|
||||||
return unwrapApiData(response, orderResponseSchema, null);
|
return unwrapApiData(response, orderResponseSchema, null);
|
||||||
} catch (error) {
|
|
||||||
console.error(`Erro ao buscar pedido ${id}:`, error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -128,13 +118,8 @@ export const orderService = {
|
||||||
* @returns {Promise<Store[]>} Array de todas as lojas, ou array vazio se nenhuma for encontrada
|
* @returns {Promise<Store[]>} Array de todas as lojas, ou array vazio se nenhuma for encontrada
|
||||||
*/
|
*/
|
||||||
findStores: async (): Promise<Store[]> => {
|
findStores: async (): Promise<Store[]> => {
|
||||||
try {
|
|
||||||
const response = await ordersApi.get('/api/v1/data-consult/stores');
|
const response = await ordersApi.get('/api/v1/data-consult/stores');
|
||||||
return unwrapApiData(response, storesResponseSchema, []);
|
return unwrapApiData(response, storesResponseSchema, []);
|
||||||
} catch (error) {
|
|
||||||
console.error('Erro ao buscar lojas:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -148,35 +133,21 @@ export const orderService = {
|
||||||
name: string
|
name: string
|
||||||
): Promise<Array<{ id: number; name: string; estcob: string }>> => {
|
): Promise<Array<{ id: number; name: string; estcob: string }>> => {
|
||||||
if (!name || name.trim().length < 2) return [];
|
if (!name || name.trim().length < 2) return [];
|
||||||
try {
|
|
||||||
const response = await ordersApi.get(
|
const response = await ordersApi.get(
|
||||||
`/api/v1/clientes/${encodeURIComponent(name)}`
|
`/api/v1/clientes/${encodeURIComponent(name)}`
|
||||||
);
|
);
|
||||||
return unwrapApiData(response, customersResponseSchema, []);
|
return unwrapApiData(response, customersResponseSchema, []);
|
||||||
} catch (error) {
|
|
||||||
console.error('Erro ao buscar clientes:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
findsellers: async (): Promise<Seller[]> => {
|
findSellers: async (): Promise<Seller[]> => {
|
||||||
try {
|
|
||||||
const response = await ordersApi.get('/api/v1/data-consult/sellers');
|
const response = await ordersApi.get('/api/v1/data-consult/sellers');
|
||||||
return unwrapApiData(response, sellersResponseSchema, []);
|
return unwrapApiData(response, sellersResponseSchema, []);
|
||||||
} catch (error) {
|
|
||||||
console.error('Erro ao buscar vendedores:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
findOrderItems: async (orderId: number): Promise<OrderItem[]> => {
|
findOrderItems: async (orderId: number): Promise<OrderItem[]> => {
|
||||||
try {
|
|
||||||
const response = await ordersApi.get(`/api/v1/orders/itens/${orderId}`);
|
const response = await ordersApi.get(`/api/v1/orders/itens/${orderId}`);
|
||||||
return unwrapApiData(response, orderItemsResponseSchema, []);
|
return unwrapApiData(response, orderItemsResponseSchema, []);
|
||||||
} catch (error) {
|
|
||||||
console.error(`Erro ao buscar itens do pedido ${orderId}:`, error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -184,7 +155,6 @@ export const orderService = {
|
||||||
orderId: number,
|
orderId: number,
|
||||||
includeCompletedDeliveries: boolean = true
|
includeCompletedDeliveries: boolean = true
|
||||||
): Promise<Shipment[]> => {
|
): Promise<Shipment[]> => {
|
||||||
try {
|
|
||||||
const response = await ordersApi.get(
|
const response = await ordersApi.get(
|
||||||
`/api/v1/orders/delivery/${orderId}`,
|
`/api/v1/orders/delivery/${orderId}`,
|
||||||
{
|
{
|
||||||
|
|
@ -192,34 +162,20 @@ export const orderService = {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return unwrapApiData(response, shipmentResponseSchema, []);
|
return unwrapApiData(response, shipmentResponseSchema, []);
|
||||||
} catch (error) {
|
|
||||||
console.error(`Erro ao buscar entregas do pedido ${orderId}:`, error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
findCargoMovement: async (orderId: number): Promise<CargoMovement[]> => {
|
findCargoMovement: async (orderId: number): Promise<CargoMovement[]> => {
|
||||||
try {
|
|
||||||
const response = await ordersApi.get(
|
const response = await ordersApi.get(
|
||||||
`/api/v1/orders/transfer/${orderId}`
|
`/api/v1/orders/transfer/${orderId}`
|
||||||
);
|
);
|
||||||
return unwrapApiData(response, cargoMovementResponseSchema, []);
|
return unwrapApiData(response, cargoMovementResponseSchema, []);
|
||||||
} catch (error) {
|
|
||||||
console.error(`Erro ao buscar movimentação de carga do pedido ${orderId}:`, error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
findCuttingItems: async (orderId: number): Promise<CuttingItem[]> => {
|
findCuttingItems: async (orderId: number): Promise<CuttingItem[]> => {
|
||||||
try {
|
|
||||||
const response = await ordersApi.get(
|
const response = await ordersApi.get(
|
||||||
`/api/v1/orders/cut-itens/${orderId}`
|
`/api/v1/orders/cut-itens/${orderId}`
|
||||||
);
|
);
|
||||||
return unwrapApiData(response, cuttingItemResponseSchema, []);
|
return unwrapApiData(response, cuttingItemResponseSchema, []);
|
||||||
} catch (error) {
|
|
||||||
console.error(`Erro ao buscar itens de corte do pedido ${orderId}:`, error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
|
||||||
<ThemeProvider theme={theme}>
|
|
||||||
<CssBaseline />
|
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
<Box sx={{ width: '100%', p: 2 }}>
|
|
||||||
<Paper
|
|
||||||
sx={{
|
|
||||||
boxShadow: 'none',
|
|
||||||
border: 'none',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
overflow: 'hidden',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DataGridPremium
|
|
||||||
rows={displayRows}
|
|
||||||
columns={demoColumns}
|
|
||||||
loading={loading}
|
|
||||||
density="compact"
|
|
||||||
autoHeight={false}
|
|
||||||
initialState={{
|
|
||||||
pagination: {
|
|
||||||
paginationModel: {
|
|
||||||
pageSize: 10,
|
|
||||||
page: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sorting: {
|
|
||||||
sortModel: [{ field: 'createDate', sort: 'desc' }],
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
pageSizeOptions={[10, 25, 50]}
|
|
||||||
paginationMode="client"
|
|
||||||
pagination
|
|
||||||
sx={{
|
|
||||||
height: 400,
|
|
||||||
border: '1px solid',
|
|
||||||
borderColor: 'divider',
|
|
||||||
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
|
|
||||||
backgroundColor: 'background.paper',
|
|
||||||
'& .MuiDataGrid-columnHeaders': {
|
|
||||||
backgroundColor: 'grey.50',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
borderBottom: '2px solid',
|
|
||||||
borderColor: 'divider',
|
|
||||||
},
|
|
||||||
'& .MuiDataGrid-row': {
|
|
||||||
borderBottom: '1px solid',
|
|
||||||
borderColor: 'divider',
|
|
||||||
cursor: 'pointer',
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: 'action.hover',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'& .MuiDataGrid-cell': {
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
},
|
|
||||||
'& .MuiDataGrid-footerContainer': {
|
|
||||||
borderTop: '2px solid',
|
|
||||||
borderColor: 'divider',
|
|
||||||
backgroundColor: 'grey.50',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
localeText={{
|
|
||||||
noRowsLabel: 'Nenhum pedido encontrado.',
|
|
||||||
noResultsOverlayLabel: 'Nenhum resultado encontrado.',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</QueryClientProvider>
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const meta: Meta<typeof OrderTableDemo> = {
|
|
||||||
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<typeof OrderTableDemo>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -135,7 +135,7 @@ export const OrderTable = () => {
|
||||||
border: 'none',
|
border: 'none',
|
||||||
},
|
},
|
||||||
'& .MuiDataGrid-columnHeaders': {
|
'& .MuiDataGrid-columnHeaders': {
|
||||||
backgroundColor: 'grey.50',
|
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.900' : 'grey.50',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
borderBottom: '2px solid',
|
borderBottom: '2px solid',
|
||||||
|
|
@ -180,7 +180,7 @@ export const OrderTable = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'& .MuiDataGrid-row:nth-of-type(even)': {
|
'& .MuiDataGrid-row:nth-of-type(even)': {
|
||||||
backgroundColor: 'grey.50',
|
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.800' : 'grey.50',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: 'action.hover',
|
backgroundColor: 'action.hover',
|
||||||
},
|
},
|
||||||
|
|
@ -226,10 +226,10 @@ export const OrderTable = () => {
|
||||||
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
|
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
|
||||||
minHeight: '48px !important',
|
minHeight: '48px !important',
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
backgroundColor: 'grey.50',
|
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.900' : 'grey.50',
|
||||||
},
|
},
|
||||||
'& .MuiDataGrid-aggregationColumnHeader': {
|
'& .MuiDataGrid-aggregationColumnHeader': {
|
||||||
backgroundColor: 'grey.100',
|
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.800' : 'grey.100',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
borderBottom: '2px solid',
|
borderBottom: '2px solid',
|
||||||
|
|
@ -239,7 +239,7 @@ export const OrderTable = () => {
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
},
|
},
|
||||||
'& .MuiDataGrid-aggregationRow': {
|
'& .MuiDataGrid-aggregationRow': {
|
||||||
backgroundColor: 'grey.100',
|
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.800' : 'grey.100',
|
||||||
borderTop: '2px solid',
|
borderTop: '2px solid',
|
||||||
borderColor: 'divider',
|
borderColor: 'divider',
|
||||||
minHeight: '40px !important',
|
minHeight: '40px !important',
|
||||||
|
|
@ -267,13 +267,13 @@ export const OrderTable = () => {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
},
|
},
|
||||||
'& .MuiDataGrid-pinnedColumnHeaders': {
|
'& .MuiDataGrid-pinnedColumnHeaders': {
|
||||||
backgroundColor: 'grey.50',
|
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.900' : 'grey.50',
|
||||||
},
|
},
|
||||||
'& .MuiDataGrid-pinnedColumns': {
|
'& .MuiDataGrid-pinnedColumns': {
|
||||||
backgroundColor: 'background.paper',
|
backgroundColor: 'background.paper',
|
||||||
},
|
},
|
||||||
'& .MuiDataGrid-pinnedColumnHeader': {
|
'& .MuiDataGrid-pinnedColumnHeader': {
|
||||||
backgroundColor: 'grey.50',
|
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'grey.900' : 'grey.50',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
borderBottom: '2px solid',
|
borderBottom: '2px solid',
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,122 @@ import {
|
||||||
getPriorityChipProps,
|
getPriorityChipProps,
|
||||||
} from '../utils/tableHelpers';
|
} 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) => (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color={secondary ? 'text.secondary' : 'text.primary'}
|
||||||
|
sx={{ fontSize: CELL_FONT_SIZE, fontWeight }}
|
||||||
|
noWrap
|
||||||
|
>
|
||||||
|
{String(value ?? '-')}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
|
||||||
|
interface CellNumericProps {
|
||||||
|
value: unknown;
|
||||||
|
formatter?: (val: number) => string;
|
||||||
|
secondary?: boolean;
|
||||||
|
fontWeight?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const CellNumeric = ({
|
||||||
|
value,
|
||||||
|
formatter,
|
||||||
|
secondary = false,
|
||||||
|
fontWeight,
|
||||||
|
}: CellNumericProps) => (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color={secondary ? 'text.secondary' : 'text.primary'}
|
||||||
|
sx={{ fontSize: CELL_FONT_SIZE, fontWeight, textAlign: 'right' }}
|
||||||
|
>
|
||||||
|
{formatter ? formatter(value as number) : String(value ?? '-')}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Typography variant="body2" sx={{ fontSize: CELL_FONT_SIZE }}>
|
||||||
|
{dateStr}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeStr = time || formatDateTime(value as string | undefined);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="body2" sx={{ fontSize: CELL_FONT_SIZE }}>
|
||||||
|
{dateStr}
|
||||||
|
</Typography>
|
||||||
|
{timeStr && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ fontSize: CAPTION_FONT_SIZE }}
|
||||||
|
>
|
||||||
|
{timeStr}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface CreateOrderColumnsOptions {
|
interface CreateOrderColumnsOptions {
|
||||||
storesMap?: Map<string, string>;
|
storesMap?: Map<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridColDef[] => {
|
export const createOrderColumns = (
|
||||||
|
options?: CreateOrderColumnsOptions
|
||||||
|
): GridColDef[] => {
|
||||||
const storesMap = options?.storesMap;
|
const storesMap = options?.storesMap;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
{
|
{
|
||||||
field: 'orderId',
|
field: 'orderId',
|
||||||
headerName: 'Pedido',
|
headerName: 'Pedido',
|
||||||
|
|
@ -30,12 +138,7 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
headerAlign: 'right',
|
headerAlign: 'right',
|
||||||
align: 'right',
|
align: 'right',
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography
|
<CellNumeric value={params.value} fontWeight={500} />
|
||||||
variant="body2"
|
|
||||||
sx={{ fontSize: '0.75rem', fontWeight: 500, textAlign: 'right' }}
|
|
||||||
>
|
|
||||||
{params.value}
|
|
||||||
</Typography>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -43,26 +146,12 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
headerName: 'Data',
|
headerName: 'Data',
|
||||||
width: 160,
|
width: 160,
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => {
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
const dateTime = formatDateTime(params.value);
|
<CellDate value={params.value} showTime />
|
||||||
return (
|
),
|
||||||
<Box>
|
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }}>
|
|
||||||
{formatDate(params.value)}
|
|
||||||
</Typography>
|
|
||||||
{dateTime && (
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
color="text.secondary"
|
|
||||||
sx={{ fontSize: '0.6875rem' }}
|
|
||||||
>
|
|
||||||
{dateTime}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
field: 'customerName',
|
field: 'customerName',
|
||||||
headerName: 'Cliente',
|
headerName: 'Cliente',
|
||||||
|
|
@ -70,11 +159,22 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
minWidth: 250,
|
minWidth: 250,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
<CellText value={params.value} />
|
||||||
{params.value}
|
|
||||||
</Typography>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'customerId',
|
||||||
|
headerName: 'Código Cliente',
|
||||||
|
width: 120,
|
||||||
|
minWidth: 110,
|
||||||
|
headerAlign: 'right',
|
||||||
|
align: 'right',
|
||||||
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
|
<CellNumeric value={params.value} secondary />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
field: 'storeId',
|
field: 'storeId',
|
||||||
headerName: 'Filial',
|
headerName: 'Filial',
|
||||||
|
|
@ -83,11 +183,7 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => {
|
renderCell: (params: Readonly<GridRenderCellParams>) => {
|
||||||
const storeId = String(params.value);
|
const storeId = String(params.value);
|
||||||
const storeName = storesMap?.get(storeId) || storeId;
|
const storeName = storesMap?.get(storeId) || storeId;
|
||||||
return (
|
return <CellText value={storeName} />;
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
|
||||||
{storeName}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -96,11 +192,11 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
width: 200,
|
width: 200,
|
||||||
minWidth: 180,
|
minWidth: 180,
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
<CellText value={params.value} />
|
||||||
{params.value}
|
|
||||||
</Typography>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
headerName: 'Situação',
|
headerName: 'Situação',
|
||||||
|
|
@ -116,11 +212,7 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
label={chipProps.label}
|
label={chipProps.label}
|
||||||
color={chipProps.color}
|
color={chipProps.color}
|
||||||
size="small"
|
size="small"
|
||||||
sx={{
|
sx={CHIP_STYLES}
|
||||||
fontSize: '0.6875rem',
|
|
||||||
height: 22,
|
|
||||||
fontWeight: 300,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
@ -132,11 +224,11 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
width: 140,
|
width: 140,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
<CellText value={params.value} />
|
||||||
{params.value}
|
|
||||||
</Typography>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
field: 'amount',
|
field: 'amount',
|
||||||
headerName: 'Valor Total',
|
headerName: 'Valor Total',
|
||||||
|
|
@ -148,61 +240,7 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
align: 'right',
|
align: 'right',
|
||||||
valueFormatter: (value) => formatCurrency(value as number),
|
valueFormatter: (value) => formatCurrency(value as number),
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography
|
<CellNumeric value={params.value} formatter={formatCurrency} fontWeight={500} />
|
||||||
variant="body2"
|
|
||||||
sx={{ fontSize: '0.75rem', fontWeight: 500, textAlign: 'right' }}
|
|
||||||
>
|
|
||||||
{formatCurrency(params.value)}
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'invoiceNumber',
|
|
||||||
headerName: 'Nota Fiscal',
|
|
||||||
width: 120,
|
|
||||||
minWidth: 110,
|
|
||||||
headerAlign: 'right',
|
|
||||||
align: 'right',
|
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ fontSize: '0.75rem', textAlign: 'right' }}
|
|
||||||
>
|
|
||||||
{params.value && params.value !== '-' ? params.value : '-'}
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'billingId',
|
|
||||||
headerName: 'Cobrança',
|
|
||||||
width: 120,
|
|
||||||
minWidth: 110,
|
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
|
||||||
{params.value || '-'}
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'sellerName',
|
|
||||||
headerName: 'Vendedor',
|
|
||||||
width: 200,
|
|
||||||
minWidth: 180,
|
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
|
||||||
{params.value}
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'deliveryType',
|
|
||||||
headerName: 'Tipo de Entrega',
|
|
||||||
width: 160,
|
|
||||||
minWidth: 140,
|
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
|
||||||
{params.value}
|
|
||||||
</Typography>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -216,12 +254,30 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
align: 'right',
|
align: 'right',
|
||||||
valueFormatter: (value) => formatNumber(value as number),
|
valueFormatter: (value) => formatNumber(value as number),
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography
|
<CellNumeric value={params.value} formatter={formatNumber} />
|
||||||
variant="body2"
|
),
|
||||||
sx={{ fontSize: '0.75rem', textAlign: 'right' }}
|
},
|
||||||
>
|
|
||||||
{formatNumber(params.value)}
|
|
||||||
</Typography>
|
{
|
||||||
|
field: 'invoiceNumber',
|
||||||
|
headerName: 'Nota Fiscal',
|
||||||
|
width: 120,
|
||||||
|
minWidth: 110,
|
||||||
|
headerAlign: 'right',
|
||||||
|
align: 'right',
|
||||||
|
renderCell: (params: Readonly<GridRenderCellParams>) => {
|
||||||
|
const value = params.value && params.value !== '-' ? params.value : '-';
|
||||||
|
return <CellNumeric value={value} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'invoiceDate',
|
||||||
|
headerName: 'Data Faturamento',
|
||||||
|
width: 150,
|
||||||
|
minWidth: 140,
|
||||||
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
|
<CellDate value={params.value} time={params.row.invoiceTime} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -230,26 +286,69 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
width: 160,
|
width: 160,
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
<CellText value={params.value} />
|
||||||
{params.value}
|
),
|
||||||
</Typography>
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'billingId',
|
||||||
|
headerName: 'Cobrança',
|
||||||
|
width: 120,
|
||||||
|
minWidth: 110,
|
||||||
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
|
<CellText value={params.value} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'customerId',
|
field: 'paymentName',
|
||||||
headerName: 'Código Cliente',
|
headerName: 'Pagamento',
|
||||||
width: 120,
|
width: 140,
|
||||||
minWidth: 110,
|
minWidth: 130,
|
||||||
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
|
<CellText value={params.value} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'sellerName',
|
||||||
|
headerName: 'Vendedor',
|
||||||
|
width: 200,
|
||||||
|
minWidth: 180,
|
||||||
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
|
<CellText value={params.value} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'sellerId',
|
||||||
|
headerName: 'RCA',
|
||||||
|
width: 100,
|
||||||
|
minWidth: 90,
|
||||||
headerAlign: 'right',
|
headerAlign: 'right',
|
||||||
align: 'right',
|
align: 'right',
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography
|
<CellNumeric value={params.value} secondary />
|
||||||
variant="body2"
|
),
|
||||||
color="text.secondary"
|
},
|
||||||
sx={{ fontSize: '0.75rem', textAlign: 'right' }}
|
{
|
||||||
>
|
field: 'codusur2Name',
|
||||||
{params.value || '-'}
|
headerName: 'RCA 2',
|
||||||
</Typography>
|
width: 180,
|
||||||
|
minWidth: 160,
|
||||||
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
|
<CellText value={params.value} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'deliveryType',
|
||||||
|
headerName: 'Tipo de Entrega',
|
||||||
|
width: 160,
|
||||||
|
minWidth: 140,
|
||||||
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
|
<CellText value={params.value} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -258,9 +357,7 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
width: 130,
|
width: 130,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }}>
|
<CellDate value={params.value} />
|
||||||
{params.value ? formatDate(params.value) : '-'}
|
|
||||||
</Typography>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -269,9 +366,7 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
width: 180,
|
width: 180,
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
<CellText value={params.value} />
|
||||||
{params.value}
|
|
||||||
</Typography>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -284,15 +379,7 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
const chipProps = getPriorityChipProps(priority);
|
const chipProps = getPriorityChipProps(priority);
|
||||||
|
|
||||||
if (!chipProps) {
|
if (!chipProps) {
|
||||||
return (
|
return <CellText value="-" secondary />;
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ fontSize: '0.75rem', color: 'text.secondary' }}
|
|
||||||
noWrap
|
|
||||||
>
|
|
||||||
-
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -301,121 +388,19 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
label={chipProps.label}
|
label={chipProps.label}
|
||||||
color={chipProps.color}
|
color={chipProps.color}
|
||||||
size="small"
|
size="small"
|
||||||
sx={{
|
sx={CHIP_PRIORITY_STYLES}
|
||||||
fontSize: '0.6875rem',
|
|
||||||
height: 22,
|
|
||||||
fontWeight: 300,
|
|
||||||
maxWidth: '100%',
|
|
||||||
'& .MuiChip-label': {
|
|
||||||
px: 1,
|
|
||||||
py: 0,
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: 'invoiceDate',
|
|
||||||
headerName: 'Data Faturamento',
|
|
||||||
width: 150,
|
|
||||||
minWidth: 140,
|
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => {
|
|
||||||
const dateStr = formatDate(params.value);
|
|
||||||
const timeStr = params.row.invoiceTime;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ fontSize: '0.75rem', whiteSpace: 'nowrap' }}
|
|
||||||
>
|
|
||||||
{dateStr}
|
|
||||||
{timeStr && (
|
|
||||||
<Box
|
|
||||||
component="span"
|
|
||||||
sx={{ color: 'text.secondary', fontSize: '0.6875rem', ml: 0.5 }}
|
|
||||||
>
|
|
||||||
{timeStr}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'invoiceTime',
|
|
||||||
headerName: 'Hora Faturamento',
|
|
||||||
width: 120,
|
|
||||||
minWidth: 110,
|
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }}>
|
|
||||||
{params.value || '-'}
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
field: 'confirmDeliveryDate',
|
field: 'confirmDeliveryDate',
|
||||||
headerName: 'Data Confirmação Entrega',
|
headerName: 'Data Confirmação Entrega',
|
||||||
width: 150,
|
width: 150,
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }}>
|
<CellDate value={params.value} />
|
||||||
{params.value ? formatDate(params.value) : '-'}
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'paymentName',
|
|
||||||
headerName: 'Pagamento',
|
|
||||||
width: 140,
|
|
||||||
minWidth: 130,
|
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
|
||||||
{params.value}
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'sellerId',
|
|
||||||
headerName: 'RCA',
|
|
||||||
width: 100,
|
|
||||||
minWidth: 90,
|
|
||||||
headerAlign: 'right',
|
|
||||||
align: 'right',
|
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color="text.secondary"
|
|
||||||
sx={{ fontSize: '0.75rem', textAlign: 'right' }}
|
|
||||||
>
|
|
||||||
{params.value || '-'}
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'partnerName',
|
|
||||||
headerName: 'Parceiro',
|
|
||||||
width: 180,
|
|
||||||
minWidth: 160,
|
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
|
||||||
{params.value}
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'codusur2Name',
|
|
||||||
headerName: 'RCA 2',
|
|
||||||
width: 180,
|
|
||||||
minWidth: 160,
|
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
|
||||||
{params.value}
|
|
||||||
</Typography>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -424,9 +409,18 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
width: 160,
|
width: 160,
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
<CellText value={params.value} />
|
||||||
{params.value}
|
),
|
||||||
</Typography>
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'partnerName',
|
||||||
|
headerName: 'Parceiro',
|
||||||
|
width: 180,
|
||||||
|
minWidth: 160,
|
||||||
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
|
<CellText value={params.value} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -435,10 +429,8 @@ export const createOrderColumns = (options?: CreateOrderColumnsOptions): GridCol
|
||||||
width: 180,
|
width: 180,
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
renderCell: (params: Readonly<GridRenderCellParams>) => (
|
||||||
<Typography variant="body2" sx={{ fontSize: '0.75rem' }} noWrap>
|
<CellText value={params.value} />
|
||||||
{params.value}
|
|
||||||
</Typography>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import { useOrderFilters } from '../hooks/useOrderFilters';
|
import { useOrderFilters } from '../hooks/useOrderFilters';
|
||||||
|
import { useOrders } from '../hooks/useOrders';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
TextField,
|
TextField,
|
||||||
|
|
@ -73,7 +74,7 @@ export const SearchBar = () => {
|
||||||
const [customerSearchTerm, setCustomerSearchTerm] = useState('');
|
const [customerSearchTerm, setCustomerSearchTerm] = useState('');
|
||||||
const customers = useCustomers(customerSearchTerm);
|
const customers = useCustomers(customerSearchTerm);
|
||||||
const [showAdvancedFilters, setShowAdvancedFilters] = useState(false);
|
const [showAdvancedFilters, setShowAdvancedFilters] = useState(false);
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
const { isFetching } = useOrders();
|
||||||
const [touchedFields, setTouchedFields] = useState<{
|
const [touchedFields, setTouchedFields] = useState<{
|
||||||
createDateIni?: boolean;
|
createDateIni?: boolean;
|
||||||
createDateEnd?: boolean;
|
createDateEnd?: boolean;
|
||||||
|
|
@ -141,13 +142,10 @@ export const SearchBar = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSearching(true);
|
|
||||||
setUrlFilters({
|
setUrlFilters({
|
||||||
...localFilters,
|
...localFilters,
|
||||||
searchTriggered: true,
|
searchTriggered: true,
|
||||||
});
|
});
|
||||||
// Reset loading state after a short delay
|
|
||||||
setTimeout(() => setIsSearching(false), 500);
|
|
||||||
}, [localFilters, setUrlFilters, validateDates]);
|
}, [localFilters, setUrlFilters, validateDates]);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
|
|
@ -230,7 +228,7 @@ export const SearchBar = () => {
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Autocomplete do MUI para Cliente */}
|
{/* Autocomplete do MUI para Cliente */}
|
||||||
<Grid size={{ xs: 12, sm: 6, md: 2.5 }}>
|
<Grid size={{ xs: 12, sm: 6, md: 2 }}>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
size="small"
|
size="small"
|
||||||
options={customers.options}
|
options={customers.options}
|
||||||
|
|
@ -294,7 +292,7 @@ export const SearchBar = () => {
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Campos de Data */}
|
{/* Campos de Data */}
|
||||||
<Grid size={{ xs: 12, sm: 12, md: 4 }}>
|
<Grid size={{ xs: 12, sm: 12, md: 3.5 }}>
|
||||||
<LocalizationProvider
|
<LocalizationProvider
|
||||||
dateAdapter={AdapterMoment}
|
dateAdapter={AdapterMoment}
|
||||||
adapterLocale="pt-br"
|
adapterLocale="pt-br"
|
||||||
|
|
@ -391,33 +389,58 @@ export const SearchBar = () => {
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Botões de Ação */}
|
{/* Botão Mais Filtros - inline com filtros primários */}
|
||||||
<Grid
|
<Grid
|
||||||
size={{ xs: 12, sm: 12, md: 1.5 }}
|
size={{ xs: 12, sm: 12, md: 2.5 }}
|
||||||
sx={{ display: 'flex', justifyContent: { xs: 'stretch', sm: 'flex-end' } }}
|
sx={{ display: 'flex', alignItems: 'flex-end', justifyContent: { xs: 'flex-start', md: 'flex-end' } }}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', gap: 1, width: { xs: '100%', sm: 'auto' } }}>
|
<Badge
|
||||||
|
badgeContent={advancedFiltersCount}
|
||||||
|
color="primary"
|
||||||
|
invisible={advancedFiltersCount === 0}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => setShowAdvancedFilters(!showAdvancedFilters)}
|
||||||
|
endIcon={
|
||||||
|
showAdvancedFilters ? <ExpandLessIcon /> : <ExpandMoreIcon />
|
||||||
|
}
|
||||||
|
aria-label={showAdvancedFilters ? 'Ocultar filtros avançados' : 'Mostrar filtros avançados'}
|
||||||
|
sx={{ textTransform: 'none', color: 'text.secondary', minHeight: 40 }}
|
||||||
|
>
|
||||||
|
{showAdvancedFilters ? 'Menos filtros' : 'Mais filtros'}
|
||||||
|
</Button>
|
||||||
|
</Badge>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Botões de Ação - nova linha abaixo */}
|
||||||
|
<Grid
|
||||||
|
size={{ xs: 12 }}
|
||||||
|
sx={{ display: 'flex', justifyContent: 'flex-end', mt: 1 }}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1, width: { xs: '100%', sm: 'auto' }, flexWrap: 'nowrap' }}>
|
||||||
<Tooltip title="Limpar filtros" arrow>
|
<Tooltip title="Limpar filtros" arrow>
|
||||||
<span>
|
<span>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
|
size="small"
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
aria-label="Limpar filtros"
|
aria-label="Limpar filtros"
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: { xs: 48, md: 40 },
|
minWidth: { xs: 'auto', sm: 90 },
|
||||||
minHeight: 44,
|
minHeight: 32,
|
||||||
px: 1.5,
|
px: 1.5,
|
||||||
|
flexShrink: 0,
|
||||||
flex: { xs: 1, sm: 'none' },
|
flex: { xs: 1, sm: 'none' },
|
||||||
|
fontSize: '0.8125rem',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
bgcolor: 'action.hover',
|
bgcolor: 'action.hover',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ResetIcon />
|
<ResetIcon sx={{ mr: 0.5, fontSize: 18 }} />
|
||||||
<Box component="span" sx={{ display: { xs: 'none', md: 'inline' }, ml: 0.5 }}>
|
|
||||||
Limpar
|
Limpar
|
||||||
</Box>
|
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -433,27 +456,28 @@ export const SearchBar = () => {
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
size="small"
|
||||||
onClick={handleFilter}
|
onClick={handleFilter}
|
||||||
disabled={!isDateValid || !!dateError || isSearching}
|
disabled={!isDateValid || !!dateError || isFetching}
|
||||||
aria-label="Buscar pedidos"
|
aria-label="Buscar pedidos"
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: { xs: 48, md: 40 },
|
minWidth: { xs: 'auto', sm: 90 },
|
||||||
minHeight: 44,
|
minHeight: 32,
|
||||||
px: 1.5,
|
px: 1.5,
|
||||||
flex: { xs: 2, sm: 'none' },
|
flexShrink: 0,
|
||||||
|
flex: { xs: 1, sm: 'none' },
|
||||||
|
fontSize: '0.8125rem',
|
||||||
'&:disabled': {
|
'&:disabled': {
|
||||||
opacity: 0.6,
|
opacity: 0.6,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isSearching ? (
|
{isFetching ? (
|
||||||
<CircularProgress size={20} color="inherit" />
|
<CircularProgress size={16} color="inherit" />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<SearchIcon />
|
<SearchIcon sx={{ mr: 0.5, fontSize: 18 }} />
|
||||||
<Box component="span" sx={{ display: { xs: 'none', md: 'inline' }, ml: 0.5 }}>
|
|
||||||
Buscar
|
Buscar
|
||||||
</Box>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -464,25 +488,6 @@ export const SearchBar = () => {
|
||||||
|
|
||||||
{/* --- Advanced Filters (Collapsible) --- */}
|
{/* --- Advanced Filters (Collapsible) --- */}
|
||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 1 }}>
|
|
||||||
<Badge
|
|
||||||
badgeContent={advancedFiltersCount}
|
|
||||||
color="primary"
|
|
||||||
invisible={advancedFiltersCount === 0}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={() => setShowAdvancedFilters(!showAdvancedFilters)}
|
|
||||||
endIcon={
|
|
||||||
showAdvancedFilters ? <ExpandLessIcon /> : <ExpandMoreIcon />
|
|
||||||
}
|
|
||||||
aria-label={showAdvancedFilters ? 'Ocultar filtros avançados' : 'Mostrar filtros avançados'}
|
|
||||||
sx={{ textTransform: 'none', color: 'text.secondary' }}
|
|
||||||
>
|
|
||||||
{showAdvancedFilters ? 'Menos filtros' : 'Mais filtros'}
|
|
||||||
</Button>
|
|
||||||
</Badge>
|
|
||||||
</Box>
|
|
||||||
<Collapse in={showAdvancedFilters}>
|
<Collapse in={showAdvancedFilters}>
|
||||||
<Grid container spacing={2} sx={{ pt: 2 }}>
|
<Grid container spacing={2} sx={{ pt: 2 }}>
|
||||||
{/* Autocomplete do MUI para Múltiplas Filiais (codfilial) */}
|
{/* Autocomplete do MUI para Múltiplas Filiais (codfilial) */}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid-premium';
|
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid-premium';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
import {
|
import {
|
||||||
formatCurrency,
|
formatCurrency,
|
||||||
formatDate,
|
formatDate,
|
||||||
|
|
@ -7,6 +8,19 @@ import {
|
||||||
getStatusLabel,
|
getStatusLabel,
|
||||||
} from '../../utils/orderFormatters';
|
} from '../../utils/orderFormatters';
|
||||||
|
|
||||||
|
const TextCell = ({ value }: { value: string | null | undefined }) => (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
lineHeight: 1.3,
|
||||||
|
py: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value ?? ''}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
export const createInformationPanelColumns = (): GridColDef[] => [
|
export const createInformationPanelColumns = (): GridColDef[] => [
|
||||||
{
|
{
|
||||||
field: 'customerName',
|
field: 'customerName',
|
||||||
|
|
@ -14,6 +28,7 @@ export const createInformationPanelColumns = (): GridColDef[] => [
|
||||||
minWidth: 180,
|
minWidth: 180,
|
||||||
flex: 1.5,
|
flex: 1.5,
|
||||||
description: 'Nome do cliente do pedido',
|
description: 'Nome do cliente do pedido',
|
||||||
|
renderCell: (params: GridRenderCellParams) => <TextCell value={params.value} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'storeId',
|
field: 'storeId',
|
||||||
|
|
@ -55,6 +70,7 @@ export const createInformationPanelColumns = (): GridColDef[] => [
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
description: 'Forma de pagamento utilizada',
|
description: 'Forma de pagamento utilizada',
|
||||||
|
renderCell: (params: GridRenderCellParams) => <TextCell value={params.value} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'billingName',
|
field: 'billingName',
|
||||||
|
|
@ -62,6 +78,7 @@ export const createInformationPanelColumns = (): GridColDef[] => [
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
description: 'Condição de pagamento',
|
description: 'Condição de pagamento',
|
||||||
|
renderCell: (params: GridRenderCellParams) => <TextCell value={params.value} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'amount',
|
field: 'amount',
|
||||||
|
|
@ -78,6 +95,7 @@ export const createInformationPanelColumns = (): GridColDef[] => [
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
description: 'Tipo de entrega selecionado',
|
description: 'Tipo de entrega selecionado',
|
||||||
|
renderCell: (params: GridRenderCellParams) => <TextCell value={params.value} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'deliveryLocal',
|
field: 'deliveryLocal',
|
||||||
|
|
@ -85,5 +103,7 @@ export const createInformationPanelColumns = (): GridColDef[] => [
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
flex: 1.2,
|
flex: 1.2,
|
||||||
description: 'Local de entrega do pedido',
|
description: 'Local de entrega do pedido',
|
||||||
|
renderCell: (params: GridRenderCellParams) => <TextCell value={params.value} />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { orderService } from '../api/order.service';
|
||||||
export function useSellers() {
|
export function useSellers() {
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: ['sellers'],
|
queryKey: ['sellers'],
|
||||||
queryFn: () => orderService.findsellers(),
|
queryFn: () => orderService.findSellers(),
|
||||||
staleTime: 1000 * 60 * 30, // 30 minutes
|
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||||
retry: 1,
|
retry: 1,
|
||||||
retryOnMount: false,
|
retryOnMount: false,
|
||||||
|
|
|
||||||
|
|
@ -130,27 +130,17 @@ export default function ProfilePage() {
|
||||||
{displayData.sectorId !== undefined && (
|
{displayData.sectorId !== undefined && (
|
||||||
<Grid size={{ xs: 12, sm: 4 }}>
|
<Grid size={{ xs: 12, sm: 4 }}>
|
||||||
<Typography variant="body2" color="textSecondary">
|
<Typography variant="body2" color="textSecondary">
|
||||||
Setor ID
|
Setor
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" fontWeight="500">
|
<Typography variant="body1" fontWeight="500">
|
||||||
{displayData.sectorId}
|
{displayData.sectorId}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
{displayData.sectorManagerId !== undefined && (
|
|
||||||
<Grid size={{ xs: 12, sm: 4 }}>
|
|
||||||
<Typography variant="body2" color="textSecondary">
|
|
||||||
Gerente de Setor ID
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body1" fontWeight="500">
|
|
||||||
{displayData.sectorManagerId}
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
{displayData.supervisorId !== undefined && (
|
{displayData.supervisorId !== undefined && (
|
||||||
<Grid size={{ xs: 12, sm: 4 }}>
|
<Grid size={{ xs: 12, sm: 4 }}>
|
||||||
<Typography variant="body2" color="textSecondary">
|
<Typography variant="body2" color="textSecondary">
|
||||||
Supervisor ID
|
Supervisor
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" fontWeight="500">
|
<Typography variant="body1" fontWeight="500">
|
||||||
{displayData.supervisorId}
|
{displayData.supervisorId}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue