284 lines
15 KiB
JavaScript
284 lines
15 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
|
import { useStatusFrota } from '../hooks/useStatusFrota';
|
|
import { useVehicles } from '../hooks/useVehicles';
|
|
import {
|
|
Activity, Search, Filter, Plus, Pencil,
|
|
MapPin, User, Truck, Calendar
|
|
} from 'lucide-react';
|
|
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { cn } from '@/lib/utils';
|
|
|
|
import { useFleetOptions } from '../hooks/useFleetOptions';
|
|
|
|
export const StatusView = () => {
|
|
const { statusList, loading, fetchStatus, createStatus, updateStatus } = useStatusFrota();
|
|
const { getSelectOptions } = useFleetOptions();
|
|
const statusFrotaOptions = getSelectOptions('status_frota');
|
|
const { vehicles, fetchVehicles } = useVehicles();
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
|
|
// Modal State
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [editingStatus, setEditingStatus] = useState(null);
|
|
|
|
// Form State
|
|
const [formData, setFormData] = useState({
|
|
placa: '',
|
|
placa_reserva: '',
|
|
status_frota: 'Operacional',
|
|
manutencao: '',
|
|
obs: '',
|
|
// Contexto
|
|
id_rota: '',
|
|
motorista: '',
|
|
base: '',
|
|
uf: '',
|
|
tipo_placa: '',
|
|
categoria: '',
|
|
modelo: '',
|
|
proprietario: '',
|
|
atuacao: '',
|
|
vecfleet: ''
|
|
});
|
|
|
|
useEffect(() => {
|
|
fetchStatus();
|
|
fetchVehicles();
|
|
}, [fetchStatus, fetchVehicles]);
|
|
|
|
const handleOpenModal = (item = null) => {
|
|
if (item) {
|
|
setEditingStatus(item);
|
|
setFormData({
|
|
placa: item.placa || '',
|
|
placa_reserva: item.placa_reserva || '',
|
|
status_frota: item.status_frota || 'Operacional',
|
|
manutencao: item.manutencao || '',
|
|
obs: item.obs || '',
|
|
id_rota: item.id_rota || '',
|
|
motorista: item.motorista || '',
|
|
base: item.base || '',
|
|
uf: item.uf || '',
|
|
tipo_placa: item.tipo_placa || '',
|
|
categoria: item.categoria || '',
|
|
modelo: item.modelo || '',
|
|
proprietario: item.proprietario || '',
|
|
atuacao: item.atuacao || '',
|
|
vecfleet: item.vecfleet || ''
|
|
});
|
|
} else {
|
|
setEditingStatus(null);
|
|
setFormData({
|
|
placa: '', placa_reserva: '', status_frota: 'Operacional', manutencao: '', obs: '',
|
|
id_rota: '', motorista: '', base: '', uf: '', tipo_placa: '',
|
|
categoria: '', modelo: '', proprietario: '', atuacao: '', vecfleet: ''
|
|
});
|
|
}
|
|
setIsModalOpen(true);
|
|
};
|
|
|
|
const handleVehicleSelect = (placa) => {
|
|
const vehicle = vehicles.find(v => v.placa === placa);
|
|
if (vehicle) {
|
|
setFormData(prev => ({
|
|
...prev,
|
|
placa: vehicle.placa,
|
|
modelo: vehicle.modelo || prev.modelo,
|
|
categoria: vehicle.categoria || prev.categoria,
|
|
base: vehicle.base || prev.base,
|
|
uf: vehicle.uf || prev.uf,
|
|
proprietario: vehicle.proprietario || prev.proprietario,
|
|
atuacao: vehicle.atuacao || prev.atuacao,
|
|
tipo_placa: vehicle.tipo_de_placa || prev.tipo_placa
|
|
}));
|
|
} else {
|
|
setFormData(prev => ({ ...prev, placa }));
|
|
}
|
|
};
|
|
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
let success;
|
|
if (editingStatus) {
|
|
success = await updateStatus(editingStatus.idstatus_frota, formData);
|
|
} else {
|
|
success = await createStatus(formData);
|
|
}
|
|
if (success) setIsModalOpen(false);
|
|
};
|
|
|
|
const filteredList = statusList.filter(s =>
|
|
(s.placa || '').toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
(s.status_frota || '').toLowerCase().includes(searchTerm.toLowerCase())
|
|
);
|
|
|
|
return (
|
|
<div className="space-y-6 pb-20">
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
|
<div className="space-y-1">
|
|
<h2 className="text-2xl font-bold tracking-tight text-slate-900">Status da Frota</h2>
|
|
<p className="text-sm text-slate-500 font-medium">Gestão de disponibilidade e situações operacionais.</p>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<div className="relative">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" size={14} />
|
|
<Input
|
|
placeholder="Buscar..."
|
|
className="pl-9 h-9 w-[200px] text-xs font-medium bg-white"
|
|
value={searchTerm}
|
|
onChange={e => setSearchTerm(e.target.value)}
|
|
/>
|
|
</div>
|
|
<Button
|
|
className="gap-2 h-9 font-bold bg-indigo-600 hover:bg-indigo-700 text-white"
|
|
onClick={() => handleOpenModal()}
|
|
>
|
|
<Plus size={16} /> Novo Status
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<Card className="border border-slate-200 shadow-sm overflow-hidden bg-white">
|
|
<div className="overflow-x-auto">
|
|
{loading ? <div className="p-8 text-center text-slate-500">Carregando...</div> : (
|
|
<table className="w-full text-left border-collapse min-w-[800px] text-sm">
|
|
<thead className="bg-slate-50 font-bold text-xs uppercase text-slate-500">
|
|
<tr>
|
|
<th className="px-6 py-3 border-b border-slate-100">Veículo</th>
|
|
<th className="px-6 py-3 border-b border-slate-100">Status</th>
|
|
<th className="px-6 py-3 border-b border-slate-100">Operacional</th>
|
|
<th className="px-6 py-3 border-b border-slate-100">Observações</th>
|
|
<th className="px-6 py-3 border-b border-slate-100 text-right">Ação</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-100">
|
|
{filteredList.map((s) => (
|
|
<tr key={s.idstatus_frota || Math.random()} className="hover:bg-slate-50/50">
|
|
<td className="px-6 py-3">
|
|
<div className="flex flex-col">
|
|
<span className="font-bold text-slate-800">{s.placa}</span>
|
|
<span className="text-[10px] text-slate-400">{s.modelo}</span>
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-3">
|
|
<Badge variant="outline" className={cn("text-[10px] font-bold uppercase",
|
|
s.status_frota === 'Operacional' ? 'bg-emerald-50 text-emerald-600 border-emerald-200' :
|
|
s.status_frota === 'Em Manutenção' ? 'bg-rose-50 text-rose-600 border-rose-200' :
|
|
'bg-slate-100 text-slate-600 border-slate-200'
|
|
)}>
|
|
{s.status_frota}
|
|
</Badge>
|
|
</td>
|
|
<td className="px-6 py-3 text-xs text-slate-500">
|
|
<div><MapPin size={10} className="inline mr-1"/>{s.base}</div>
|
|
<div><User size={10} className="inline mr-1"/>{s.motorista || '-'}</div>
|
|
</td>
|
|
<td className="px-6 py-3 text-xs text-slate-500 truncate max-w-[200px]">
|
|
{s.obs || '-'}
|
|
</td>
|
|
<td className="px-6 py-3 text-right">
|
|
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-indigo-600" onClick={() => handleOpenModal(s)}>
|
|
<Pencil size={14} />
|
|
</Button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
|
|
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
|
|
<DialogContent className="sm:max-w-[700px] bg-white text-slate-900">
|
|
<DialogHeader>
|
|
<DialogTitle>{editingStatus ? 'Editar Status' : 'Atualizar Status'}</DialogTitle>
|
|
<DialogDescription>Atualize a situação atual do veículo.</DialogDescription>
|
|
</DialogHeader>
|
|
<form onSubmit={handleSubmit} className="grid grid-cols-2 gap-4 py-4 max-h-[75vh] overflow-y-auto custom-scrollbar px-6">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Placa</Label>
|
|
<div className="flex gap-2">
|
|
<Input value={formData.placa} onChange={e => setFormData({...formData, placa: e.target.value})} className="font-mono" placeholder="ABC-1234"/>
|
|
<Button type="button" size="icon" variant="outline" onClick={() => handleVehicleSelect(formData.placa)}><Search size={14}/></Button>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Status Frota</Label>
|
|
<Select value={formData.status_frota} onValueChange={v => setFormData(p => ({...p, status_frota: v}))}>
|
|
<SelectTrigger><SelectValue/></SelectTrigger>
|
|
<SelectContent>
|
|
{statusFrotaOptions.length > 0 ? (
|
|
statusFrotaOptions.map(opt => (
|
|
<SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
|
|
))
|
|
) : (
|
|
<SelectItem value="Operacional">Operacional</SelectItem>
|
|
)}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="col-span-2 grid grid-cols-3 gap-4 bg-slate-50 p-3 rounded-lg border border-slate-100">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-[10px] font-bold text-slate-400 uppercase">Motorista</Label>
|
|
<Input className="h-8 text-xs bg-white" value={formData.motorista} onChange={e => setFormData({...formData, motorista: e.target.value})} />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-[10px] font-bold text-slate-400 uppercase">Base</Label>
|
|
<Input className="h-8 text-xs bg-white" value={formData.base} onChange={e => setFormData({...formData, base: e.target.value})} />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-[10px] font-bold text-slate-400 uppercase">ID Rota</Label>
|
|
<Input className="h-8 text-xs bg-white" value={formData.id_rota} onChange={e => setFormData({...formData, id_rota: e.target.value})} />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-[10px] font-bold text-slate-400 uppercase">Placa Reserva</Label>
|
|
<Input className="h-8 text-xs bg-white" value={formData.placa_reserva} onChange={e => setFormData({...formData, placa_reserva: e.target.value})} />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-[10px] font-bold text-slate-400 uppercase">Modelo</Label>
|
|
<Input className="h-8 text-xs bg-white" value={formData.modelo} onChange={e => setFormData({...formData, modelo: e.target.value})} />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-[10px] font-bold text-slate-400 uppercase">UF</Label>
|
|
<Input className="h-8 text-xs bg-white" value={formData.uf} onChange={e => setFormData({...formData, uf: e.target.value})} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-span-2 space-y-1.5">
|
|
<Label className="text-xs font-bold text-slate-500 uppercase">Observações</Label>
|
|
<Textarea value={formData.obs} onChange={e => setFormData({...formData, obs: e.target.value})} />
|
|
</div>
|
|
|
|
<DialogFooter className="col-span-2 mt-2">
|
|
<Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>Cancelar</Button>
|
|
<Button type="submit" className="bg-indigo-600 hover:bg-indigo-700 text-white">Salvar</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
};
|