diff --git a/src/features/dashboard/header/AppLinks.tsx b/src/features/dashboard/header/AppLinks.tsx
deleted file mode 100644
index 80f8e8f..0000000
--- a/src/features/dashboard/header/AppLinks.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { Avatar, Box, Typography, Grid, Stack } from '@mui/material';
-import * as dropdownData from './data';
-import Link from 'next/link';
-import React from 'react';
-
-const AppLinks = () => {
- return (
-
- {dropdownData.appsLink.map((links) => (
-
-
-
-
-
-
-
-
- {links.title}
-
-
- {links.subtext}
-
-
-
-
-
- ))}
-
- );
-};
-
-export default AppLinks;
diff --git a/src/features/dashboard/header/Header.tsx b/src/features/dashboard/header/Header.tsx
index 1857583..16cee51 100644
--- a/src/features/dashboard/header/Header.tsx
+++ b/src/features/dashboard/header/Header.tsx
@@ -12,11 +12,8 @@ import DarkModeIcon from '@mui/icons-material/DarkMode';
import LightModeIcon from '@mui/icons-material/LightMode';
import { useCustomizerStore } from '../store/useCustomizerStore';
-import Notifications from './Notification';
import Profile from './Profile';
import Search from './Search';
-import Navigation from './Navigation';
-import MobileRightSidebar from './MobileRightSidebar';
const AppBarStyled = styled(AppBar)(({ theme }) => ({
boxShadow: 'none',
@@ -33,7 +30,6 @@ const ToolbarStyled = styled(Toolbar)(({ theme }) => ({
const Header = () => {
const lgUp = useMediaQuery((theme: Theme) => theme.breakpoints.up('lg'));
- const lgDown = useMediaQuery((theme: Theme) => theme.breakpoints.down('lg'));
const {
activeMode,
@@ -51,7 +47,7 @@ const Header = () => {
{/* ------------------------------------------- */}
- {/* Toggle Button Sidebar (Fluxo 2) */}
+ {/* Toggle Button Sidebar */}
{/* ------------------------------------------- */}
{
{/* ------------------------------------------- */}
- {/* Search & Navigation */}
+ {/* Search */}
{/* ------------------------------------------- */}
- {lgUp && }
@@ -84,12 +79,9 @@ const Header = () => {
{activeMode === 'light' ? : }
-
-
{/* ------------------------------------------- */}
- {/* Mobile Right Sidebar & Profile */}
+ {/* Profile */}
{/* ------------------------------------------- */}
- {lgDown && }
@@ -98,3 +90,4 @@ const Header = () => {
};
export default Header;
+
diff --git a/src/features/dashboard/header/MobileRightSidebar.tsx b/src/features/dashboard/header/MobileRightSidebar.tsx
deleted file mode 100644
index b165d4a..0000000
--- a/src/features/dashboard/header/MobileRightSidebar.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import React, { useState } from 'react';
-import {
- IconApps,
- IconChevronDown,
- IconChevronUp,
- IconGridDots,
- IconMail,
- IconMessages,
-} from '@tabler/icons-react';
-import {
- Box,
- Typography,
- Drawer,
- IconButton,
- List,
- ListItemButton,
- ListItemIcon,
- ListItemText,
- Collapse,
-} from '@mui/material';
-
-import Link from 'next/link';
-import AppLinks from './AppLinks';
-
-const MobileRightSidebar = () => {
- const [showDrawer, setShowDrawer] = useState(false);
-
- const [open, setOpen] = React.useState(true);
-
- const handleClick = () => {
- setOpen(!open);
- };
-
- const cartContent = (
-
- {/* ------------------------------------------- */}
- {/* Apps Content */}
- {/* ------------------------------------------- */}
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
- Email
-
-
-
-
-
-
-
-
-
- Apps
-
-
- {open ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- );
-
- return (
-
- setShowDrawer(true)}
- sx={{
- ...(showDrawer && {
- color: 'primary.main',
- }),
- }}
- >
-
-
- {/* ------------------------------------------- */}
- {/* Cart Sidebar */}
- {/* ------------------------------------------- */}
- setShowDrawer(false)}
- PaperProps={{ sx: { width: '300px' } }}
- >
-
-
- Navigation
-
-
-
- {/* component */}
- {cartContent}
-
-
- );
-};
-
-export default MobileRightSidebar;
diff --git a/src/features/dashboard/header/Navigation.tsx b/src/features/dashboard/header/Navigation.tsx
deleted file mode 100644
index e371d42..0000000
--- a/src/features/dashboard/header/Navigation.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import { useState } from 'react';
-import { Box, Menu, Typography, Button, Divider, Grid } from '@mui/material';
-import Link from 'next/link';
-import { IconChevronDown, IconHelp } from '@tabler/icons-react';
-import AppLinks from './AppLinks';
-
-const AppDD = () => {
- const [anchorEl2, setAnchorEl2] = useState(null);
-
- const handleClick2 = (event: any) => {
- setAnchorEl2(event.currentTarget);
- };
-
- const handleClose2 = () => {
- setAnchorEl2(null);
- };
-
- return (
- <>
-
-
- {/* ------------------------------------------- */}
- {/* Message Dropdown */}
- {/* ------------------------------------------- */}
-
-
-
-
- >
- );
-};
-
-export default AppDD;
diff --git a/src/features/dashboard/header/Notification.tsx b/src/features/dashboard/header/Notification.tsx
deleted file mode 100644
index 89d49e1..0000000
--- a/src/features/dashboard/header/Notification.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-import React, { useState } from 'react';
-import {
- IconButton,
- Box,
- Badge,
- Menu,
- MenuItem,
- Avatar,
- Typography,
- Button,
- Chip,
-} from '@mui/material';
-import * as dropdownData from './data';
-import { Scrollbar } from '@/shared/components';
-
-import { IconBellRinging } from '@tabler/icons-react';
-import { Stack } from '@mui/system';
-import Link from 'next/link';
-
-const Notifications = () => {
- const [anchorEl2, setAnchorEl2] = useState(null);
-
- const handleClick2 = (event: any) => {
- setAnchorEl2(event.currentTarget);
- };
-
- const handleClose2 = () => {
- setAnchorEl2(null);
- };
-
- return (
-
-
-
-
-
-
- {/* ------------------------------------------- */}
- {/* Message Dropdown */}
- {/* ------------------------------------------- */}
-
-
- );
-};
-
-export default Notifications;
diff --git a/src/features/dashboard/header/Profile.tsx b/src/features/dashboard/header/Profile.tsx
index b8f4aee..0d8394d 100644
--- a/src/features/dashboard/header/Profile.tsx
+++ b/src/features/dashboard/header/Profile.tsx
@@ -5,11 +5,9 @@ import {
Menu,
Avatar,
Typography,
- Divider,
Button,
IconButton,
} from '@mui/material';
-import { IconMail } from '@tabler/icons-react';
import { Stack } from '@mui/system';
import { useAuthStore } from '../../login/store/useAuthStore';
@@ -27,10 +25,18 @@ const Profile = () => {
setAnchorEl2(null);
};
+ // Gera as iniciais do nome
+ const getIniciais = () => {
+ if (!user?.nome) return user?.userName?.[0]?.toUpperCase() || 'U';
+ const partes = user.nome.trim().split(' ').filter(Boolean);
+ if (partes.length === 1) return partes[0][0].toUpperCase();
+ return (partes[0][0] + partes[partes.length - 1][0]).toUpperCase();
+ };
+
return (
{
bgcolor: 'primary.main',
}}
>
- {user?.nome?.[0] || user?.userName?.[0] || 'U'}
+ {getIniciais()}
{/* ------------------------------------------- */}
- {/* Message Dropdown */}
+ {/* Profile Dropdown */}
{/* ------------------------------------------- */}
);
};
export default Profile;
+
diff --git a/src/features/dashboard/header/data.ts b/src/features/dashboard/header/data.ts
deleted file mode 100644
index 8383eb9..0000000
--- a/src/features/dashboard/header/data.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-// Notifications dropdown
-
-interface NotificationType {
- id: string;
- avatar: string;
- title: string;
- subtitle: string;
-}
-
-const notifications: NotificationType[] = [
- {
- id: '1',
- avatar: '/images/profile/user-2.jpg',
- title: 'Roman Joined the Team!',
- subtitle: 'Congratulate him',
- },
- {
- id: '2',
- avatar: '/images/profile/user-3.jpg',
- title: 'New message received',
- subtitle: 'Salma sent you new message',
- },
- {
- id: '3',
- avatar: '/images/profile/user-4.jpg',
- title: 'New Payment received',
- subtitle: 'Check your earnings',
- },
- {
- id: '4',
- avatar: '/images/profile/user-5.jpg',
- title: 'Jolly completed tasks',
- subtitle: 'Assign her new tasks',
- },
- {
- id: '5',
- avatar: '/images/profile/user-6.jpg',
- title: 'Roman Joined the Team!',
- subtitle: 'Congratulate him',
- },
- {
- id: '6',
- avatar: '/images/profile/user-7.jpg',
- title: 'New message received',
- subtitle: 'Salma sent you new message',
- },
- {
- id: '7',
- avatar: '/images/profile/user-8.jpg',
- title: 'New Payment received',
- subtitle: 'Check your earnings',
- },
- {
- id: '8',
- avatar: '/images/profile/user-9.jpg',
- title: 'Jolly completed tasks',
- subtitle: 'Assign her new tasks',
- },
-];
-
-//
-// Profile dropdown
-//
-interface ProfileType {
- href: string;
- title: string;
- subtitle: string;
- icon: any;
-}
-const profile: ProfileType[] = [
- {
- href: '/dashboard/profile',
- title: 'My Profile',
- subtitle: 'Account Settings',
- icon: '/images/svgs/icon-account.svg',
- },
- {
- href: '/apps/email',
- title: 'My Inbox',
- subtitle: 'Messages & Emails',
- icon: '/images/svgs/icon-inbox.svg',
- },
- {
- href: '/apps/notes',
- title: 'My Tasks',
- subtitle: 'To-do and Daily Tasks',
- icon: '/images/svgs/icon-tasks.svg',
- },
-];
-
-// apps dropdown
-
-interface AppsLinkType {
- href: string;
- title: string;
- subtext: string;
- avatar: string;
-}
-
-const appsLink: AppsLinkType[] = [
- {
- href: '/apps/chats',
- title: 'Chat Application',
- subtext: 'New messages arrived',
- avatar: '/images/svgs/icon-dd-chat.svg',
- },
- {
- href: '/apps/ecommerce/shop',
- title: 'eCommerce App',
- subtext: 'New stock available',
- avatar: '/images/svgs/icon-dd-cart.svg',
- },
- {
- href: '/apps/notes',
- title: 'Notes App',
- subtext: 'To-do and Daily tasks',
- avatar: '/images/svgs/icon-dd-invoice.svg',
- },
- {
- href: '/apps/contacts',
- title: 'Contact Application',
- subtext: '2 Unsaved Contacts',
- avatar: '/images/svgs/icon-dd-mobile.svg',
- },
- {
- href: '/apps/tickets',
- title: 'Tickets App',
- subtext: 'Submit tickets',
- avatar: '/images/svgs/icon-dd-lifebuoy.svg',
- },
- {
- href: '/apps/email',
- title: 'Email App',
- subtext: 'Get new emails',
- avatar: '/images/svgs/icon-dd-message-box.svg',
- },
- {
- href: '/apps/blog/post',
- title: 'Blog App',
- subtext: 'added new blog',
- avatar: '/images/svgs/icon-dd-application.svg',
- },
-];
-
-interface LinkType {
- href: string;
- title: string;
-}
-
-const pageLinks: LinkType[] = [
- {
- href: '/theme-pages/pricing',
- title: 'Pricing Page',
- },
- {
- href: '/auth/auth1/login',
- title: 'Authentication Design',
- },
- {
- href: '/auth/auth1/register',
- title: 'Register Now',
- },
- {
- href: '/404',
- title: '404 Error Page',
- },
- {
- href: '/apps/note',
- title: 'Notes App',
- },
- {
- href: '/apps/user-profile/profile',
- title: 'User Application',
- },
- {
- href: '/apps/blog/post',
- title: 'Blog Design',
- },
- {
- href: '/apps/ecommerce/checkout',
- title: 'Shopping Cart',
- },
-];
-
-export { notifications, profile, pageLinks, appsLink };
diff --git a/src/features/login/components/AuthInitializer.tsx b/src/features/login/components/AuthInitializer.tsx
index 93dcfd6..4df399f 100644
--- a/src/features/login/components/AuthInitializer.tsx
+++ b/src/features/login/components/AuthInitializer.tsx
@@ -20,7 +20,7 @@ export function AuthInitializer({ children }: { children: React.ReactNode }) {
try {
await loginService.refreshToken();
- const profile = await profileService.getMe();
+ const profile = await profileService.obterColaboradorAtual();
setUser(mapToSafeProfile(profile));
} catch (error) {
console.warn('Sessão expirada ou inválida', error);
diff --git a/src/features/login/hooks/useAuth.ts b/src/features/login/hooks/useAuth.ts
index 94c9032..6c2a213 100644
--- a/src/features/login/hooks/useAuth.ts
+++ b/src/features/login/hooks/useAuth.ts
@@ -1,10 +1,11 @@
-import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import { useAuthStore } from '../store/useAuthStore';
import { loginService } from '../services/login.service';
import { profileService } from '../../profile/services/profile.service';
import { clearAuthData } from '../utils/tokenRefresh';
import { mapToSafeProfile } from '../utils/mappers';
+import { COLABORADOR_ATUAL_QUERY_KEY } from '../../profile';
export function useAuth() {
const queryClient = useQueryClient();
@@ -16,11 +17,11 @@ export function useAuth() {
mutationFn: loginService.login,
onSuccess: async () => {
try {
- const profile = await profileService.getMe();
+ const profile = await profileService.obterColaboradorAtual();
const safeProfile = mapToSafeProfile(profile);
setUser(safeProfile);
- queryClient.setQueryData(['auth-me'], safeProfile);
+ queryClient.setQueryData(COLABORADOR_ATUAL_QUERY_KEY, profile);
router.push('/dashboard');
} catch (error) {
@@ -30,19 +31,6 @@ export function useAuth() {
},
});
- const useMe = () =>
- useQuery({
- queryKey: ['auth-me'],
- queryFn: async () => {
- const data = await profileService.getMe();
- const safeData = mapToSafeProfile(data);
- setUser(safeData);
- return safeData;
- },
- retry: false,
- staleTime: Infinity,
- });
-
const logout = async () => {
try {
await loginService.logout();
@@ -54,5 +42,5 @@ export function useAuth() {
}
};
- return { loginMutation, useMe, logout };
+ return { loginMutation, logout };
}
diff --git a/src/features/login/interfaces/types.ts b/src/features/login/interfaces/types.ts
index a6178bd..bd6f67e 100644
--- a/src/features/login/interfaces/types.ts
+++ b/src/features/login/interfaces/types.ts
@@ -1,6 +1,6 @@
import { z } from 'zod';
import type { ReactNode } from 'react';
-import type { UserProfile } from '../../profile/types';
+import type { UserProfileDto } from '../../profile/types';
import {
loginSchema,
@@ -38,9 +38,9 @@ export interface AuthLoginProps {
}
export interface AuthState {
- user: UserProfile | null;
+ user: UserProfileDto | null;
isAuthenticated: boolean;
- setUser: (user: UserProfile | null) => void;
+ setUser: (user: UserProfileDto | null) => void;
logout: () => void;
hydrate: () => void;
}
diff --git a/src/features/login/utils/mappers.ts b/src/features/login/utils/mappers.ts
index e130a0d..d1c2013 100644
--- a/src/features/login/utils/mappers.ts
+++ b/src/features/login/utils/mappers.ts
@@ -1,16 +1,22 @@
-import { UserProfile } from '../../profile/types';
+import { UserProfileDto } from '../../profile/types';
+import { Colaborador } from '../../profile/domain/Colaborador';
-export const mapToSafeProfile = (data: any): UserProfile => {
+/**
+ * Converte Colaborador (entidade) ou objeto raw para UserProfileDto.
+ * Suporta ambos os formatos de propriedade (entidade DDD e DTO da API).
+ */
+export const mapToSafeProfile = (data: Colaborador | any): UserProfileDto => {
return {
matricula: data.matricula,
userName: data.userName,
nome: data.nome,
codigoFilial: data.codigoFilial,
nomeFilial: data.nomeFilial,
- rca: data.rca,
- discountPercent: data.discountPercent,
- sectorId: data.sectorId,
- sectorManagerId: data.sectorManagerId,
- supervisorId: data.supervisorId,
+ rca: data.codigoRCA ?? data.rca,
+ discountPercent: data.percentualDesconto ?? data._percentualDesconto ?? data.discountPercent,
+ sectorId: data.codigoSetor ?? data.sectorId,
+ sectorManagerId: data.sectorManagerId ?? 0,
+ supervisorId: data.codigoSupervisor ?? data.supervisorId,
};
};
+
diff --git a/src/features/orders/api/order.service.ts b/src/features/orders/api/order.service.ts
index d21f0ea..7b1bf58 100644
--- a/src/features/orders/api/order.service.ts
+++ b/src/features/orders/api/order.service.ts
@@ -15,6 +15,8 @@ import {
storesResponseSchema,
customersResponseSchema,
sellersResponseSchema,
+ partnersResponseSchema,
+ productsResponseSchema,
unwrapApiData,
} from '../schemas/order.schema';
import {
@@ -178,5 +180,42 @@ export const orderService = {
return unwrapApiData(response, cuttingItemResponseSchema, []);
},
+ /**
+ * Busca parceiros por nome/CPF.
+ * Retorna array vazio se o termo de busca tiver menos de 2 caracteres.
+ *
+ * @param {string} filter - O termo de busca (mínimo 2 caracteres)
+ * @returns {Promise>} Array de parceiros correspondentes
+ */
+ findPartners: async (
+ filter: string
+ ): Promise> => {
+ if (!filter || filter.trim().length < 2) return [];
+
+ const response = await ordersApi.get(
+ `/api/v1/parceiros/${encodeURIComponent(filter)}`
+ );
+ return unwrapApiData(response, partnersResponseSchema, []);
+ },
+
+ /**
+ * Busca produtos por código ou descrição.
+ * Retorna array vazio se o termo de busca tiver menos de 2 caracteres.
+ *
+ * @param {string} term - O termo de busca (mínimo 2 caracteres)
+ * @returns {Promise>} Array de produtos correspondentes
+ */
+ findProducts: async (
+ term: string
+ ): Promise> => {
+ if (!term || term.trim().length < 2) return [];
+
+ const response = await ordersApi.get(
+ `/api/v1/data-consult/products/${encodeURIComponent(term)}`
+ );
+ return unwrapApiData(response, productsResponseSchema, []);
+ },
+
+
};
diff --git a/src/features/orders/components/SearchBar.tsx b/src/features/orders/components/SearchBar.tsx
index 626bfe5..940b2f3 100644
--- a/src/features/orders/components/SearchBar.tsx
+++ b/src/features/orders/components/SearchBar.tsx
@@ -20,6 +20,8 @@ import {
import { useStores } from '../store/useStores';
import { useCustomers } from '../hooks/useCustomers';
import { useSellers } from '../hooks/useSellers';
+import { usePartners } from '../hooks/usePartners';
+import { useProducts } from '../hooks/useProducts';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment';
@@ -39,6 +41,10 @@ interface LocalFilters {
orderId: number | null;
customerId: number | null;
customerName: string | null;
+ partnerId: string | null;
+ partnerName: string | null;
+ productId: number | null;
+ productName: string | null;
createDateIni: string | null;
createDateEnd: string | null;
store: string[] | null;
@@ -54,6 +60,10 @@ const getInitialLocalFilters = (
orderId: urlFilters.orderId ?? null,
customerId: urlFilters.customerId ?? null,
customerName: urlFilters.customerName ?? null,
+ partnerId: urlFilters.partnerId ?? null,
+ partnerName: urlFilters.partnerName ?? null,
+ productId: urlFilters.productId ?? null,
+ productName: urlFilters.productName ?? null,
createDateIni: urlFilters.createDateIni ?? null,
createDateEnd: urlFilters.createDateEnd ?? null,
store: urlFilters.store ?? null,
@@ -73,6 +83,10 @@ export const SearchBar = () => {
const sellers = useSellers();
const [customerSearchTerm, setCustomerSearchTerm] = useState('');
const customers = useCustomers(customerSearchTerm);
+ const [partnerSearchTerm, setPartnerSearchTerm] = useState('');
+ const partners = usePartners(partnerSearchTerm);
+ const [productSearchTerm, setProductSearchTerm] = useState('');
+ const products = useProducts(productSearchTerm);
const [showAdvancedFilters, setShowAdvancedFilters] = useState(false);
const { isFetching } = useOrders();
const [touchedFields, setTouchedFields] = useState<{
@@ -94,6 +108,8 @@ export const SearchBar = () => {
const handleReset = useCallback(() => {
setTouchedFields({});
setCustomerSearchTerm('');
+ setPartnerSearchTerm('');
+ setProductSearchTerm('');
const resetState = {
status: null,
@@ -103,7 +119,6 @@ export const SearchBar = () => {
codusur2: null,
store: null,
orderId: null,
- productId: null,
stockId: null,
hasPreBox: false,
includeCheckout: false,
@@ -112,6 +127,10 @@ export const SearchBar = () => {
searchTriggered: false,
customerId: null,
customerName: null,
+ partnerId: null,
+ partnerName: null,
+ productId: null,
+ productName: null,
};
setLocalFilters(getInitialLocalFilters(resetState));
@@ -172,6 +191,8 @@ export const SearchBar = () => {
localFilters.store?.length,
localFilters.stockId?.length,
localFilters.sellerId,
+ localFilters.partnerId,
+ localFilters.productId,
].filter(Boolean).length;
return (
@@ -601,6 +622,139 @@ export const SearchBar = () => {
}}
/>
+
+ {/* Autocomplete do MUI para Parceiro */}
+
+ option.label}
+ isOptionEqualToValue={(option, value) => option.id === value.id}
+ value={
+ partners.options.find(
+ (option) =>
+ localFilters.partnerId ===
+ option.partner?.id?.toString()
+ ) || null
+ }
+ onChange={(_, newValue) => {
+ if (!newValue) {
+ updateLocalFilter('partnerName', null);
+ updateLocalFilter('partnerId', null);
+ setPartnerSearchTerm('');
+ return;
+ }
+
+ updateLocalFilter(
+ 'partnerId',
+ newValue.partner?.id?.toString() || null
+ );
+ updateLocalFilter(
+ 'partnerName',
+ newValue.partner?.nome || null
+ );
+ }}
+ onInputChange={(_, newInputValue, reason) => {
+ if (reason === 'clear') {
+ updateLocalFilter('partnerName', null);
+ updateLocalFilter('partnerId', null);
+ setPartnerSearchTerm('');
+ return;
+ }
+
+ if (reason === 'input') {
+ setPartnerSearchTerm(newInputValue);
+ if (!newInputValue) {
+ updateLocalFilter('partnerName', null);
+ updateLocalFilter('partnerId', null);
+ setPartnerSearchTerm('');
+ }
+ }
+ }}
+ loading={partners.isLoading}
+ renderInput={(params: AutocompleteRenderInputParams) => (
+
+ )}
+ noOptionsText={
+ partnerSearchTerm.length < 2
+ ? 'Digite pelo menos 2 caracteres'
+ : 'Nenhum parceiro encontrado'
+ }
+ loadingText="Buscando parceiros..."
+ filterOptions={(x) => x}
+ clearOnBlur={false}
+ selectOnFocus
+ handleHomeEndKeys
+ />
+
+
+ {/* Autocomplete do MUI para Produto */}
+
+ option.label}
+ isOptionEqualToValue={(option, value) => option.id === value.id}
+ value={
+ products.options.find(
+ (option) => localFilters.productId === option.product?.id
+ ) || null
+ }
+ onChange={(_, newValue) => {
+ if (!newValue) {
+ updateLocalFilter('productName', null);
+ updateLocalFilter('productId', null);
+ setProductSearchTerm('');
+ return;
+ }
+
+ updateLocalFilter('productId', newValue.product?.id || null);
+ updateLocalFilter(
+ 'productName',
+ newValue.product?.description || null
+ );
+ }}
+ onInputChange={(_, newInputValue, reason) => {
+ if (reason === 'clear') {
+ updateLocalFilter('productName', null);
+ updateLocalFilter('productId', null);
+ setProductSearchTerm('');
+ return;
+ }
+
+ if (reason === 'input') {
+ setProductSearchTerm(newInputValue);
+ if (!newInputValue) {
+ updateLocalFilter('productName', null);
+ updateLocalFilter('productId', null);
+ setProductSearchTerm('');
+ }
+ }
+ }}
+ loading={products.isLoading}
+ renderInput={(params: AutocompleteRenderInputParams) => (
+
+ )}
+ noOptionsText={
+ productSearchTerm.length < 2
+ ? 'Digite pelo menos 2 caracteres'
+ : 'Nenhum produto encontrado'
+ }
+ loadingText="Buscando produtos..."
+ filterOptions={(x) => x}
+ clearOnBlur={false}
+ selectOnFocus
+ handleHomeEndKeys
+ />
+
diff --git a/src/features/orders/hooks/useOrderFilters.ts b/src/features/orders/hooks/useOrderFilters.ts
index dabce54..92bee7a 100644
--- a/src/features/orders/hooks/useOrderFilters.ts
+++ b/src/features/orders/hooks/useOrderFilters.ts
@@ -16,12 +16,15 @@ export const useOrderFilters = () => {
sellerId: parseAsString,
customerName: parseAsString,
customerId: parseAsInteger,
+ partnerName: parseAsString,
+ partnerId: parseAsString,
codfilial: parseAsArrayOf(parseAsString, ','),
codusur2: parseAsArrayOf(parseAsString, ','),
store: parseAsArrayOf(parseAsString, ','),
orderId: parseAsInteger,
productId: parseAsInteger,
+ productName: parseAsString,
stockId: parseAsArrayOf(parseAsString, ','),
hasPreBox: parseAsBoolean.withDefault(false),
diff --git a/src/features/orders/hooks/usePartners.ts b/src/features/orders/hooks/usePartners.ts
new file mode 100644
index 0000000..08a5691
--- /dev/null
+++ b/src/features/orders/hooks/usePartners.ts
@@ -0,0 +1,48 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { useQuery } from '@tanstack/react-query';
+import { orderService } from '../api/order.service';
+
+export interface Partner {
+ id: number;
+ cpf: string;
+ nome: string;
+}
+
+export function usePartners(searchTerm: string) {
+ const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm);
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setDebouncedSearchTerm(searchTerm);
+ }, 300);
+
+ return () => clearTimeout(timer);
+ }, [searchTerm]);
+
+ const isEnabled = debouncedSearchTerm.length >= 2;
+
+ const query = useQuery({
+ queryKey: ['partners', debouncedSearchTerm],
+ queryFn: () => orderService.findPartners(debouncedSearchTerm),
+ enabled: isEnabled,
+ staleTime: 1000 * 60 * 5,
+ retry: 1,
+ retryOnMount: false,
+ refetchOnWindowFocus: false,
+ });
+
+ const options =
+ query.data?.map((partner, index) => ({
+ value: partner.id.toString(),
+ label: partner.nome,
+ id: `partner-${partner.id}-${index}`,
+ partner: partner,
+ })) ?? [];
+
+ return {
+ ...query,
+ options,
+ };
+}
diff --git a/src/features/orders/hooks/useProducts.ts b/src/features/orders/hooks/useProducts.ts
new file mode 100644
index 0000000..4c57153
--- /dev/null
+++ b/src/features/orders/hooks/useProducts.ts
@@ -0,0 +1,47 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { useQuery } from '@tanstack/react-query';
+import { orderService } from '../api/order.service';
+
+export interface Product {
+ id: number;
+ description: string;
+}
+
+export function useProducts(searchTerm: string) {
+ const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm);
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setDebouncedSearchTerm(searchTerm);
+ }, 300);
+
+ return () => clearTimeout(timer);
+ }, [searchTerm]);
+
+ const isEnabled = debouncedSearchTerm.length >= 2;
+
+ const query = useQuery({
+ queryKey: ['products', debouncedSearchTerm],
+ queryFn: () => orderService.findProducts(debouncedSearchTerm),
+ enabled: isEnabled,
+ staleTime: 1000 * 60 * 5,
+ retry: 1,
+ retryOnMount: false,
+ refetchOnWindowFocus: false,
+ });
+
+ const options =
+ query.data?.map((product, index) => ({
+ value: product.id.toString(),
+ label: product.description,
+ id: `product-${product.id}-${index}`,
+ product: product,
+ })) ?? [];
+
+ return {
+ ...query,
+ options,
+ };
+}
diff --git a/src/features/orders/schemas/api-responses.schema.ts b/src/features/orders/schemas/api-responses.schema.ts
index c8fe4d4..a6144ee 100644
--- a/src/features/orders/schemas/api-responses.schema.ts
+++ b/src/features/orders/schemas/api-responses.schema.ts
@@ -2,6 +2,8 @@ import { z } from 'zod';
import { storeSchema } from './store.schema';
import { customerSchema } from './customer.schema';
import { sellerSchema } from './seller.schema';
+import { partnerSchema } from './partner.schema';
+import { productSchema } from './product.schema';
import { createApiSchema } from './api-response.schema';
/**
@@ -39,3 +41,15 @@ export const customersResponseSchema = createApiSchema(z.array(customerSchema));
* Formato: { success: boolean, data: Seller[] }
*/
export const sellersResponseSchema = createApiSchema(z.array(sellerSchema));
+
+/**
+ * Schema para validar a resposta da API ao buscar parceiros.
+ * Formato: { success: boolean, data: Partner[] }
+ */
+export const partnersResponseSchema = createApiSchema(z.array(partnerSchema));
+
+/**
+ * Schema para validar a resposta da API ao buscar produtos.
+ * Formato: { success: boolean, data: Product[] }
+ */
+export const productsResponseSchema = createApiSchema(z.array(productSchema));
diff --git a/src/features/orders/schemas/order-filters.schema.ts b/src/features/orders/schemas/order-filters.schema.ts
index f8d4580..f400fc5 100644
--- a/src/features/orders/schemas/order-filters.schema.ts
+++ b/src/features/orders/schemas/order-filters.schema.ts
@@ -16,6 +16,7 @@ export const findOrdersSchema = z.object({
minute: z.coerce.number().optional(),
partnerId: z.string().optional(),
+ partnerName: z.string().optional(),
codusur2: z.string().optional(),
customerName: z.string().optional(),
stockId: z.union([z.string(), z.array(z.string())]).optional(),
@@ -27,6 +28,7 @@ export const findOrdersSchema = z.object({
orderId: z.coerce.number().optional(),
invoiceId: z.coerce.number().optional(),
productId: z.coerce.number().optional(),
+ productName: z.string().optional(),
createDateIni: z.string().optional(),
createDateEnd: z.string().optional(),
@@ -98,19 +100,31 @@ const formatValueToString = (val: any): string => {
*/
export const orderApiParamsSchema = findOrdersSchema
.transform((filters) => {
- // Remove customerName quando customerId existe (evita redundância)
- const { customerName, customerId, ...rest } = filters;
- return customerId ? { customerId, ...rest } : filters;
+ const {
+ productName,
+ partnerName,
+ customerName,
+ customerId,
+ ...rest
+ } = filters;
+
+ const queryParams: any = { ...rest };
+
+ if (customerId) queryParams.customerId = customerId;
+ else if (customerName) queryParams.customerName = customerName;
+
+ if (filters.partnerId) queryParams.partnerId = filters.partnerId;
+ if (filters.productId) queryParams.productId = filters.productId;
+
+ return queryParams;
})
.transform((filters) => {
- // Mapeamento de chaves que precisam ser renomeadas
const keyMap: Record = {
store: 'codfilial',
};
return Object.entries(filters).reduce(
(acc, [key, value]) => {
- // Early return: ignora valores vazios
if (isEmptyValue(value)) return acc;
const apiKey = keyMap[key] ?? key;
diff --git a/src/features/orders/schemas/order.schema.ts b/src/features/orders/schemas/order.schema.ts
index 7f87de1..27a16d7 100644
--- a/src/features/orders/schemas/order.schema.ts
+++ b/src/features/orders/schemas/order.schema.ts
@@ -26,6 +26,16 @@ export { sellerSchema } from './seller.schema';
export type { Seller } from './seller.schema';
export { sellersResponseSchema } from './api-responses.schema';
+// Schema de parceiros
+export { partnerSchema } from './partner.schema';
+export type { Partner } from './partner.schema';
+export { partnersResponseSchema } from './api-responses.schema';
+
+// Schema de produtos
+export { productSchema } from './product.schema';
+export type { Product } from './product.schema';
+export { productsResponseSchema } from './api-responses.schema';
+
// Schema de itens de pedido
export { orderItemSchema, orderItemsResponseSchema } from './order.item.schema';
export type { OrderItem, OrderItemsResponse } from './order.item.schema';
diff --git a/src/features/orders/schemas/partner.schema.ts b/src/features/orders/schemas/partner.schema.ts
new file mode 100644
index 0000000..92d180c
--- /dev/null
+++ b/src/features/orders/schemas/partner.schema.ts
@@ -0,0 +1,15 @@
+import { z } from 'zod';
+
+/**
+ * Schema Zod para validar um parceiro.
+ */
+export const partnerSchema = z.object({
+ id: z.number(),
+ cpf: z.string(),
+ nome: z.string(),
+});
+
+/**
+ * Type para parceiro inferido do schema.
+ */
+export type Partner = z.infer;
diff --git a/src/features/orders/schemas/product.schema.ts b/src/features/orders/schemas/product.schema.ts
new file mode 100644
index 0000000..3f86a11
--- /dev/null
+++ b/src/features/orders/schemas/product.schema.ts
@@ -0,0 +1,14 @@
+import { z } from 'zod';
+
+/**
+ * Schema Zod para validar um produto na busca.
+ */
+export const productSchema = z.object({
+ id: z.number(),
+ description: z.string(),
+});
+
+/**
+ * Type para produto inferido do schema.
+ */
+export type Product = z.infer;
diff --git a/src/features/profile/components/ProfilePage.tsx b/src/features/profile/components/ProfilePage.tsx
index b8061c5..507cc2c 100644
--- a/src/features/profile/components/ProfilePage.tsx
+++ b/src/features/profile/components/ProfilePage.tsx
@@ -7,15 +7,12 @@ import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CircularProgress from '@mui/material/CircularProgress';
import Alert from '@mui/material/Alert';
-import { useAuthStore } from '../../login/store/useAuthStore';
-import { useAuth } from '../../login/hooks/useAuth';
+import Avatar from '@mui/material/Avatar';
+import Chip from '@mui/material/Chip';
+import { useColaboradorAtual } from '../hooks/useColaboradorAtual';
export default function ProfilePage() {
- const { useMe } = useAuth();
- const { data: profile, isLoading, error } = useMe();
- const user = useAuthStore((s) => s.user);
-
- const displayData = profile || user;
+ const { data: colaborador, isLoading, error } = useColaboradorAtual();
if (isLoading) {
return (
@@ -36,12 +33,53 @@ export default function ProfilePage() {
return Erro ao carregar dados do perfil;
}
- if (!displayData) {
+ if (!colaborador) {
return Nenhum dado de perfil disponível;
}
return (
+ {/* Header com Avatar e Nome */}
+
+
+
+
+
+ {colaborador.iniciais}
+
+
+
+ {colaborador.nome}
+
+
+ {colaborador.nomeComFilial}
+
+
+ {colaborador.ehRepresentanteComercial && (
+
+ )}
+ {colaborador.podeAplicarDesconto && (
+
+ )}
+
+
+
+
+
+
+
+ {/* Informações Pessoais */}
@@ -54,7 +92,7 @@ export default function ProfilePage() {
Nome
- {displayData.nome || '-'}
+ {colaborador.nome || '-'}
@@ -62,7 +100,7 @@ export default function ProfilePage() {
Usuário
- {displayData.userName || '-'}
+ {colaborador.userName || '-'}
@@ -70,7 +108,7 @@ export default function ProfilePage() {
Matrícula
- {displayData.matricula || '-'}
+ {colaborador.matricula || '-'}
@@ -78,6 +116,7 @@ export default function ProfilePage() {
+ {/* Informações Profissionais */}
@@ -90,8 +129,7 @@ export default function ProfilePage() {
Filial
- {displayData.nomeFilial || '-'} (
- {displayData.codigoFilial || '-'})
+ {colaborador.nomeFilial || '-'} ({colaborador.codigoFilial || '-'})
@@ -99,16 +137,16 @@ export default function ProfilePage() {
RCA
- {displayData.rca || '-'}
+ {colaborador.ehRepresentanteComercial ? colaborador.codigoRCA : 'Não é vendedor'}
- {displayData.discountPercent !== undefined && (
+ {colaborador.podeAplicarDesconto && (
- Desconto (%)
+ Desconto Disponível
- {displayData.discountPercent}%
+ {colaborador.percentualDesconto}%
)}
@@ -117,9 +155,9 @@ export default function ProfilePage() {
- {(displayData.sectorId !== undefined ||
- displayData.sectorManagerId !== undefined ||
- displayData.supervisorId !== undefined) && (
+ {/* Informações Adicionais */}
+ {(colaborador.codigoSetor !== undefined ||
+ colaborador.codigoSupervisor !== undefined) && (
@@ -127,23 +165,23 @@ export default function ProfilePage() {
Informações Adicionais
- {displayData.sectorId !== undefined && (
+ {colaborador.codigoSetor !== undefined && (
Setor
- {displayData.sectorId}
+ {colaborador.codigoSetor}
)}
- {displayData.supervisorId !== undefined && (
+ {colaborador.codigoSupervisor !== undefined && (
Supervisor
- {displayData.supervisorId}
+ {colaborador.codigoSupervisor}
)}
@@ -155,3 +193,4 @@ export default function ProfilePage() {
);
}
+
diff --git a/src/features/profile/domain/Colaborador.ts b/src/features/profile/domain/Colaborador.ts
new file mode 100644
index 0000000..0de80b3
--- /dev/null
+++ b/src/features/profile/domain/Colaborador.ts
@@ -0,0 +1,71 @@
+import { UserProfileDto } from '../types';
+
+/**
+ * Entidade de Domínio: Colaborador
+ */
+export class Colaborador {
+ private constructor(
+ public readonly matricula: number,
+ public readonly userName: string,
+ public readonly nome: string,
+ public readonly codigoFilial: string,
+ public readonly nomeFilial: string,
+ public readonly codigoRCA: number,
+ private readonly _percentualDesconto: number,
+ public readonly codigoSetor: number,
+ public readonly codigoSupervisor: number
+ ) {}
+
+ // ========== FACTORY METHODS ==========
+
+ static criarAPartirDoDto(dto: UserProfileDto): Colaborador {
+ return new Colaborador(
+ dto.matricula,
+ dto.userName,
+ dto.nome,
+ dto.codigoFilial,
+ dto.nomeFilial,
+ dto.rca,
+ dto.discountPercent,
+ dto.sectorId,
+ dto.supervisorId
+ );
+ }
+
+ // ========== COMPUTED PROPERTIES ==========
+
+ get iniciais(): string {
+ if (!this.nome) return 'U';
+ const partes = this.nome.trim().split(' ').filter(Boolean);
+ if (partes.length === 1) {
+ return partes[0][0].toUpperCase();
+ }
+ return (partes[0][0] + partes[partes.length - 1][0]).toUpperCase();
+ }
+
+ get ehRepresentanteComercial(): boolean {
+ return this.codigoRCA > 0;
+ }
+
+ get podeAplicarDesconto(): boolean {
+ return this._percentualDesconto > 0 && this.ehRepresentanteComercial;
+ }
+
+ get percentualDesconto(): number {
+ return this._percentualDesconto;
+ }
+
+ get nomeComFilial(): string {
+ return `${this.nome} - ${this.nomeFilial}`;
+ }
+
+ // ========== BUSINESS METHODS ==========
+
+ aplicarDescontoAoValor(valorOriginal: number): number {
+ if (!this.podeAplicarDesconto) {
+ return valorOriginal;
+ }
+ return valorOriginal * (1 - this._percentualDesconto / 100);
+ }
+}
+
diff --git a/src/features/profile/hooks/useColaboradorAtual.ts b/src/features/profile/hooks/useColaboradorAtual.ts
new file mode 100644
index 0000000..c5d940a
--- /dev/null
+++ b/src/features/profile/hooks/useColaboradorAtual.ts
@@ -0,0 +1,19 @@
+import { useQuery, UseQueryResult } from '@tanstack/react-query';
+import { profileService } from '../services/profile.service';
+import { Colaborador } from '../domain/Colaborador';
+
+export const COLABORADOR_ATUAL_QUERY_KEY = ['colaborador-atual'] as const;
+
+/**
+ * Hook para obter o colaborador atualmente autenticado.
+ * Encapsula a lógica de busca e cache do perfil do usuário,
+ * desacoplando essa responsabilidade do módulo de login.
+ */
+export function useColaboradorAtual(): UseQueryResult {
+ return useQuery({
+ queryKey: COLABORADOR_ATUAL_QUERY_KEY,
+ queryFn: () => profileService.obterColaboradorAtual(),
+ retry: false,
+ staleTime: Infinity,
+ });
+}
diff --git a/src/features/profile/index.ts b/src/features/profile/index.ts
index d580e7e..59bdeb7 100644
--- a/src/features/profile/index.ts
+++ b/src/features/profile/index.ts
@@ -1,3 +1,5 @@
// Profile feature barrel export
export { default as ProfilePage } from './components/ProfilePage';
-export type { UserProfile } from './types';
+export type { UserProfileDto } from './types';
+export { Colaborador } from './domain/Colaborador';
+export { useColaboradorAtual, COLABORADOR_ATUAL_QUERY_KEY } from './hooks/useColaboradorAtual';
diff --git a/src/features/profile/services/profile.service.ts b/src/features/profile/services/profile.service.ts
index 57f9576..d0924aa 100644
--- a/src/features/profile/services/profile.service.ts
+++ b/src/features/profile/services/profile.service.ts
@@ -1,9 +1,23 @@
import { profileApi } from '../api';
-import { UserProfile } from '../types';
+import { UserProfileDto } from '../types';
+import { Colaborador } from '../domain/Colaborador';
export const profileService = {
- getMe: async (): Promise => {
- const response = await profileApi.get('/auth/me');
+ /**
+ * Obtém o colaborador atualmente autenticado como entidade de domínio.
+ * Segue o padrão de Linguagem Ubíqua do DDD.
+ */
+ obterColaboradorAtual: async (): Promise => {
+ const response = await profileApi.get('/auth/me');
+ return Colaborador.criarAPartirDoDto(response.data);
+ },
+
+ /**
+ * Obtém os dados brutos (DTO) do colaborador autenticado.
+ * Usar apenas quando a conversão para entidade de domínio não for necessária.
+ */
+ obterColaboradorAtualDto: async (): Promise => {
+ const response = await profileApi.get('/auth/me');
return response.data;
},
};
diff --git a/src/features/profile/types.ts b/src/features/profile/types.ts
index 07a5876..27e17ba 100644
--- a/src/features/profile/types.ts
+++ b/src/features/profile/types.ts
@@ -1,7 +1,7 @@
/**
* Tipagem da resposta do User Info (Endpoint 1.3)
*/
-export interface UserProfile {
+export interface UserProfileDto {
matricula: number;
userName: string;
nome: string;