This commit is contained in:
2025-06-17 17:46:21 +01:00
parent efc4fdb4c0
commit 4a8395a307
38 changed files with 12370 additions and 1 deletions

178
Chargers copy.jsx Normal file
View File

@@ -0,0 +1,178 @@
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>
);
}