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 {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ 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);
|
||||
|
|
@ -34,7 +34,7 @@ export function useAuth() {
|
|||
useQuery({
|
||||
queryKey: ['auth-me'],
|
||||
queryFn: async () => {
|
||||
const data = await profileService.getMe();
|
||||
const data = await profileService.obterColaboradorAtual();
|
||||
const safeData = mapToSafeProfile(data);
|
||||
setUser(safeData);
|
||||
return safeData;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -7,15 +7,23 @@ 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 Avatar from '@mui/material/Avatar';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import { useAuthStore } from '../../login/store/useAuthStore';
|
||||
import { useAuth } from '../../login/hooks/useAuth';
|
||||
import { Colaborador } from '../domain/Colaborador';
|
||||
|
||||
export default function ProfilePage() {
|
||||
const { useMe } = useAuth();
|
||||
const { data: profile, isLoading, error } = useMe();
|
||||
const { data: colaborador, isLoading, error } = useMe();
|
||||
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) {
|
||||
return (
|
||||
|
|
@ -42,6 +50,47 @@ export default function ProfilePage() {
|
|||
|
||||
return (
|
||||
<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 }}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
|
|
@ -78,6 +127,7 @@ export default function ProfilePage() {
|
|||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* Informações Profissionais */}
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
|
|
@ -90,8 +140,7 @@ export default function ProfilePage() {
|
|||
Filial
|
||||
</Typography>
|
||||
<Typography variant="body1" fontWeight="500">
|
||||
{displayData.nomeFilial || '-'} (
|
||||
{displayData.codigoFilial || '-'})
|
||||
{displayData.nomeFilial || '-'} ({displayData.codigoFilial || '-'})
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
|
|
@ -99,16 +148,16 @@ export default function ProfilePage() {
|
|||
RCA
|
||||
</Typography>
|
||||
<Typography variant="body1" fontWeight="500">
|
||||
{displayData.rca || '-'}
|
||||
{displayData.ehRepresentanteComercial ? displayData.codigoRCA : 'Não é vendedor'}
|
||||
</Typography>
|
||||
</Box>
|
||||
{displayData.discountPercent !== undefined && (
|
||||
{displayData.podeAplicarDesconto && (
|
||||
<Box>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Desconto (%)
|
||||
Desconto Disponível
|
||||
</Typography>
|
||||
<Typography variant="body1" fontWeight="500">
|
||||
{displayData.discountPercent}%
|
||||
{displayData.percentualDesconto}%
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
|
@ -117,9 +166,9 @@ export default function ProfilePage() {
|
|||
</Card>
|
||||
</Grid>
|
||||
|
||||
{(displayData.sectorId !== undefined ||
|
||||
displayData.sectorManagerId !== undefined ||
|
||||
displayData.supervisorId !== undefined) && (
|
||||
{/* Informações Adicionais */}
|
||||
{(displayData.codigoSetor !== undefined ||
|
||||
displayData.codigoSupervisor !== undefined) && (
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
|
|
@ -127,23 +176,23 @@ export default function ProfilePage() {
|
|||
Informações Adicionais
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
{displayData.sectorId !== undefined && (
|
||||
{displayData.codigoSetor !== undefined && (
|
||||
<Grid size={{ xs: 12, sm: 4 }}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Setor
|
||||
</Typography>
|
||||
<Typography variant="body1" fontWeight="500">
|
||||
{displayData.sectorId}
|
||||
{displayData.codigoSetor}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
{displayData.supervisorId !== undefined && (
|
||||
{displayData.codigoSupervisor !== undefined && (
|
||||
<Grid size={{ xs: 12, sm: 4 }}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Supervisor
|
||||
</Typography>
|
||||
<Typography variant="body1" fontWeight="500">
|
||||
{displayData.supervisorId}
|
||||
{displayData.codigoSupervisor}
|
||||
</Typography>
|
||||
</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
|
||||
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 { UserProfile } from '../types';
|
||||
import { UserProfileDto } from '../types';
|
||||
import { Colaborador } from '../domain/Colaborador';
|
||||
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue