fix: correções de email, avatar e filtro de equipe no agendador
Backend:
- Corrigido mapeamento de email na listagem e busca por ID de profissionais. Agora o sistema utiliza o email do usuário vinculado (`usuario_email`) como fallback caso o email do perfil profissional esteja vazio.
Frontend:
- EventScheduler: Implementado filtro estrito para exibir apenas profissionais que foram explicitamente adicionados à equipe do evento ("Gerenciar Equipe"), prevenindo escalações indevidas.
- EventScheduler: Adicionada validação para ocultar cargos administrativos sem função operacional definida.
- ProfessionalDetailsModal: Corrigida a lógica de exibição do avatar para suportar a propriedade `avatar_url` (padrão atual do backend), resolvendo o problema de imagens quebradas ou ícones genéricos.
This commit is contained in:
parent
9ff55b36bd
commit
7010e8e7d9
4 changed files with 16 additions and 6 deletions
|
|
@ -89,6 +89,10 @@ func toResponse(p interface{}) ProfissionalResponse {
|
||||||
AvatarURL: fromPgText(v.AvatarUrl),
|
AvatarURL: fromPgText(v.AvatarUrl),
|
||||||
}
|
}
|
||||||
case generated.ListProfissionaisRow:
|
case generated.ListProfissionaisRow:
|
||||||
|
email := fromPgText(v.Email)
|
||||||
|
if email == nil {
|
||||||
|
email = fromPgText(v.UsuarioEmail)
|
||||||
|
}
|
||||||
return ProfissionalResponse{
|
return ProfissionalResponse{
|
||||||
ID: uuid.UUID(v.ID.Bytes).String(),
|
ID: uuid.UUID(v.ID.Bytes).String(),
|
||||||
UsuarioID: uuid.UUID(v.UsuarioID.Bytes).String(),
|
UsuarioID: uuid.UUID(v.UsuarioID.Bytes).String(),
|
||||||
|
|
@ -116,7 +120,7 @@ func toResponse(p interface{}) ProfissionalResponse {
|
||||||
TabelaFree: fromPgText(v.TabelaFree),
|
TabelaFree: fromPgText(v.TabelaFree),
|
||||||
ExtraPorEquipamento: fromPgBool(v.ExtraPorEquipamento),
|
ExtraPorEquipamento: fromPgBool(v.ExtraPorEquipamento),
|
||||||
Equipamentos: fromPgText(v.Equipamentos),
|
Equipamentos: fromPgText(v.Equipamentos),
|
||||||
Email: fromPgText(v.Email),
|
Email: email,
|
||||||
AvatarURL: fromPgText(v.AvatarUrl),
|
AvatarURL: fromPgText(v.AvatarUrl),
|
||||||
}
|
}
|
||||||
case generated.GetProfissionalByIDRow:
|
case generated.GetProfissionalByIDRow:
|
||||||
|
|
@ -147,6 +151,7 @@ func toResponse(p interface{}) ProfissionalResponse {
|
||||||
TabelaFree: fromPgText(v.TabelaFree),
|
TabelaFree: fromPgText(v.TabelaFree),
|
||||||
ExtraPorEquipamento: fromPgBool(v.ExtraPorEquipamento),
|
ExtraPorEquipamento: fromPgBool(v.ExtraPorEquipamento),
|
||||||
Equipamentos: fromPgText(v.Equipamentos),
|
Equipamentos: fromPgText(v.Equipamentos),
|
||||||
|
Email: fromPgText(v.Email),
|
||||||
AvatarURL: fromPgText(v.AvatarUrl),
|
AvatarURL: fromPgText(v.AvatarUrl),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Plus, Trash, User, Truck, MapPin } from "lucide-react";
|
import { Plus, Trash, User, Truck, MapPin } from "lucide-react";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
import { listEscalas, createEscala, deleteEscala, EscalaInput } from "../services/apiService";
|
import { listEscalas, createEscala, deleteEscala, EscalaInput, getFunctions } from "../services/apiService";
|
||||||
import { useData } from "../contexts/DataContext";
|
import { useData } from "../contexts/DataContext";
|
||||||
import { UserRole } from "../types";
|
import { UserRole } from "../types";
|
||||||
|
|
||||||
|
|
@ -23,6 +23,7 @@ const EventScheduler: React.FC<EventSchedulerProps> = ({ agendaId, dataEvento, a
|
||||||
const { token, user } = useAuth();
|
const { token, user } = useAuth();
|
||||||
const { professionals, events } = useData();
|
const { professionals, events } = useData();
|
||||||
const [escalas, setEscalas] = useState<any[]>([]);
|
const [escalas, setEscalas] = useState<any[]>([]);
|
||||||
|
const [roles, setRoles] = useState<{ id: string; nome: string }[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
// New entry state
|
// New entry state
|
||||||
|
|
@ -78,6 +79,9 @@ const EventScheduler: React.FC<EventSchedulerProps> = ({ agendaId, dataEvento, a
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (agendaId && token) {
|
if (agendaId && token) {
|
||||||
fetchEscalas();
|
fetchEscalas();
|
||||||
|
getFunctions().then(res => {
|
||||||
|
if (res.data) setRoles(res.data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [agendaId, token]);
|
}, [agendaId, token]);
|
||||||
|
|
||||||
|
|
@ -143,10 +147,11 @@ const EventScheduler: React.FC<EventSchedulerProps> = ({ agendaId, dataEvento, a
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. Start with all professionals or just the allowed ones
|
// 1. Start with all professionals or just the allowed ones
|
||||||
let availableProfs = professionals;
|
// FILTER: Only show professionals with a valid role (Function), matching "Equipe" page logic.
|
||||||
|
let availableProfs = professionals.filter(p => roles.some(r => r.id === p.funcao_profissional_id));
|
||||||
const allowedMap = new Map<string, string>(); // ID -> Status
|
const allowedMap = new Map<string, string>(); // ID -> Status
|
||||||
|
|
||||||
if (allowedProfessionals && allowedProfessionals.length > 0) {
|
if (allowedProfessionals) {
|
||||||
// Normalize allowed list
|
// Normalize allowed list
|
||||||
const ids: string[] = [];
|
const ids: string[] = [];
|
||||||
allowedProfessionals.forEach((p: any) => {
|
allowedProfessionals.forEach((p: any) => {
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ export const ProfessionalDetailsModal: React.FC<ProfessionalDetailsModalProps> =
|
||||||
<div
|
<div
|
||||||
className="w-32 h-32 rounded-full border-4 border-white bg-white shadow-lg mb-4 bg-gray-200"
|
className="w-32 h-32 rounded-full border-4 border-white bg-white shadow-lg mb-4 bg-gray-200"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${professional.avatar || `https://ui-avatars.com/api/?name=${encodeURIComponent(professional.name || professional.nome)}&background=random`})`,
|
backgroundImage: `url(${professional.avatar_url || professional.avatar || `https://ui-avatars.com/api/?name=${encodeURIComponent(professional.name || professional.nome)}&background=random`})`,
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
backgroundPosition: 'center'
|
backgroundPosition: 'center'
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -611,7 +611,7 @@ export const ProfessionalForm: React.FC<ProfessionalFormProps> = ({
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Nome do Titular da Conta ou Observação
|
Observações
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={formData.observacao}
|
value={formData.observacao}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue