testes/src/features/prafrot/views/AvailabilityView.jsx

212 lines
10 KiB
JavaScript

import React, { useEffect, useState } from 'react';
import { useAvailability } from '../hooks/useAvailability';
import { useVehicles } from '../hooks/useVehicles';
import ExcelTable from '../components/ExcelTable';
import { Plus, Search, Calendar as CalendarIcon } from 'lucide-react';
import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription
} from "@/components/ui/dialog";
import { toast } from 'sonner';
// Reusing styled components locally
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-lg 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-lg 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-lg 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>
);
};
export default function AvailabilityView() {
const { availabilities, fetchAvailabilities, createAvailability, updateAvailability, deleteAvailability } = useAvailability();
const { vehicles, fetchVehicles } = useVehicles();
const [searchTerm, setSearchTerm] = useState('');
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingItem, setEditingItem] = useState(null);
const initialFormState = {
iddisponibilidade_frota: '', idveiculo_frota: '', placa: '', disponibilidade: '', status_disponibilidade: 'Disponível'
};
const [formData, setFormData] = useState(initialFormState);
useEffect(() => {
fetchAvailabilities();
fetchVehicles();
}, []);
const handleOpenModal = (item = null) => {
if (item) {
setEditingItem(item);
setFormData({ ...initialFormState, ...item });
} else {
setEditingItem(null);
setFormData(initialFormState);
}
setIsModalOpen(true);
};
const handlePlateChange = (e) => {
const val = e.target.value;
const foundVehicle = vehicles.find(v => v.placa === val);
setFormData({
...formData,
placa: val,
idveiculo_frota: foundVehicle ? foundVehicle.idveiculo_frota : ''
});
};
const handleSubmit = async (e) => {
e.preventDefault();
const payload = {
...formData,
iddisponibilidade_frota: formData.iddisponibilidade_frota ? Number(formData.iddisponibilidade_frota) : undefined,
idveiculo_frota: formData.idveiculo_frota ? Number(formData.idveiculo_frota) : null
};
let success;
if (editingItem) {
success = await updateAvailability(Number(editingItem.iddisponibilidade_frota || editingItem.id), payload);
} else {
success = await createAvailability(payload);
}
if (success) setIsModalOpen(false);
};
const filteredData = availabilities.filter(item =>
item.placa?.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<div className="space-y-6">
<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">Disponibilidade e Agenda</h1>
<p className="text-slate-500 text-sm">Visualização de disponibilidade da frota.</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-lg 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 Registro
</DarkButton>
</div>
</div>
<div className="h-[600px] w-full max-w-full overflow-hidden min-w-0">
<ExcelTable
data={filteredData}
columns={[
{ header: 'ID', field: 'iddisponibilidade_frota', width: '80px' },
{ header: 'VEÍCULO ID', field: 'idveiculo_frota', width: '100px' },
{ header: 'PLACA', field: 'placa', width: '120px', className: 'font-mono font-bold text-orange-600 dark:text-orange-500' },
{ header: 'DATA', field: 'disponibilidade', width: '180px', render: (row) => row.disponibilidade?.split('T')[0] },
{ header: 'STATUS', field: 'status_disponibilidade', width: '150px', render: (row) => (
<span className={`inline-flex items-center px-2 py-0.5 rounded text-[9px] font-bold uppercase tracking-wider border ${
row.status_disponibilidade === 'Disponível' ? 'bg-orange-500/10 text-orange-500 border-orange-500/20' :
'bg-red-500/10 text-red-500 border-red-500/20'
}`}>
{row.status_disponibilidade}
</span>
)}
]}
filterDefs={[
{ field: 'placa', label: 'Placa', type: 'text', placeholder: 'Buscar placa...' },
{ field: 'status_disponibilidade', label: 'Status', type: 'select' },
]}
onEdit={handleOpenModal}
onDelete={(item) => deleteAvailability(item.iddisponibilidade_frota)}
/>
</div>
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<DialogContent className="max-w-md bg-white dark:bg-[#1c1c1c] border-slate-200 dark:border-[#2a2a2a] text-slate-700 dark:text-slate-200 p-0 overflow-hidden">
<DialogHeader className="p-6 border-b border-slate-200 dark:border-[#2a2a2a]">
<DialogTitle className="text-slate-800 dark:text-white uppercase font-bold">
{editingItem ? `Editando ID: ${editingItem.iddisponibilidade_frota}` : 'Nova Disponibilidade'}
</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4 px-6 py-4 max-h-[75vh] overflow-y-auto custom-scrollbar">
{formData.iddisponibilidade_frota && (
<div className="bg-orange-500/5 p-3 rounded-xl border border-orange-500/10 mb-2">
<p className="text-[10px] uppercase font-bold text-orange-500/60 tracking-widest">ID do Registro</p>
<p className="text-lg font-bold text-orange-500">{formData.iddisponibilidade_frota}</p>
</div>
)}
<div className="space-y-1.5">
<label className="text-[10px] uppercase font-bold text-slate-500 dark:text-slate-400 tracking-wider ml-1">Placa (Pesquisar)</label>
<input
list="veiculos-list"
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 focus:ring-1 focus:ring-orange-500 transition-all placeholder:text-slate-400 dark:placeholder:text-slate-700"
value={formData.placa}
onChange={handlePlateChange}
placeholder="Digite ou selecione a placa..."
required
/>
<datalist id="veiculos-list">
{vehicles.map(v => (
<option key={v.idveiculo_frota} value={v.placa} />
))}
</datalist>
{formData.idveiculo_frota && (
<span className="text-[10px] text-green-500 ml-1">Veículo vinculado (ID: {formData.idveiculo_frota})</span>
)}
</div>
<DarkInput type="date" label="Data de Disponibilidade" value={formData.disponibilidade?.split('T')[0]} onChange={e => setFormData({...formData, disponibilidade: e.target.value})} />
<DarkSelect label="Status" options={['Disponível', 'Indisponível', 'Reserva']} value={formData.status_disponibilidade} onChange={v => setFormData({...formData, status_disponibilidade: v})} />
<DialogFooter className="bg-slate-50 dark:bg-transparent 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">Salvar</DarkButton>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</div>
);
}