- Adicionar restrições de exclusão de FOT quando há eventos associados - Implementar tooltips para motivos de recusa de eventos por fotógrafos - Filtrar eventos recusados das listas de fotógrafos - Adicionar sistema de filtros avançados no modal de gerenciar equipe - Implementar campos completos de gestão de equipe (fotógrafos, recepcionistas, cinegrafistas, estúdios, pontos de foto, pontos decorados, pontos LED) - Adicionar colunas de gestão na tabela principal com cálculos automáticos de profissionais faltantes - Implementar controle de visibilidade da seção de gestão apenas para empresas - Adicionar status visual "Profissionais OK" com indicadores de completude - Implementar sistema de cálculo em tempo real de equipe necessária vs confirmada - Adicionar validações condicionais baseadas no tipo de usuário
652 lines
28 KiB
TypeScript
652 lines
28 KiB
TypeScript
import React, { useState, useMemo } from "react";
|
|
import { EventData, EventStatus, UserRole } from "../types";
|
|
import { CheckCircle, ArrowUpDown, ArrowUp, ArrowDown } from "lucide-react";
|
|
import { STATUS_COLORS } from "../constants";
|
|
|
|
interface EventTableProps {
|
|
events: EventData[];
|
|
onEventClick: (event: EventData) => void;
|
|
onApprove?: (e: React.MouseEvent, eventId: string) => void;
|
|
onReject?: (e: React.MouseEvent, eventId: string, reason?: string) => void;
|
|
userRole: UserRole;
|
|
currentProfessionalId?: string;
|
|
onAssignmentResponse?: (e: React.MouseEvent, eventId: string, status: string, reason?: string) => void;
|
|
isManagingTeam?: boolean; // Nova prop para determinar se está na tela de gerenciar equipe
|
|
professionals?: any[]; // Lista de profissionais para cálculos de gestão de equipe
|
|
}
|
|
|
|
type SortField =
|
|
| "fot"
|
|
| "date"
|
|
| "curso"
|
|
| "instituicao"
|
|
| "anoFormatura"
|
|
| "empresa"
|
|
| "type"
|
|
| "status";
|
|
type SortOrder = "asc" | "desc" | null;
|
|
|
|
export const EventTable: React.FC<EventTableProps> = ({
|
|
events,
|
|
onEventClick,
|
|
onApprove,
|
|
onReject,
|
|
userRole,
|
|
currentProfessionalId,
|
|
onAssignmentResponse,
|
|
isManagingTeam = false,
|
|
professionals = [],
|
|
}) => {
|
|
const canApprove = isManagingTeam && (userRole === UserRole.BUSINESS_OWNER || userRole === UserRole.SUPERADMIN);
|
|
const canReject = userRole === UserRole.BUSINESS_OWNER || userRole === UserRole.SUPERADMIN;
|
|
const isPhotographer = userRole === UserRole.PHOTOGRAPHER;
|
|
|
|
// Função para calcular status da equipe
|
|
const calculateTeamStatus = (event: EventData) => {
|
|
const assignments = event.assignments || [];
|
|
|
|
// Contadores de profissionais aceitos por tipo
|
|
const acceptedFotografos = assignments.filter(a => {
|
|
if (a.status !== "ACEITO") return false;
|
|
const professional = professionals.find(p => p.id === a.professionalId);
|
|
return professional && (professional.role || "").toLowerCase().includes("fot");
|
|
}).length;
|
|
|
|
const acceptedRecepcionistas = assignments.filter(a => {
|
|
if (a.status !== "ACEITO") return false;
|
|
const professional = professionals.find(p => p.id === a.professionalId);
|
|
return professional && (professional.role || "").toLowerCase().includes("recep");
|
|
}).length;
|
|
|
|
const acceptedCinegrafistas = assignments.filter(a => {
|
|
if (a.status !== "ACEITO") return false;
|
|
const professional = professionals.find(p => p.id === a.professionalId);
|
|
return professional && (professional.role || "").toLowerCase().includes("cine");
|
|
}).length;
|
|
|
|
// Quantidades necessárias
|
|
const qtdFotografos = event.qtdFotografos || 0;
|
|
const qtdRecepcionistas = event.qtdRecepcionistas || 0;
|
|
const qtdCinegrafistas = event.qtdCinegrafistas || 0;
|
|
|
|
// Calcular faltantes
|
|
const fotoFaltante = Math.max(0, qtdFotografos - acceptedFotografos);
|
|
const recepFaltante = Math.max(0, qtdRecepcionistas - acceptedRecepcionistas);
|
|
const cineFaltante = Math.max(0, qtdCinegrafistas - acceptedCinegrafistas);
|
|
|
|
// Verificar se todos os profissionais estão OK
|
|
const profissionaisOK = fotoFaltante === 0 && recepFaltante === 0 && cineFaltante === 0;
|
|
|
|
return {
|
|
acceptedFotografos,
|
|
acceptedRecepcionistas,
|
|
acceptedCinegrafistas,
|
|
fotoFaltante,
|
|
recepFaltante,
|
|
cineFaltante,
|
|
profissionaisOK
|
|
};
|
|
};
|
|
|
|
const [sortField, setSortField] = useState<SortField | null>(null);
|
|
const [sortOrder, setSortOrder] = useState<SortOrder>(null);
|
|
|
|
const handleSort = (field: SortField) => {
|
|
if (sortField === field) {
|
|
if (sortOrder === "asc") {
|
|
setSortOrder("desc");
|
|
} else if (sortOrder === "desc") {
|
|
setSortOrder(null);
|
|
setSortField(null);
|
|
}
|
|
} else {
|
|
setSortField(field);
|
|
setSortOrder("asc");
|
|
}
|
|
};
|
|
|
|
const sortedEvents = useMemo(() => {
|
|
if (!sortField || !sortOrder) {
|
|
return events;
|
|
}
|
|
|
|
const sorted = [...events].sort((a, b) => {
|
|
let aValue: any;
|
|
let bValue: any;
|
|
|
|
switch (sortField) {
|
|
case "fot":
|
|
aValue = (a as any).fotId || "";
|
|
bValue = (b as any).fotId || "";
|
|
break;
|
|
case "date":
|
|
aValue = new Date(a.date + "T00:00:00").getTime();
|
|
bValue = new Date(b.date + "T00:00:00").getTime();
|
|
break;
|
|
case "curso":
|
|
aValue = ((a as any).curso || "").toLowerCase();
|
|
bValue = ((b as any).curso || "").toLowerCase();
|
|
break;
|
|
case "instituicao":
|
|
aValue = ((a as any).instituicao || "").toLowerCase();
|
|
bValue = ((b as any).instituicao || "").toLowerCase();
|
|
break;
|
|
case "anoFormatura":
|
|
aValue = (a as any).anoFormatura || 0;
|
|
bValue = (b as any).anoFormatura || 0;
|
|
break;
|
|
case "empresa":
|
|
aValue = ((a as any).empresa || "").toLowerCase();
|
|
bValue = ((b as any).empresa || "").toLowerCase();
|
|
break;
|
|
case "type":
|
|
aValue = a.type.toLowerCase();
|
|
bValue = b.type.toLowerCase();
|
|
break;
|
|
case "status":
|
|
aValue = a.status.toLowerCase();
|
|
bValue = b.status.toLowerCase();
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
if (aValue < bValue) return sortOrder === "asc" ? -1 : 1;
|
|
if (aValue > bValue) return sortOrder === "asc" ? 1 : -1;
|
|
return 0;
|
|
});
|
|
|
|
return sorted;
|
|
}, [events, sortField, sortOrder]);
|
|
|
|
const getSortIcon = (field: SortField) => {
|
|
if (sortField !== field) {
|
|
return (
|
|
<ArrowUpDown
|
|
size={14}
|
|
className="opacity-0 group-hover:opacity-50 transition-opacity"
|
|
/>
|
|
);
|
|
}
|
|
if (sortOrder === "asc") {
|
|
return <ArrowUp size={14} className="text-brand-gold" />;
|
|
}
|
|
return <ArrowDown size={14} className="text-brand-gold" />;
|
|
};
|
|
|
|
const formatDate = (date: string) => {
|
|
const eventDate = new Date(date + "T00:00:00");
|
|
return eventDate.toLocaleDateString("pt-BR", {
|
|
day: "2-digit",
|
|
month: "2-digit",
|
|
year: "numeric",
|
|
});
|
|
};
|
|
|
|
const getStatusDisplay = (status: EventStatus) => {
|
|
const statusLabels: Record<EventStatus, string> = {
|
|
[EventStatus.PENDING_APPROVAL]: "Pendente",
|
|
[EventStatus.CONFIRMED]: "Confirmado",
|
|
[EventStatus.PLANNING]: "Planejamento",
|
|
[EventStatus.IN_PROGRESS]: "Em Andamento",
|
|
[EventStatus.DELIVERED]: "Entregue",
|
|
[EventStatus.ARCHIVED]: "Arquivado",
|
|
};
|
|
return statusLabels[status] || status;
|
|
};
|
|
|
|
return (
|
|
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden shadow-sm">
|
|
{/* Mobile Card View */}
|
|
<div className="md:hidden divide-y divide-gray-100">
|
|
{sortedEvents.map((event) => {
|
|
let photographerAssignment = null;
|
|
if (isPhotographer && currentProfessionalId && event.assignments) {
|
|
photographerAssignment = event.assignments.find(a => a.professionalId === currentProfessionalId);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
key={event.id}
|
|
className="p-4 hover:bg-gray-50 active:bg-gray-100 transition-colors cursor-pointer"
|
|
onClick={() => onEventClick(event)}
|
|
>
|
|
<div className="flex justify-between items-start mb-2">
|
|
<div>
|
|
<span className="font-bold text-gray-900 block">FOT {event.fot || "-"}</span>
|
|
<span className="text-xs text-gray-500">{event.type}</span>
|
|
</div>
|
|
<span className="text-sm font-medium text-gray-600 bg-gray-100 px-2 py-1 rounded">
|
|
{formatDate(event.date)}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="mb-3 space-y-1">
|
|
<p className="text-sm text-gray-800 font-medium">{event.curso || "Sem curso defined"}</p>
|
|
<p className="text-sm text-gray-600">{event.instituicao || "-"}</p>
|
|
<div className="flex items-center gap-2 text-xs text-gray-500">
|
|
<span>{event.empresa || "-"}</span>
|
|
<span>•</span>
|
|
<span>{event.anoFormatura || "-"}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center mt-3 pt-3 border-t border-gray-50">
|
|
<span
|
|
className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${STATUS_COLORS[event.status] || "bg-gray-100 text-gray-800"}`}
|
|
>
|
|
{getStatusDisplay(event.status)}
|
|
</span>
|
|
|
|
{(canApprove || canReject || isPhotographer) && (
|
|
<div onClick={(e) => e.stopPropagation()} className="flex items-center gap-2">
|
|
{canApprove && event.status === EventStatus.PENDING_APPROVAL && (
|
|
<button
|
|
onClick={(e) => onApprove?.(e, event.id)}
|
|
className="bg-green-500 text-white p-2 rounded-full hover:bg-green-600 transition-colors shadow-sm"
|
|
title="Aprovar evento"
|
|
>
|
|
<CheckCircle size={16} />
|
|
</button>
|
|
)}
|
|
|
|
{canReject && !canApprove && event.status === EventStatus.PENDING_APPROVAL && (
|
|
<button
|
|
onClick={(e) => {
|
|
const reason = prompt("Motivo da rejeição (opcional):");
|
|
onReject?.(e, event.id, reason || undefined);
|
|
}}
|
|
className="bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors shadow-sm"
|
|
title="Recusar evento"
|
|
>
|
|
✕
|
|
</button>
|
|
)}
|
|
|
|
{isPhotographer && photographerAssignment && (
|
|
<>
|
|
{photographerAssignment.status === "PENDENTE" && (
|
|
<div className="flex gap-2">
|
|
<button
|
|
onClick={(e) => onAssignmentResponse?.(e, event.id, "ACEITO")}
|
|
className="bg-green-100 text-green-700 px-3 py-1 rounded-md text-xs font-bold border border-green-200"
|
|
>
|
|
Aceitar
|
|
</button>
|
|
<button
|
|
onClick={(e) => {
|
|
const reason = prompt("Motivo da rejeição (opcional):");
|
|
onAssignmentResponse?.(e, event.id, "REJEITADO", reason || undefined);
|
|
}}
|
|
className="bg-red-100 text-red-700 px-3 py-1 rounded-md text-xs font-bold border border-red-200"
|
|
>
|
|
Rejeitar
|
|
</button>
|
|
</div>
|
|
)}
|
|
{photographerAssignment.status === "ACEITO" && (
|
|
<span className="text-green-600 text-xs font-bold border border-green-200 bg-green-50 px-2 py-1 rounded">Aceito</span>
|
|
)}
|
|
{photographerAssignment.status === "REJEITADO" && (
|
|
<span className="text-red-600 text-xs font-bold border border-red-200 bg-red-50 px-2 py-1 rounded">Rejeitado</span>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
<div className="hidden md:block overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead className="bg-gray-50 border-b border-gray-200">
|
|
<tr>
|
|
<th
|
|
className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
|
|
onClick={() => handleSort("fot")}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
FOT
|
|
{getSortIcon("fot")}
|
|
</div>
|
|
</th>
|
|
<th
|
|
className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
|
|
onClick={() => handleSort("date")}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
Data
|
|
{getSortIcon("date")}
|
|
</div>
|
|
</th>
|
|
<th
|
|
className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
|
|
onClick={() => handleSort("curso")}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
Curso
|
|
{getSortIcon("curso")}
|
|
</div>
|
|
</th>
|
|
<th
|
|
className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
|
|
onClick={() => handleSort("instituicao")}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
Instituição
|
|
{getSortIcon("instituicao")}
|
|
</div>
|
|
</th>
|
|
<th
|
|
className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
|
|
onClick={() => handleSort("anoFormatura")}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
Ano Formatura
|
|
{getSortIcon("anoFormatura")}
|
|
</div>
|
|
</th>
|
|
<th
|
|
className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
|
|
onClick={() => handleSort("empresa")}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
Empresa
|
|
{getSortIcon("empresa")}
|
|
</div>
|
|
</th>
|
|
<th
|
|
className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
|
|
onClick={() => handleSort("type")}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
Tipo Evento
|
|
{getSortIcon("type")}
|
|
</div>
|
|
</th>
|
|
<th
|
|
className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
|
|
onClick={() => handleSort("status")}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
Status
|
|
{getSortIcon("status")}
|
|
</div>
|
|
</th>
|
|
{/* Novas colunas de gestão de equipe */}
|
|
{(userRole === UserRole.BUSINESS_OWNER || userRole === UserRole.SUPERADMIN) && (
|
|
<>
|
|
<th className="px-3 py-3 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
QTD Form.
|
|
</th>
|
|
<th className="px-3 py-3 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
Fotóg.
|
|
</th>
|
|
<th className="px-3 py-3 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
Recep.
|
|
</th>
|
|
<th className="px-3 py-3 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
Cine.
|
|
</th>
|
|
<th className="px-3 py-3 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
Estúd.
|
|
</th>
|
|
<th className="px-3 py-3 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
Pts. Foto
|
|
</th>
|
|
<th className="px-3 py-3 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
Pts. Dec.
|
|
</th>
|
|
<th className="px-3 py-3 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
Pts. LED
|
|
</th>
|
|
<th className="px-3 py-3 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
Prof. OK?
|
|
</th>
|
|
<th className="px-3 py-3 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
Fot. Falt.
|
|
</th>
|
|
<th className="px-3 py-3 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
Rec. Falt.
|
|
</th>
|
|
<th className="px-3 py-3 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
Cin. Falt.
|
|
</th>
|
|
</>
|
|
)}
|
|
{(canApprove || isPhotographer) && (
|
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider w-32">
|
|
Ações
|
|
</th>
|
|
)}
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-100">
|
|
{sortedEvents.map((event) => {
|
|
// Logic to find photographer assignment status
|
|
let photographerAssignment = null;
|
|
if (isPhotographer && currentProfessionalId && event.assignments) {
|
|
photographerAssignment = event.assignments.find(a => a.professionalId === currentProfessionalId);
|
|
}
|
|
|
|
return (
|
|
<tr
|
|
key={event.id}
|
|
onClick={() => onEventClick(event)}
|
|
className="hover:bg-gray-50 cursor-pointer transition-colors"
|
|
>
|
|
<td className="px-4 py-3">
|
|
<span className="text-sm font-medium text-gray-900">
|
|
{event.fot || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-3">
|
|
<span className="text-sm text-gray-600">
|
|
{formatDate(event.date)}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-3">
|
|
<span className="text-sm text-gray-600">
|
|
{event.curso || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-3">
|
|
<span className="text-sm text-gray-600">
|
|
{event.instituicao || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-3">
|
|
<span className="text-sm text-gray-600">
|
|
{event.anoFormatura || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-3">
|
|
<span className="text-sm text-gray-600">
|
|
{event.empresa || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-3">
|
|
<span className="text-sm text-gray-600">{event.type}</span>
|
|
</td>
|
|
<td className="px-4 py-3">
|
|
<span
|
|
className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${STATUS_COLORS[event.status] || "bg-gray-100 text-gray-800"
|
|
}`}
|
|
>
|
|
{getStatusDisplay(event.status)}
|
|
</span>
|
|
</td>
|
|
{/* Novas células de gestão de equipe */}
|
|
{(userRole === UserRole.BUSINESS_OWNER || userRole === UserRole.SUPERADMIN) && (() => {
|
|
const teamStatus = calculateTeamStatus(event);
|
|
return (
|
|
<>
|
|
<td className="px-3 py-3 text-center">
|
|
<span className="text-xs text-gray-900">
|
|
{event.qtdFormandos || event.attendees || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-3 py-3 text-center">
|
|
<span className="text-xs text-gray-900">
|
|
{event.qtdFotografos || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-3 py-3 text-center">
|
|
<span className="text-xs text-gray-900">
|
|
{event.qtdRecepcionistas || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-3 py-3 text-center">
|
|
<span className="text-xs text-gray-900">
|
|
{event.qtdCinegrafistas || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-3 py-3 text-center">
|
|
<span className="text-xs text-gray-900">
|
|
{event.qtdEstudios || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-3 py-3 text-center">
|
|
<span className="text-xs text-gray-900">
|
|
{event.qtdPontosFoto || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-3 py-3 text-center">
|
|
<span className="text-xs text-gray-900">
|
|
{event.qtdPontosDecorados || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-3 py-3 text-center">
|
|
<span className="text-xs text-gray-900">
|
|
{event.qtdPontosLed || "-"}
|
|
</span>
|
|
</td>
|
|
<td className="px-3 py-3 text-center">
|
|
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
|
|
teamStatus.profissionaisOK
|
|
? "bg-green-100 text-green-800"
|
|
: "bg-red-100 text-red-800"
|
|
}`}>
|
|
{teamStatus.profissionaisOK ? "✓" : "✗"}
|
|
</span>
|
|
</td>
|
|
<td className="px-3 py-3 text-center">
|
|
<span className={`text-xs font-medium ${
|
|
teamStatus.fotoFaltante > 0 ? "text-red-600" : "text-green-600"
|
|
}`}>
|
|
{teamStatus.fotoFaltante}
|
|
</span>
|
|
</td>
|
|
<td className="px-3 py-3 text-center">
|
|
<span className={`text-xs font-medium ${
|
|
teamStatus.recepFaltante > 0 ? "text-red-600" : "text-green-600"
|
|
}`}>
|
|
{teamStatus.recepFaltante}
|
|
</span>
|
|
</td>
|
|
<td className="px-3 py-3 text-center">
|
|
<span className={`text-xs font-medium ${
|
|
teamStatus.cineFaltante > 0 ? "text-red-600" : "text-green-600"
|
|
}`}>
|
|
{teamStatus.cineFaltante}
|
|
</span>
|
|
</td>
|
|
</>
|
|
);
|
|
})()}
|
|
{(canApprove || canReject || isPhotographer) && (
|
|
<td
|
|
className="px-4 py-3"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
{canApprove && event.status === EventStatus.PENDING_APPROVAL && (
|
|
<button
|
|
onClick={(e) => onApprove?.(e, event.id)}
|
|
className="bg-green-500 text-white px-2 py-1 rounded text-xs font-semibold hover:bg-green-600 transition-colors flex items-center gap-1 whitespace-nowrap"
|
|
title="Aprovar evento"
|
|
>
|
|
<CheckCircle size={12} />
|
|
Aprovar
|
|
</button>
|
|
)}
|
|
|
|
{canReject && !canApprove && event.status === EventStatus.PENDING_APPROVAL && (
|
|
<button
|
|
onClick={(e) => {
|
|
const reason = prompt("Motivo da rejeição (opcional):");
|
|
onReject?.(e, event.id, reason || undefined);
|
|
}}
|
|
className="bg-red-500 text-white px-2 py-1 rounded text-xs font-semibold hover:bg-red-600 transition-colors flex items-center gap-1 whitespace-nowrap"
|
|
title="Recusar evento"
|
|
>
|
|
✕ Recusar
|
|
</button>
|
|
)}
|
|
|
|
{isPhotographer && photographerAssignment && (
|
|
<>
|
|
{photographerAssignment.status === "PENDENTE" && (
|
|
<>
|
|
<button
|
|
onClick={(e) => onAssignmentResponse?.(e, event.id, "ACEITO")}
|
|
className="bg-green-500 text-white px-2 py-1 rounded text-xs font-semibold hover:bg-green-600 transition-colors"
|
|
title="Aceitar"
|
|
>
|
|
Aceitar
|
|
</button>
|
|
<button
|
|
onClick={(e) => {
|
|
const reason = prompt("Motivo da rejeição (opcional):");
|
|
onAssignmentResponse?.(e, event.id, "REJEITADO", reason || undefined);
|
|
}}
|
|
className="bg-red-500 text-white px-2 py-1 rounded text-xs font-semibold hover:bg-red-600 transition-colors"
|
|
title="Rejeitar"
|
|
>
|
|
Rejeitar
|
|
</button>
|
|
</>
|
|
)}
|
|
{photographerAssignment.status === "ACEITO" && (
|
|
<span className="text-green-600 text-xs font-bold border border-green-200 bg-green-50 px-2 py-1 rounded">Aceito</span>
|
|
)}
|
|
{photographerAssignment.status === "REJEITADO" && (
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-red-600 text-xs font-bold border border-red-200 bg-red-50 px-2 py-1 rounded">Rejeitado</span>
|
|
{photographerAssignment.reason && (
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
alert(`Motivo: ${photographerAssignment.reason}`);
|
|
}}
|
|
className="bg-gray-100 text-gray-600 px-2 py-1 rounded text-xs hover:bg-gray-200 transition-colors"
|
|
title="Ver motivo"
|
|
>
|
|
Motivo
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</td>
|
|
)}
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{sortedEvents.length === 0 && (
|
|
<div className="text-center py-12 text-gray-500">
|
|
<p>Nenhum evento encontrado.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|