549 lines
30 KiB
JavaScript
549 lines
30 KiB
JavaScript
import React, { useEffect, useState, useMemo } from 'react';
|
|
import { useVehicles } from '../hooks/useVehicles';
|
|
import { useDrivers } from '../hooks/useDrivers';
|
|
import { useFleetLists } from '../hooks/useFleetLists';
|
|
import AutocompleteInput from '../components/AutocompleteInput';
|
|
import ExcelTable from '../components/ExcelTable';
|
|
import {
|
|
Plus, Filter, Search, Edit2, Trash2,
|
|
MapPin, Truck, Calendar, DollarSign, Wrench, CheckCircle
|
|
} from 'lucide-react';
|
|
import {
|
|
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription
|
|
} from "@/components/ui/dialog";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { toast } from 'sonner';
|
|
import { useFeedback } from '../components/FeedbackNotification';
|
|
|
|
// --- Components Styled for Dark Theme ---
|
|
|
|
const DarkInput = ({ label, ...props }) => (
|
|
<div className="space-y-1.5">
|
|
{label && <label className="text-[10px] uppercase font-bold text-slate-500 dark:text-slate-400 tracking-wider ml-1">{label}</label>}
|
|
<input
|
|
className="w-full bg-slate-50 dark:bg-[#141414] border border-slate-200 dark:border-[#333] rounded-xl px-3 py-2 text-sm text-slate-700 dark:text-slate-200 focus:outline-none focus:border-orange-500 focus:ring-1 focus:ring-orange-500 transition-all placeholder:text-slate-400 dark:placeholder:text-slate-700"
|
|
{...props}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
const DarkSelect = ({ label, options, value, onChange }) => (
|
|
<div className="space-y-1.5">
|
|
{label && <label className="text-[10px] uppercase font-bold text-slate-500 dark:text-slate-400 tracking-wider ml-1">{label}</label>}
|
|
<select
|
|
value={value}
|
|
onChange={e => onChange(e.target.value)}
|
|
className="w-full bg-slate-50 dark:bg-[#141414] border border-slate-200 dark:border-[#333] rounded-xl px-3 py-2 text-sm text-slate-700 dark:text-slate-200 focus:outline-none focus:border-orange-500 focus:ring-1 focus:ring-orange-500 transition-all cursor-pointer"
|
|
>
|
|
<option value="">Selecione...</option>
|
|
{options.map(opt => (
|
|
<option key={opt} value={opt}>{opt}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
);
|
|
|
|
const DarkButton = ({ children, variant = 'primary', className = '', ...props }) => {
|
|
const baseClass = "px-4 py-2 rounded-xl font-bold text-sm transition-all shadow-lg active:scale-95 flex items-center justify-center gap-2";
|
|
const variants = {
|
|
primary: "bg-orange-600 hover:bg-orange-500 text-white shadow-orange-500/10",
|
|
secondary: "bg-slate-100 dark:bg-[#2a2a2a] hover:bg-slate-200 dark:hover:bg-[#333] text-slate-700 dark:text-slate-200 border border-slate-200 dark:border-[#333]",
|
|
ghost: "bg-transparent hover:bg-slate-100 dark:hover:bg-[#2a2a2a] text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-white"
|
|
};
|
|
|
|
return (
|
|
<button className={`${baseClass} ${variants[variant]} ${className}`} {...props}>
|
|
{children}
|
|
</button>
|
|
);
|
|
};
|
|
|
|
// --- Inline Status Editor ---
|
|
const StatusCell = ({ currentStatus, idVehicle, options, onUpdate }) => {
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [tempValue, setTempValue] = useState(currentStatus);
|
|
|
|
useEffect(() => {
|
|
setTempValue(currentStatus);
|
|
}, [currentStatus]);
|
|
|
|
const handleBlur = () => {
|
|
setIsEditing(false);
|
|
if (tempValue !== currentStatus) {
|
|
onUpdate(idVehicle, tempValue);
|
|
}
|
|
};
|
|
|
|
const handleChange = (e) => {
|
|
setTempValue(e.target.value);
|
|
// Optional: Auto-save immediately on change instead of blur?
|
|
// User asked: "Clicar fora e enviaria", so blur is better.
|
|
};
|
|
|
|
if (isEditing) {
|
|
return (
|
|
<select
|
|
autoFocus
|
|
value={tempValue}
|
|
onChange={handleChange}
|
|
onBlur={handleBlur}
|
|
className="w-full bg-slate-50 dark:bg-[#141414] border border-orange-500 rounded px-1 py-0.5 text-[10px] uppercase font-bold text-slate-700 dark:text-slate-200 focus:outline-none"
|
|
onClick={(e) => e.stopPropagation()} // Prevent row selection if applied
|
|
>
|
|
<option value="">Selecione...</option>
|
|
{options.map(opt => (
|
|
<option key={opt} value={opt}>{opt}</option>
|
|
))}
|
|
</select>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
onClick={(e) => { e.stopPropagation(); setIsEditing(true); }}
|
|
className={`inline-flex items-center px-2 py-0.5 rounded-full text-[9px] font-bold uppercase tracking-wider border cursor-pointer hover:opacity-80 transition-opacity ${
|
|
currentStatus === 'ATIVO' ? 'bg-orange-500/10 text-orange-500 border-orange-500/20' :
|
|
'bg-slate-500/10 text-slate-500 border-slate-500/20'
|
|
}`}
|
|
>
|
|
{currentStatus || 'N/A'}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
import { prafrotStatisticsService } from '../services/prafrotStatisticsService';
|
|
|
|
export default function VehiclesView() {
|
|
const { vehicles, loading, fetchVehicles, createVehicle, updateVehicle, updateVehicleStatus, deleteVehicle } = useVehicles();
|
|
const { fetchListsConfig, statusFrotaOptions } = useFleetLists();
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [editingVehicle, setEditingVehicle] = useState(null);
|
|
const [statusStats, setStatusStats] = useState([]);
|
|
const [selectedStatusRecords, setSelectedStatusRecords] = useState(null);
|
|
const [isRecordsModalOpen, setIsRecordsModalOpen] = useState(false);
|
|
|
|
// Initial Form State
|
|
const initialFormState = {
|
|
ano_fabricacao: '', ano_modelo: '', atuacao: '', base: '', categoria: 'Passeio',
|
|
chassi: '', cnpj: '', combustivel: 'Gasolina', contrato: '', coordenador: '',
|
|
cor: '', data_limite: '', dispatcher: '', fabricante: '', fiscal_operacao: '',
|
|
geotab: 'NÃO', gestor: '', golfleet: 'NÃO', idveiculo_frota: '', modelo: '',
|
|
placa: '', pooltrack: 'NÃO', primeira_locacao: '', proprietario: '',
|
|
renavam: '', sascar: 'NÃO', t4s: 'NÃO', tipo_de_placa: '', tipo_frota: '',
|
|
uf: '', valor_aluguel: '', valor_fipe: '',
|
|
situacao: 'ATIVO', km_atual: '', observacoes: '', empresa: '', financiamento: '',
|
|
locadora: '', motorista_atual: ''
|
|
};
|
|
const [formData, setFormData] = useState(initialFormState);
|
|
|
|
const { drivers, fetchDrivers } = useDrivers();
|
|
|
|
useEffect(() => {
|
|
fetchVehicles();
|
|
fetchDrivers();
|
|
fetchListsConfig();
|
|
|
|
// Fetch Status Stats
|
|
prafrotStatisticsService.getPlacasPorStatus().then(data => {
|
|
if (Array.isArray(data)) setStatusStats(data);
|
|
});
|
|
}, []);
|
|
|
|
const getStatusData = (status) => {
|
|
return statusStats.find(item => {
|
|
const itemStatus = (item.status || item.status_frota || '');
|
|
return itemStatus.trim().toLowerCase() === status?.trim().toLowerCase();
|
|
});
|
|
};
|
|
|
|
const handleStatusClick = (status) => {
|
|
const data = getStatusData(status);
|
|
if (data && data.registros) {
|
|
setSelectedStatusRecords({
|
|
title: status,
|
|
records: JSON.parse(data.registros).filter(r => r !== null)
|
|
});
|
|
setIsRecordsModalOpen(true);
|
|
}
|
|
};
|
|
|
|
const handleOpenModal = (vehicle = null) => {
|
|
if (vehicle) {
|
|
setEditingVehicle(vehicle);
|
|
setFormData({
|
|
...initialFormState,
|
|
...vehicle, // Fill with vehicle data
|
|
tipo_placa: vehicle.tipo_de_placa || vehicle.tipo_placa || '', // Handle varied naming
|
|
data_aquisicao: vehicle.data_aquisicao?.split('T')[0] || '',
|
|
data_venda: vehicle.data_venda?.split('T')[0] || '',
|
|
data_limite: vehicle.data_limite?.split('T')[0] || '',
|
|
primeira_locacao: vehicle.primeira_locacao?.split('T')[0] || '',
|
|
situacao: vehicle.situacao || vehicle.status_frota || 'ATIVO',
|
|
});
|
|
} else {
|
|
setEditingVehicle(null);
|
|
setFormData(initialFormState);
|
|
}
|
|
setIsModalOpen(true);
|
|
};
|
|
|
|
const { success: notifySuccess, error: notifyError, notifyFields } = useFeedback();
|
|
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
|
|
// Validação de campos obrigatórios
|
|
const requiredFields = [];
|
|
if (!formData.placa) requiredFields.push('Placa');
|
|
if (!formData.modelo) requiredFields.push('Modelo');
|
|
if (!formData.fabricante) requiredFields.push('Fabricante');
|
|
|
|
if (requiredFields.length > 0) {
|
|
notifyFields(requiredFields);
|
|
return;
|
|
}
|
|
|
|
const payload = { ...formData, tipo_de_placa: formData.tipo_placa };
|
|
|
|
delete payload.motorista_atual;
|
|
delete payload.empresa;
|
|
delete payload.locadora;
|
|
delete payload.km_atual;
|
|
delete payload.data_venda;
|
|
delete payload.valor_venda;
|
|
delete payload.situacao;
|
|
|
|
let success;
|
|
if (editingVehicle) {
|
|
success = await updateVehicle(editingVehicle.idveiculo_frota, payload);
|
|
} else {
|
|
success = await createVehicle(payload);
|
|
}
|
|
|
|
if (success) setIsModalOpen(false);
|
|
};
|
|
|
|
const filteredVehicles = useMemo(() => Array.isArray(vehicles) ? vehicles.filter(v =>
|
|
v.placa?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
v.modelo?.toLowerCase().includes(searchTerm.toLowerCase())
|
|
) : [], [vehicles, searchTerm]);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header Actions */}
|
|
<div className="flex flex-col md:flex-row justify-between items-end md:items-center gap-4">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-slate-800 dark:text-white tracking-tight">Frota & Ativos</h1>
|
|
<p className="text-slate-500 text-sm">Gerencie os veículos cadastrados na plataforma.</p>
|
|
</div>
|
|
<div className="flex items-center gap-3 w-full md:w-auto">
|
|
<div className="relative flex-1 md:flex-none">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" size={16} />
|
|
<input
|
|
className="bg-white dark:bg-[#1c1c1c] border border-slate-200 dark:border-[#2a2a2a] text-slate-700 dark:text-slate-200 pl-10 pr-4 py-2 rounded-xl text-sm focus:outline-none focus:border-orange-500 w-full md:w-64"
|
|
placeholder="Buscar placa..."
|
|
value={searchTerm}
|
|
onChange={e => setSearchTerm(e.target.value)}
|
|
/>
|
|
</div>
|
|
<DarkButton onClick={() => handleOpenModal()}>
|
|
<Plus size={18} /> Novo Veículo
|
|
</DarkButton>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Status Highlights */}
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div
|
|
onClick={() => handleStatusClick('Veículo Alugado')}
|
|
className="bg-white dark:bg-[#1c1c1c] border border-slate-200 dark:border-[#2a2a2a] rounded-xl p-6 shadow-sm hover:shadow-md transition-all flex items-center justify-between group cursor-pointer hover:border-blue-500/30"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className="p-3 bg-blue-500/10 rounded-xl text-blue-600 group-hover:bg-blue-500 group-hover:text-white transition-colors">
|
|
<Truck size={24} />
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<span className="text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400">Veículos Alugados</span>
|
|
<div className="text-3xl font-bold text-slate-800 dark:text-white mt-0.5">
|
|
{getStatusData('Veículo Alugado')?.total || 0}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
onClick={() => handleStatusClick('Em Operação')}
|
|
className="bg-white dark:bg-[#1c1c1c] border border-slate-200 dark:border-[#2a2a2a] rounded-xl p-6 shadow-sm hover:shadow-md transition-all flex items-center justify-between group cursor-pointer hover:border-orange-500/30"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className="p-3 bg-orange-500/10 rounded-xl text-orange-600 group-hover:bg-orange-500 group-hover:text-white transition-colors">
|
|
<CheckCircle size={24} />
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<span className="text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400">Em Operação</span>
|
|
<div className="text-3xl font-bold text-slate-800 dark:text-white mt-0.5">
|
|
{getStatusData('Em Operação')?.total || 0}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Modular Excel Table */}
|
|
<div className="h-[600px] w-full max-w-full overflow-hidden min-w-0">
|
|
<ExcelTable
|
|
data={filteredVehicles}
|
|
loading={loading}
|
|
columns={[
|
|
{ header: 'ID', field: 'idveiculo_frota', width: '80px' },
|
|
|
|
{ header: 'PLACA', field: 'placa', width: '100px', className: 'font-mono font-bold text-orange-600 dark:text-orange-500' },
|
|
{ header: 'MODELO', field: 'modelo', width: '140px' },
|
|
{ header: ' FABRICANTE', field: 'fabricante', width: '120px' },
|
|
{ header: 'CORES', field: 'cor', width: '100px' },
|
|
{ header: 'ANO FAB.', field: 'ano_fabricacao', width: '80px' },
|
|
{ header: 'ANO MOD.', field: 'ano_modelo', width: '80px' },
|
|
{ header: 'RENAVAM', field: 'renavam', width: '130px' },
|
|
{ header: 'CHASSI', field: 'chassi', width: '180px' },
|
|
{ header: 'CATEGORIA', field: 'categoria', width: '120px' },
|
|
{ header: 'COMBUSTÍVEL', field: 'combustivel', width: '100px' },
|
|
{ header: 'TIPO PLACA', field: 'tipo_de_placa', width: '100px' },
|
|
|
|
{ header: 'TIPO FROTA', field: 'tipo_frota', width: '120px' },
|
|
{ header: 'ATUAÇÃO', field: 'atuacao', width: '150px' },
|
|
{ header: 'BASE', field: 'base', width: '120px' },
|
|
{ header: 'UF', field: 'uf', width: '60px' },
|
|
{ header: 'PROPRIETÁRIO', field: 'proprietario', width: '140px' },
|
|
{ header: 'CNPJ', field: 'cnpj', width: '150px' },
|
|
{ header: 'CONTRATO', field: 'contrato', width: '120px' },
|
|
{ header: 'GESTOR', field: 'gestor', width: '140px' },
|
|
{ header: 'COORDENADOR', field: 'coordenador', width: '140px' },
|
|
{ header: 'DISPATCHER', field: 'dispatcher', width: '140px' },
|
|
{ header: 'FISCAL OPER.', field: 'fiscal_operacao', width: '140px' },
|
|
{ header: 'VALOR FIPE', field: 'valor_fipe', width: '120px' },
|
|
{ header: 'VALOR ALUGUEL', field: 'valor_aluguel', width: '120px' },
|
|
{ header: '1ª LOCAÇÃO', field: 'primeira_locacao', width: '110px' },
|
|
{ header: 'DATA LIMITE', field: 'data_limite', width: '110px' },
|
|
{ header: 'GEOTAB', field: 'geotab', width: '80px' },
|
|
{ header: 'SASCAR', field: 'sascar', width: '80px' },
|
|
{ header: 'GOLFLEET', field: 'golfleet', width: '80px' },
|
|
{ header: 'POOLTRACK', field: 'pooltrack', width: '80px' },
|
|
{ header: 'T4S', field: 't4s', width: '80px' },
|
|
]}
|
|
filterDefs={[
|
|
{ field: 'placa', label: 'Placa', type: 'text', placeholder: 'Buscar placa...' },
|
|
{ field: 'modelo', label: 'Modelo', type: 'select' },
|
|
{ field: 'fabricante', label: 'Fabricante', type: 'select' },
|
|
{ field: 'base', label: 'Base', type: 'select' },
|
|
{ field: 'proprietario', label: 'Proprietário', type: 'select' },
|
|
{ field: 'tipo_frota', label: 'Tipo de Frota', type: 'select' },
|
|
]}
|
|
onEdit={handleOpenModal}
|
|
onDelete={(item) => deleteVehicle(item.idveiculo_frota)}
|
|
/>
|
|
</div>
|
|
|
|
{/* Modal - Dark Theme */}
|
|
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
|
|
<DialogContent className="max-w-4xl bg-white dark:bg-[#1c1c1c] border-slate-200 dark:border-[#2a2a2a] text-slate-700 dark:text-slate-200 p-0 overflow-hidden shadow-2xl">
|
|
<DialogHeader className="p-6 border-b border-slate-200 dark:border-[#2a2a2a] bg-slate-50 dark:bg-[#1c1c1c]">
|
|
<DialogTitle className="text-xl font-bold text-slate-800 dark:text-white uppercase tracking-tight">
|
|
{editingVehicle ? `Editando: ${formData.placa}` : 'Cadastro de Novo Veículo'}
|
|
</DialogTitle>
|
|
<DialogDescription className="text-slate-500 dark:text-stone-500">
|
|
Preencha os dados completos do ativo na frota.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<form onSubmit={handleSubmit} className="px-6 py-2 max-h-[75vh] overflow-y-auto custom-scrollbar">
|
|
{/* Seletor de Status Destacado */}
|
|
{/* Seletor de Status removido conforme solicitacao */}
|
|
|
|
|
|
<Tabs defaultValue="basicos" className="w-full">
|
|
<TabsList className="bg-slate-100 dark:bg-[#141414] border border-slate-200 dark:border-[#2a2a2a] w-full justify-start p-1 h-auto mb-4">
|
|
{['basicos', 'operacional', 'financeiro', 'tecnico'].map(tab => (
|
|
<TabsTrigger
|
|
key={tab}
|
|
value={tab}
|
|
className="data-[state=active]:bg-orange-600 data-[state=active]:text-white text-slate-500 dark:text-stone-400 text-[10px] uppercase font-bold px-4 py-2"
|
|
>
|
|
{tab === 'tecnico' ? 'RASTREADOR' : tab}
|
|
</TabsTrigger>
|
|
))}
|
|
</TabsList>
|
|
|
|
<div className="pb-4 min-h-[350px]">
|
|
<TabsContent value="basicos" className="space-y-4 m-0">
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<DarkInput label="Placa" value={formData.placa} onChange={e => setFormData({...formData, placa: e.target.value})} required />
|
|
<DarkInput label="Chassi" value={formData.chassi} onChange={e => setFormData({...formData, chassi: e.target.value})} />
|
|
<DarkInput label="Renavam" value={formData.renavam} onChange={e => setFormData({...formData, renavam: e.target.value})} />
|
|
</div>
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<DarkInput label="Modelo" value={formData.modelo} onChange={e => setFormData({...formData, modelo: e.target.value})} />
|
|
<DarkInput label="Fabricante" value={formData.fabricante} onChange={e => setFormData({...formData, fabricante: e.target.value})} />
|
|
<DarkInput label="Cor" value={formData.cor} onChange={e => setFormData({...formData, cor: e.target.value})} />
|
|
</div>
|
|
<div className="grid grid-cols-4 gap-4">
|
|
<DarkInput label="Ano Fab." value={formData.ano_fabricacao} onChange={e => setFormData({...formData, ano_fabricacao: e.target.value})} />
|
|
<DarkInput label="Ano Mod." value={formData.ano_modelo} onChange={e => setFormData({...formData, ano_modelo: e.target.value})} />
|
|
<DarkSelect label="Categoria" options={['Utilitário', 'Passeio', 'Pesado']} value={formData.categoria} onChange={v => setFormData({...formData, categoria: v})} />
|
|
<DarkSelect label="Combustível" options={['Flex', 'Diesel', 'Gasolina', 'Elétrico']} value={formData.combustivel} onChange={v => setFormData({...formData, combustivel: v})} />
|
|
</div>
|
|
<DarkInput label="Tipo Placa (Contexto)" value={formData.tipo_placa} onChange={e => setFormData({...formData, tipo_placa: e.target.value})} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="operacional" className="space-y-4 m-0">
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<DarkInput label="Base" value={formData.base} onChange={e => setFormData({...formData, base: e.target.value})} />
|
|
<DarkInput label="UF" value={formData.uf} onChange={e => setFormData({...formData, uf: e.target.value})} maxLength={2} />
|
|
{/* Proprietário comentado nesta aba conforme nova solicitação para aba financeiro */}
|
|
{/* <DarkSelect label="Proprietário" options={['Localiza', 'Movida', 'Próprio', 'Unidas']} value={formData.proprietario} onChange={v => setFormData({...formData, proprietario: v})} /> */}
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<DarkInput label="Atuação / Operação" value={formData.atuacao} onChange={e => setFormData({...formData, atuacao: e.target.value})} />
|
|
<DarkInput label="Tipo Frota" value={formData.tipo_frota} onChange={e => setFormData({...formData, tipo_frota: e.target.value})} />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<DarkInput label="Gestor" value={formData.gestor} onChange={e => setFormData({...formData, gestor: e.target.value})} />
|
|
<DarkInput label="Coordenador" value={formData.coordenador} onChange={e => setFormData({...formData, coordenador: e.target.value})} />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<DarkInput label="Dispatcher" value={formData.dispatcher} onChange={e => setFormData({...formData, dispatcher: e.target.value})} />
|
|
<DarkInput label="Fiscal de Operação" value={formData.fiscal_operacao} onChange={e => setFormData({...formData, fiscal_operacao: e.target.value})} />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{/* Campos motorista e status comentados para escrita e envio ao back conforme solicitado */}
|
|
{/* <AutocompleteInput
|
|
label="Motorista Atual"
|
|
value={formData.motorista_atual}
|
|
onChange={v => setFormData({...formData, motorista_atual: v})}
|
|
options={drivers}
|
|
displayKey="NOME_FAVORECIDO"
|
|
valueKey="NOME_FAVORECIDO"
|
|
placeholder="Buscar motorista..."
|
|
/>
|
|
<DarkSelect label="Status Veículo" options={['ATIVO', 'INATIVO', 'MANUTENÇÃO', 'VENDIDO', 'SINISTRADO']} value={formData.status_veiculo} onChange={v => setFormData({...formData, status_veiculo: v})} /> */}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="financeiro" className="space-y-4 m-0">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<DarkInput label="Valor FIPE" value={formData.valor_fipe} onChange={e => setFormData({...formData, valor_fipe: e.target.value})} />
|
|
<DarkInput label="Valor Aluguel" value={formData.valor_aluguel} onChange={e => setFormData({...formData, valor_aluguel: e.target.value})} />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<DarkInput label="CNPJ Proprietário" value={formData.cnpj} onChange={e => setFormData({...formData, cnpj: e.target.value})} />
|
|
<DarkInput label="Contrato" value={formData.contrato} onChange={e => setFormData({...formData, contrato: e.target.value})} />
|
|
</div>
|
|
<div className="grid grid-cols-3 gap-4">
|
|
{/* Empresa e Locadora comentados para escrita e envio ao back */}
|
|
{/* <DarkInput label="Empresa" value={formData.empresa} onChange={e => setFormData({...formData, empresa: e.target.value})} /> */}
|
|
<DarkInput label="Proprietário" value={formData.proprietario} onChange={e => setFormData({...formData, proprietario: e.target.value})} />
|
|
<DarkInput label="Financiamento" value={formData.financiamento} onChange={e => setFormData({...formData, financiamento: e.target.value})} />
|
|
{/* <DarkInput label="Locadora" value={formData.locadora} onChange={e => setFormData({...formData, locadora: e.target.value})} /> */}
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<DarkInput type="date" label="Data Aquisição" value={formData.data_aquisicao} onChange={e => setFormData({...formData, data_aquisicao: e.target.value})} />
|
|
<DarkInput type="number" label="Valor Aquisição" value={formData.valor_aquisicao} onChange={e => setFormData({...formData, valor_aquisicao: e.target.value})} />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{/* Campos comentados para escrita e envio ao back conforme solicitado */}
|
|
{/* <DarkInput type="date" label="Data Venda" value={formData.data_venda} onChange={e => setFormData({...formData, data_venda: e.target.value})} />
|
|
<DarkInput type="number" label="Valor Venda" value={formData.valor_venda} onChange={e => setFormData({...formData, valor_venda: e.target.value})} /> */}
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<DarkInput type="date" label="Primeira Locação" value={formData.primeira_locacao} onChange={e => setFormData({...formData, primeira_locacao: e.target.value})} />
|
|
<DarkInput type="date" label="Data Limite" value={formData.data_limite} onChange={e => setFormData({...formData, data_limite: e.target.value})} />
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="tecnico" className="space-y-4 m-0">
|
|
<div className="grid grid-cols-1 gap-4">
|
|
{/* KM Atual comentado para escrita e envio ao back */}
|
|
{/* <DarkInput label="KM Atual" value={formData.km_atual} onChange={e => setFormData({...formData, km_atual: e.target.value})} /> */}
|
|
</div>
|
|
<div className="grid grid-cols-4 gap-4 pt-2">
|
|
{['geotab', 'sascar', 'golfleet', 'pooltrack', 't4s'].map(tracker => (
|
|
<DarkSelect key={tracker} label={tracker.toUpperCase()} options={['SIM', 'NÃO']} value={formData[tracker]} onChange={v => setFormData({...formData, [tracker]: v})} />
|
|
))}
|
|
</div>
|
|
<div className="gap-4 pt-2">
|
|
<label className="text-[10px] uppercase font-bold text-slate-500 tracking-wider ml-1">Observações Gerais</label>
|
|
<textarea
|
|
className="w-full bg-slate-50 dark:bg-[#141414] border border-slate-200 dark:border-[#333] rounded-lg px-3 py-2 text-sm text-slate-700 dark:text-slate-200 focus:outline-none focus:border-orange-500 transition-all placeholder:text-slate-400 min-h-[80px]"
|
|
value={formData.observacoes}
|
|
onChange={e => setFormData({...formData, observacoes: e.target.value})}
|
|
/>
|
|
</div>
|
|
</TabsContent>
|
|
</div>
|
|
</Tabs>
|
|
</form>
|
|
|
|
<DialogFooter className="bg-slate-50 dark:bg-[#141414] border-t border-slate-200 dark:border-[#2a2a2a] p-4 gap-2">
|
|
<DarkButton type="button" variant="ghost" onClick={() => setIsModalOpen(false)}>Cancelar</DarkButton>
|
|
<DarkButton type="submit" onClick={handleSubmit}>Salvar Veículo</DarkButton>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Records Detail Modal */}
|
|
<Dialog open={isRecordsModalOpen} onOpenChange={setIsRecordsModalOpen}>
|
|
<DialogContent className="max-w-7xl w-[95vw] h-[90vh] bg-white dark:bg-[#0f0f0f] border-slate-200 dark:border-[#2a2a2a] p-0 overflow-hidden shadow-2xl flex flex-col !outline-none">
|
|
<DialogHeader className="p-6 border-b border-slate-200 dark:border-[#2a2a2a] shrink-0 bg-slate-50/50 dark:bg-[#0f0f0f]">
|
|
<div className="flex items-center gap-4">
|
|
<div className="p-4 bg-blue-500/10 rounded-2xl text-blue-600 shadow-inner">
|
|
<Truck size={28} />
|
|
</div>
|
|
<div>
|
|
<DialogTitle className="text-2xl font-bold text-slate-800 dark:text-white uppercase tracking-tight">
|
|
Veículos: {selectedStatusRecords?.title}
|
|
</DialogTitle>
|
|
<DialogDescription className="text-slate-500 dark:text-slate-400 font-medium text-sm">
|
|
Listagem completa dos {selectedStatusRecords?.records?.length || 0} veículos com status <span className="text-blue-500 font-bold">"{selectedStatusRecords?.title}"</span>.
|
|
</DialogDescription>
|
|
</div>
|
|
</div>
|
|
</DialogHeader>
|
|
|
|
<div className="flex-1 overflow-hidden p-6 bg-white dark:bg-[#1c1c1c]">
|
|
<div className="h-full w-full rounded-2xl border border-slate-200 dark:border-[#2a2a2a] overflow-hidden shadow-sm">
|
|
<ExcelTable
|
|
data={selectedStatusRecords?.records || []}
|
|
columns={[
|
|
{ header: 'Placa', field: 'placa', width: '100px', className: 'font-mono font-bold text-orange-600 dark:text-orange-500' },
|
|
{ header: 'Modelo', field: 'modelo', width: '150px' },
|
|
{ header: 'Unidade', field: 'base', width: '120px' },
|
|
{ header: 'Motorista', field: 'motorista', width: '180px' },
|
|
{ header: 'Status', field: 'status', width: '140px' },
|
|
{ header: 'Manutenção', field: 'manutencao', width: '120px' },
|
|
{ header: 'Atuação', field: 'atuacao', width: '140px' },
|
|
{ header: 'Proprietário', field: 'proprietario', width: '140px' },
|
|
{ header: 'VecFleet', field: 'vecfleet', width: '140px' },
|
|
{ header: 'OBS', field: 'obs', width: '250px' },
|
|
]}
|
|
filterDefs={[
|
|
{ field: 'placa', label: 'Placa', type: 'text' },
|
|
{ field: 'motorista', label: 'Motorista', type: 'text' },
|
|
{ field: 'base', label: 'Unidade', type: 'select' }
|
|
]}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter className="p-4 border-t border-slate-200 dark:border-[#2a2a2a] bg-slate-50/50 dark:bg-[#1c1c1c]">
|
|
<DarkButton variant="secondary" onClick={() => setIsRecordsModalOpen(false)}>
|
|
Fechar Listagem
|
|
</DarkButton>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
|
|
|