179 lines
6.5 KiB
JavaScript
179 lines
6.5 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { Plus, Loader2, X, Trash2 } from 'lucide-react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
|
export default function ChargersPage() {
|
|
const [chargers, setChargers] = useState([]);
|
|
const [selectedCharger, setSelectedCharger] = useState(null);
|
|
const [deletingId, setDeletingId] = useState(null);
|
|
const [showAdd, setShowAdd] = useState(false);
|
|
const navigate = useNavigate();
|
|
|
|
// Carregar os carregadores ao montar o componente
|
|
useEffect(() => {
|
|
async function fetchChargers() {
|
|
try {
|
|
const res = await fetch('/api/chargers');
|
|
const json = await res.json();
|
|
if (res.ok) {
|
|
setChargers(json.data || []);
|
|
} else {
|
|
throw new Error('Falha ao carregar carregadores');
|
|
}
|
|
} catch (err) {
|
|
console.error(err.message);
|
|
}
|
|
}
|
|
fetchChargers();
|
|
}, []);
|
|
|
|
const handleOpenAdd = () => setShowAdd(true);
|
|
const handleCloseAdd = () => setShowAdd(false);
|
|
|
|
const handleSelect = (id) => {
|
|
setSelectedCharger(id); // Atualiza o carregador selecionado
|
|
navigate(`/charger/${id}`); // Navega para a página do carregador
|
|
};
|
|
|
|
const handleDelete = async (id) => {
|
|
if (window.confirm('Excluir este carregador? Esta ação não pode ser desfeita.')) {
|
|
setDeletingId(id);
|
|
try {
|
|
const res = await fetch(`/api/chargers/${id}`, { method: 'DELETE' });
|
|
if (res.ok) {
|
|
setChargers((prevChargers) => prevChargers.filter(c => c.id !== id));
|
|
} else {
|
|
throw new Error('Erro ao excluir carregador');
|
|
}
|
|
} catch (err) {
|
|
console.error('Erro ao deletar carregador', err);
|
|
} finally {
|
|
setDeletingId(null);
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="max-w-2xl mx-auto p-4 pb-24">
|
|
<div className="flex items-center justify-between mb-5">
|
|
<h1 className="text-2xl font-bold">Meus Carregadores</h1>
|
|
<button
|
|
className="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg shadow hover:bg-blue-700 transition"
|
|
onClick={handleOpenAdd}
|
|
>
|
|
<Plus size={20} /> Novo
|
|
</button>
|
|
</div>
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
{chargers.length > 0 ? (
|
|
chargers.map((charger) => (
|
|
<motion.div
|
|
key={charger.id}
|
|
className={`bg-white rounded-xl shadow-md border p-5 flex flex-col gap-2 cursor-pointer transition relative ${selectedCharger === charger.id ? 'border-blue-500 bg-blue-50' : ''}`}
|
|
onClick={() => handleSelect(charger.id)}
|
|
>
|
|
<button
|
|
className="absolute top-3 right-3 text-gray-400 hover:text-red-500 transition z-10"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleDelete(charger.id);
|
|
}}
|
|
disabled={deletingId === charger.id}
|
|
>
|
|
{deletingId === charger.id ? <Loader2 className="animate-spin" size={18} /> : <Trash2 size={18} />}
|
|
</button>
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<span className={`inline-block w-2 h-2 rounded-full ${charger.status === 'online' ? 'bg-green-500' : charger.status === 'standby' ? 'bg-yellow-400' : 'bg-gray-400'}`} />
|
|
<span className="font-semibold text-lg">{charger.nome}</span>
|
|
</div>
|
|
<div className="text-xs text-gray-500">Código: <span className="font-mono">{charger.codigoEmparelhamento}</span></div>
|
|
<div className="text-xs text-gray-500">Local: {charger.local}</div>
|
|
<div className="text-xs text-gray-400">Última atividade: {charger.ultimaAtividade}</div>
|
|
</motion.div>
|
|
))
|
|
) : (
|
|
<p className="text-gray-500">Nenhum carregador encontrado</p>
|
|
)}
|
|
</div>
|
|
<AnimatePresence>
|
|
{showAdd && <AddChargerModal onClose={handleCloseAdd} />}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function AddChargerModal({ onClose }) {
|
|
const [nome, setNome] = useState('');
|
|
const [local, setLocal] = useState('');
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
async function handleSubmit(e) {
|
|
e.preventDefault();
|
|
setSaving(true);
|
|
try {
|
|
const res = await fetch('/api/chargers', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ nome, local })
|
|
});
|
|
const json = await res.json();
|
|
onClose();
|
|
} catch (err) {
|
|
console.error('Erro ao adicionar carregador', err);
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className="fixed inset-0 bg-black bg-opacity-40 z-50 flex items-center justify-center"
|
|
>
|
|
<motion.div
|
|
initial={{ scale: 0.94, y: 40, opacity: 0 }}
|
|
animate={{ scale: 1, y: 0, opacity: 1 }}
|
|
exit={{ scale: 0.92, y: 40, opacity: 0 }}
|
|
className="bg-white rounded-2xl shadow-xl p-8 w-full max-w-sm relative"
|
|
>
|
|
<button className="absolute top-3 right-3 text-gray-500 hover:text-gray-800" onClick={onClose} aria-label="Fechar">
|
|
<X />
|
|
</button>
|
|
<h2 className="text-xl font-bold mb-4">Novo Carregador</h2>
|
|
<form className="space-y-4" onSubmit={handleSubmit}>
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">Nome</label>
|
|
<input
|
|
type="text"
|
|
value={nome}
|
|
onChange={(e) => setNome(e.target.value)}
|
|
required
|
|
className="w-full border border-gray-300 rounded-lg px-3 py-2"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">Localização</label>
|
|
<input
|
|
type="text"
|
|
value={local}
|
|
onChange={(e) => setLocal(e.target.value)}
|
|
required
|
|
className="w-full border border-gray-300 rounded-lg px-3 py-2"
|
|
/>
|
|
</div>
|
|
<button
|
|
type="submit"
|
|
disabled={saving}
|
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded-lg transition flex items-center justify-center gap-2"
|
|
>
|
|
{saving && <Loader2 className="animate-spin" size={18} />} Salvar
|
|
</button>
|
|
</form>
|
|
</motion.div>
|
|
</motion.div>
|
|
);
|
|
}
|