photum/frontend/components/EventLogistics.tsx
NANDO9322 943b4f6506 feat(financeiro): implementação do extrato financeiro do profissional e melhorias na agenda
- Backend:
  - Adicionado endpoint para extrato financeiro do profissional (/meus-pagamentos).
  - Atualizada query SQL para incluir nome da empresa e curso nos detalhes da transação.
  - Adicionado retorno de valores (Free, Extra, Descrição) na API.

- Frontend:
  - Nova página "Meus Pagamentos" com modal de detalhes da transação.
  - Removido componente antigo PhotographerFinance.
  - Ajustado filtro de motoristas na Logística para exibir apenas profissionais atribuídos e com carro.
  - Corrigida exibição da função do profissional na Escala (mostra a função atribuída no evento, ex: Cinegrafista).
  - Melhoria no botão de voltar na tela de detalhes do evento.
2026-01-16 16:07:49 -03:00

216 lines
9.7 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { Plus, Trash, User, Truck, Car } from "lucide-react";
import { useAuth } from "../contexts/AuthContext";
import { listCarros, createCarro, deleteCarro, addPassenger, removePassenger, listPassengers, listCarros as fetchCarrosApi } from "../services/apiService";
import { useData } from "../contexts/DataContext";
import { UserRole } from "../types";
interface EventLogisticsProps {
agendaId: string;
assignedProfessionals?: string[];
}
interface Carro {
id: string;
driver_id: string;
driver_name: string;
driver_avatar: string;
arrival_time: string;
notes: string;
passengers: any[]; // We will fetch and attach
}
const EventLogistics: React.FC<EventLogisticsProps> = ({ agendaId, assignedProfessionals }) => {
const { token, user } = useAuth();
const { professionals } = useData();
const [carros, setCarros] = useState<Carro[]>([]);
const [loading, setLoading] = useState(false);
// New Car State
const [driverId, setDriverId] = useState("");
const [arrivalTime, setArrivalTime] = useState("07:00");
const [notes, setNotes] = useState("");
const isEditable = user?.role === UserRole.SUPERADMIN || user?.role === UserRole.BUSINESS_OWNER;
useEffect(() => {
if (agendaId && token) {
loadCarros();
}
}, [agendaId, token]);
const loadCarros = async () => {
setLoading(true);
const res = await fetchCarrosApi(agendaId, token!);
if (res.data) {
// For each car, fetch passengers
const carsWithPassengers = await Promise.all(res.data.map(async (car: any) => {
const passRes = await listPassengers(car.id, token!);
return { ...car, passengers: passRes.data || [] };
}));
setCarros(carsWithPassengers);
}
setLoading(false);
};
const handleAddCarro = async () => {
// Driver ID is optional (could be external), but for now select from professionals
const input = {
agenda_id: agendaId,
motorista_id: driverId || undefined,
horario_chegada: arrivalTime,
observacoes: notes
};
const res = await createCarro(input, token!);
if (res.data) {
loadCarros();
setDriverId("");
setNotes("");
} else {
alert("Erro ao criar carro: " + res.error);
}
};
const handleDeleteCarro = async (id: string) => {
if (confirm("Remover este carro e passageiros?")) {
await deleteCarro(id, token!);
loadCarros();
}
};
const handleAddPassenger = async (carId: string, profId: string) => {
if (!profId) return;
const res = await addPassenger(carId, profId, token!);
if (!res.error) {
loadCarros();
} else {
alert("Erro ao adicionar passageiro: " + res.error);
}
};
const handleRemovePassenger = async (carId: string, profId: string) => {
const res = await removePassenger(carId, profId, token!);
if (!res.error) {
loadCarros();
}
};
return (
<div className="bg-white p-4 rounded-lg shadow space-y-4">
<h3 className="text-lg font-semibold text-gray-800 flex items-center">
<Truck className="w-5 h-5 mr-2 text-orange-500" />
Logística de Transporte
</h3>
{/* Add Car Form - Only for Admins */}
{isEditable && (
<div className="bg-gray-50 p-3 rounded-md flex flex-wrap gap-2 items-end">
<div className="flex-1 min-w-[200px]">
<label className="text-xs text-gray-500">Motorista (Opcional)</label>
<select
className="w-full p-2 rounded border bg-white"
value={driverId}
onChange={e => setDriverId(e.target.value)}
>
<option value="">Selecione ou deixe vazio...</option>
{professionals
.filter(p => {
const hasCar = p.carro_disponivel;
const isAssigned = !assignedProfessionals || assignedProfessionals.includes(p.id);
return hasCar && isAssigned;
})
.map(p => (
<option key={p.id} value={p.id}>{p.nomeEventos || p.nome}</option>
))}
</select>
</div>
<div className="w-24">
<label className="text-xs text-gray-500">Chegada</label>
<input
type="time"
className="w-full p-2 rounded border bg-white"
value={arrivalTime}
onChange={e => setArrivalTime(e.target.value)}
/>
</div>
<button
onClick={handleAddCarro}
className="bg-orange-600 hover:bg-orange-700 text-white p-2 rounded flex items-center"
>
<Plus size={20} /> <span className="ml-1 text-sm">Carro</span>
</button>
</div>
)}
{/* Cars List */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{loading ? <p>Carregando...</p> : carros.map(car => (
<div key={car.id} className="border rounded-lg p-3 bg-gray-50">
<div className="flex justify-between items-start mb-2 border-b pb-2">
<div className="flex items-center gap-2">
<div className="bg-orange-100 p-1.5 rounded-full">
<Car className="w-4 h-4 text-orange-600" />
</div>
<div>
<p className="font-bold text-sm text-gray-800">
{car.driver_name || "Motorista não definido"}
</p>
<p className="text-xs text-gray-500">Chegada: {car.arrival_time}</p>
</div>
</div>
{isEditable && (
<button onClick={() => handleDeleteCarro(car.id)} className="text-gray-400 hover:text-red-500">
<Trash size={14} />
</button>
)}
</div>
{/* Passengers */}
<div className="space-y-1 mb-3">
<p className="text-xs font-semibold text-gray-500 uppercase">Passageiros</p>
{car.passengers.length === 0 && <p className="text-xs italic text-gray-400">Vazio</p>}
{car.passengers.map((p: any) => (
<div key={p.id} className="flex justify-between items-center text-sm bg-white p-1 rounded px-2 border">
<span className="truncate">{p.name || "Desconhecido"}</span>
{isEditable && (
<button onClick={() => handleRemovePassenger(car.id, p.profissional_id)} className="text-red-400 hover:text-red-600">
<Trash size={12} />
</button>
)}
</div>
))}
</div>
{/* Add Passenger - Only for Admins */}
{isEditable && (
<select
className="w-full text-xs p-1 rounded border bg-white"
onChange={(e) => {
if (e.target.value) handleAddPassenger(car.id, e.target.value);
e.target.value = "";
}}
>
<option value="">+ Adicionar Passageiro</option>
{professionals
.filter(p => {
// Filter 1: Must be assigned to event (if restriction list provided)
// If assignedProfessionals prop is missing or empty, maybe we should show all?
// User asked to RESTRICT. So if provided, WE RESTRICT.
const isAssigned = !assignedProfessionals || assignedProfessionals.includes(p.id);
// Filter 2: Must not be already in this car
const isInCar = car.passengers.some((pass: any) => pass.profissional_id === p.id);
return isAssigned && !isInCar;
})
.map(p => (
<option key={p.id} value={p.id}>{p.nomeEventos || p.nome}</option>
))}
</select>
)}
</div>
))}
</div>
</div>
);
};
export default EventLogistics;