refactor(profile): aplicar DDD com Intent-Revealing Names e Linguagem Ubíqua
- Renomear métodos e propriedades para português (Linguagem Ubíqua) - Aplicar Intent-Revealing Names na entidade Colaborador - Corrigir mapToSafeProfile para suportar nomes da entidade DDD - Remover comentários redundantes (nomes auto-explicativos) Arquivos modificados: - Colaborador.ts: fromDto → criarAPartirDoDto, ehRCA → ehRepresentanteComercial - profile.service.ts: getMe → obterColaboradorAtual - ProfilePage.tsx: atualizar referências aos novos nomes - mappers.ts: suportar ambos formatos (entidade e DTO)
This commit is contained in:
parent
21b9ff5afe
commit
e19798e582
|
|
@ -20,7 +20,7 @@ export function AuthInitializer({ children }: { children: React.ReactNode }) {
|
||||||
try {
|
try {
|
||||||
await loginService.refreshToken();
|
await loginService.refreshToken();
|
||||||
|
|
||||||
const profile = await profileService.getMe();
|
const profile = await profileService.obterColaboradorAtual();
|
||||||
setUser(mapToSafeProfile(profile));
|
setUser(mapToSafeProfile(profile));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Sessão expirada ou inválida', error);
|
console.warn('Sessão expirada ou inválida', error);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export function useAuth() {
|
||||||
mutationFn: loginService.login,
|
mutationFn: loginService.login,
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
try {
|
try {
|
||||||
const profile = await profileService.getMe();
|
const profile = await profileService.obterColaboradorAtual();
|
||||||
const safeProfile = mapToSafeProfile(profile);
|
const safeProfile = mapToSafeProfile(profile);
|
||||||
|
|
||||||
setUser(safeProfile);
|
setUser(safeProfile);
|
||||||
|
|
@ -34,7 +34,7 @@ export function useAuth() {
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: ['auth-me'],
|
queryKey: ['auth-me'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const data = await profileService.getMe();
|
const data = await profileService.obterColaboradorAtual();
|
||||||
const safeData = mapToSafeProfile(data);
|
const safeData = mapToSafeProfile(data);
|
||||||
setUser(safeData);
|
setUser(safeData);
|
||||||
return safeData;
|
return safeData;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type { UserProfile } from '../../profile/types';
|
import type { UserProfileDto } from '../../profile/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
loginSchema,
|
loginSchema,
|
||||||
|
|
@ -38,9 +38,9 @@ export interface AuthLoginProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthState {
|
export interface AuthState {
|
||||||
user: UserProfile | null;
|
user: UserProfileDto | null;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
setUser: (user: UserProfile | null) => void;
|
setUser: (user: UserProfileDto | null) => void;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
hydrate: () => void;
|
hydrate: () => void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
return {
|
||||||
matricula: data.matricula,
|
matricula: data.matricula,
|
||||||
userName: data.userName,
|
userName: data.userName,
|
||||||
nome: data.nome,
|
nome: data.nome,
|
||||||
codigoFilial: data.codigoFilial,
|
codigoFilial: data.codigoFilial,
|
||||||
nomeFilial: data.nomeFilial,
|
nomeFilial: data.nomeFilial,
|
||||||
rca: data.rca,
|
rca: data.codigoRCA ?? data.rca,
|
||||||
discountPercent: data.discountPercent,
|
discountPercent: data.percentualDesconto ?? data._percentualDesconto ?? data.discountPercent,
|
||||||
sectorId: data.sectorId,
|
sectorId: data.codigoSetor ?? data.sectorId,
|
||||||
sectorManagerId: data.sectorManagerId,
|
sectorManagerId: data.sectorManagerId ?? 0,
|
||||||
supervisorId: data.supervisorId,
|
supervisorId: data.codigoSupervisor ?? data.supervisorId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,23 @@ import Card from '@mui/material/Card';
|
||||||
import CardContent from '@mui/material/CardContent';
|
import CardContent from '@mui/material/CardContent';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import Alert from '@mui/material/Alert';
|
import Alert from '@mui/material/Alert';
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import Chip from '@mui/material/Chip';
|
||||||
import { useAuthStore } from '../../login/store/useAuthStore';
|
import { useAuthStore } from '../../login/store/useAuthStore';
|
||||||
import { useAuth } from '../../login/hooks/useAuth';
|
import { useAuth } from '../../login/hooks/useAuth';
|
||||||
|
import { Colaborador } from '../domain/Colaborador';
|
||||||
|
|
||||||
export default function ProfilePage() {
|
export default function ProfilePage() {
|
||||||
const { useMe } = useAuth();
|
const { useMe } = useAuth();
|
||||||
const { data: profile, isLoading, error } = useMe();
|
const { data: colaborador, isLoading, error } = useMe();
|
||||||
const user = useAuthStore((s) => s.user);
|
const user = useAuthStore((s) => s.user);
|
||||||
|
|
||||||
const displayData = profile || user;
|
// Converte o DTO para a entidade de domínio Colaborador
|
||||||
|
const displayData: Colaborador | null = colaborador
|
||||||
|
? Colaborador.criarAPartirDoDto(colaborador)
|
||||||
|
: user
|
||||||
|
? Colaborador.criarAPartirDoDto(user)
|
||||||
|
: null;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -42,6 +50,47 @@ export default function ProfilePage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
|
{/* Header com Avatar e Nome */}
|
||||||
|
<Grid size={{ xs: 12 }}>
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3 }}>
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
bgcolor: 'primary.main',
|
||||||
|
fontSize: '1.5rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{displayData.iniciais}
|
||||||
|
</Avatar>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5" fontWeight="600">
|
||||||
|
{displayData.nome}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
{displayData.nomeComFilial}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ mt: 1, display: 'flex', gap: 1 }}>
|
||||||
|
{displayData.ehRepresentanteComercial && (
|
||||||
|
<Chip label="RCA" color="primary" size="small" />
|
||||||
|
)}
|
||||||
|
{displayData.podeAplicarDesconto && (
|
||||||
|
<Chip
|
||||||
|
label={`${displayData.percentualDesconto}% desconto`}
|
||||||
|
color="success"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Informações Pessoais */}
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
@ -78,6 +127,7 @@ export default function ProfilePage() {
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
{/* Informações Profissionais */}
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
@ -90,8 +140,7 @@ export default function ProfilePage() {
|
||||||
Filial
|
Filial
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" fontWeight="500">
|
<Typography variant="body1" fontWeight="500">
|
||||||
{displayData.nomeFilial || '-'} (
|
{displayData.nomeFilial || '-'} ({displayData.codigoFilial || '-'})
|
||||||
{displayData.codigoFilial || '-'})
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
|
|
@ -99,16 +148,16 @@ export default function ProfilePage() {
|
||||||
RCA
|
RCA
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" fontWeight="500">
|
<Typography variant="body1" fontWeight="500">
|
||||||
{displayData.rca || '-'}
|
{displayData.ehRepresentanteComercial ? displayData.codigoRCA : 'Não é vendedor'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{displayData.discountPercent !== undefined && (
|
{displayData.podeAplicarDesconto && (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="body2" color="textSecondary">
|
<Typography variant="body2" color="textSecondary">
|
||||||
Desconto (%)
|
Desconto Disponível
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" fontWeight="500">
|
<Typography variant="body1" fontWeight="500">
|
||||||
{displayData.discountPercent}%
|
{displayData.percentualDesconto}%
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
@ -117,9 +166,9 @@ export default function ProfilePage() {
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{(displayData.sectorId !== undefined ||
|
{/* Informações Adicionais */}
|
||||||
displayData.sectorManagerId !== undefined ||
|
{(displayData.codigoSetor !== undefined ||
|
||||||
displayData.supervisorId !== undefined) && (
|
displayData.codigoSupervisor !== undefined) && (
|
||||||
<Grid size={{ xs: 12 }}>
|
<Grid size={{ xs: 12 }}>
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
@ -127,23 +176,23 @@ export default function ProfilePage() {
|
||||||
Informações Adicionais
|
Informações Adicionais
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{displayData.sectorId !== undefined && (
|
{displayData.codigoSetor !== undefined && (
|
||||||
<Grid size={{ xs: 12, sm: 4 }}>
|
<Grid size={{ xs: 12, sm: 4 }}>
|
||||||
<Typography variant="body2" color="textSecondary">
|
<Typography variant="body2" color="textSecondary">
|
||||||
Setor
|
Setor
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" fontWeight="500">
|
<Typography variant="body1" fontWeight="500">
|
||||||
{displayData.sectorId}
|
{displayData.codigoSetor}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
{displayData.supervisorId !== undefined && (
|
{displayData.codigoSupervisor !== undefined && (
|
||||||
<Grid size={{ xs: 12, sm: 4 }}>
|
<Grid size={{ xs: 12, sm: 4 }}>
|
||||||
<Typography variant="body2" color="textSecondary">
|
<Typography variant="body2" color="textSecondary">
|
||||||
Supervisor
|
Supervisor
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" fontWeight="500">
|
<Typography variant="body1" fontWeight="500">
|
||||||
{displayData.supervisorId}
|
{displayData.codigoSupervisor}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
// Profile feature barrel export
|
// Profile feature barrel export
|
||||||
export { default as ProfilePage } from './components/ProfilePage';
|
export { default as ProfilePage } from './components/ProfilePage';
|
||||||
export type { UserProfile } from './types';
|
export type { UserProfileDto } from './types';
|
||||||
|
export { Colaborador } from './domain/Colaborador';
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,23 @@
|
||||||
import { profileApi } from '../api';
|
import { profileApi } from '../api';
|
||||||
import { UserProfile } from '../types';
|
import { UserProfileDto } from '../types';
|
||||||
|
import { Colaborador } from '../domain/Colaborador';
|
||||||
|
|
||||||
export const profileService = {
|
export const profileService = {
|
||||||
getMe: async (): Promise<UserProfile> => {
|
/**
|
||||||
const response = await profileApi.get<UserProfile>('/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<Colaborador> => {
|
||||||
|
const response = await profileApi.get<UserProfileDto>('/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<UserProfileDto> => {
|
||||||
|
const response = await profileApi.get<UserProfileDto>('/auth/me');
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Tipagem da resposta do User Info (Endpoint 1.3)
|
* Tipagem da resposta do User Info (Endpoint 1.3)
|
||||||
*/
|
*/
|
||||||
export interface UserProfile {
|
export interface UserProfileDto {
|
||||||
matricula: number;
|
matricula: number;
|
||||||
userName: string;
|
userName: string;
|
||||||
nome: string;
|
nome: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue