Go back home
}
/>
);
// === Fim de: ./src/main.jsx ===
// === Início de: ./src/App.jsx ===
import { Outlet } from 'react-router-dom';
import BottomNav from './components/BottomNav';
function AppLayout() {
return (
);
}
export default AppLayout;
// === Fim de: ./src/App.jsx ===
// === Início de: ./src/hooks/useChargers.js ===
// src/hooks/useChargers.js
import { useEffect, useState } from "react";
import { getAuthHeader } from "@/utils/apiAuthHeader";
export function useChargers() {
const [chargers, setChargers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
setLoading(true);
fetch("/api/chargers", {
headers: getAuthHeader(),
})
.then(res => {
if (!res.ok) throw new Error("Erro ao buscar carregadores");
return res.json();
})
.then(json => {
setChargers(json.data || []);
setError("");
})
.catch(() => setError("Falha ao carregar carregadores"))
.finally(() => setLoading(false));
}, []);
return { chargers, setChargers, loading, error };
}
// === Fim de: ./src/hooks/useChargers.js ===
// === Início de: ./src/hooks/useSchedules.js ===
// src/hooks/useSchedules.js
import { useState, useEffect } from 'react';
import { getAuthHeader } from '@/utils/apiAuthHeader';
// Agora recebe chargerId!
export function useSchedules(chargerId) {
const [schedules, setSchedules] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
if (!chargerId) return; // Só busca se houver carregador selecionado
setLoading(true);
fetch(`/api/chargers/${chargerId}/schedule`, {
headers: getAuthHeader(),
})
.then(res => {
if (res.status === 401 || res.status === 403) {
setError('Sessão expirada. Faça login novamente.');
return { data: [] };
}
return res.json();
})
.then(json => {
setSchedules(json.data || []);
setError('');
})
.catch(() => setError('Falha ao carregar agendamentos'))
.finally(() => setLoading(false));
}, [chargerId]); // <-- Atualiza quando o chargerId mudar
return { schedules, setSchedules, loading, error };
}
// === Fim de: ./src/hooks/useSchedules.js ===
// === Início de: ./src/hooks/useChargerStatus.js ===
// === Fim de: ./src/hooks/useChargerStatus.js ===
// === Início de: ./src/hooks/useApi.js ===
// src/hooks/useApi.js
import { useState } from 'react';
import { getAuthHeader } from '@/utils/apiAuthHeader';
export default function useApi() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const request = async (url, options = {}) => {
setLoading(true);
setError('');
try {
const res = await fetch(url, {
...options,
headers: {
...getAuthHeader(),
...(options.headers || {})
},
});
if (res.status === 401 || res.status === 403) {
setError('Sessão expirada. Faça login novamente.');
throw new Error('Sessão expirada');
}
const data = await res.json();
if (!res.ok) throw new Error(data.message || 'Erro na API');
return data;
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
};
return { request, loading, error };
}
// === Fim de: ./src/hooks/useApi.js ===
// === Início de: ./src/hooks/useCharger.js ===
// src/hooks/useCharger.js
import { useState, useEffect } from 'react';
import { getAuthHeader } from '@/utils/apiAuthHeader';
export function useCharger(chargerId) {
const [chargerData, setChargerData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
if (!chargerId) return;
const fetchCharger = async () => {
setLoading(true);
try {
const res = await fetch(`/api/chargers/${chargerId}/status`, {
headers: getAuthHeader(),
});
if (!res.ok) throw new Error('Failed to fetch charger data');
const json = await res.json();
setChargerData(json.data);
setError('');
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchCharger();
}, [chargerId]);
return { chargerData, loading, error };
}
// === Fim de: ./src/hooks/useCharger.js ===
// === Início de: ./src/hooks/useHistory.js ===
// src/hooks/useHistory.js
import { useState, useEffect } from 'react';
import { getAuthHeader } from '@/utils/apiAuthHeader';
// Agora recebe chargerId!
export function useHistory(chargerId) {
const [histories, setHistory] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
if (!chargerId) return; // Só busca se houver carregador selecionado
setLoading(true);
fetch(`/api/chargers/${chargerId}/history`, {
headers: getAuthHeader(),
})
.then(res => {
if (res.status === 401 || res.status === 403) {
setError('Sessão expirada. Faça login novamente.');
return { data: [] };
}
return res.json();
})
.then(json => {
setHistory(json.data || []);
setError('');
})
.catch(() => setError('Falha ao carregar agendamentos'))
.finally(() => setLoading(false));
}, [chargerId]); // <-- Atualiza quando o chargerId mudar
return { histories, setHistory, loading, error };
}
// === Fim de: ./src/hooks/useHistory.js ===
// === Início de: ./src/utils/apiAuthHeader.js ===
export function getAuthHeader() {
if (typeof window === 'undefined') return {};
const token = window.localStorage?.getItem('token');
return token ? { Authorization: `Bearer ${token}` } : {};
}
// === Fim de: ./src/utils/apiAuthHeader.js ===
// === Início de: ./src/utils/apiFetch.js ===
// utils/apiFetch.js
import { getAuthHeader } from './apiAuthHeader';
export async function apiFetch(url, options = {}) {
const res = await fetch(url, {
...options,
headers: {
...getAuthHeader(),
...(options.headers || {}),
},
});
const json = await res.json();
if (!res.ok || !json.success) {
throw new Error(json.message || 'Erro na requisição');
}
return json.data;
}
// === Fim de: ./src/utils/apiFetch.js ===
// === Início de: ./src/components/ProtectedRoute.jsx ===
import { useAuth } from '@/contexts/AuthContext';
import { Navigate, useLocation } from 'react-router-dom';
// Uso:
export default function ProtectedRoute({ children }) {
const { isAuthenticated } = useAuth();
const location = useLocation();
if (!isAuthenticated) {
// redireciona para /login, guardando a rota atual para possível redirect pós-login
return ;
}
return children;
}
// === Fim de: ./src/components/ProtectedRoute.jsx ===
// === Início de: ./src/components/BottomNav.jsx ===
import { Link, useMatch, useResolvedPath } from 'react-router-dom';
import { Zap, Calendar, Settings, BatteryCharging } from 'lucide-react';
import { motion } from 'framer-motion';
const tabs = [
{ label: 'Carregador', to: '/dashboard', icon: Zap },
{ label: 'Histórico', to: '/history', icon: Calendar },
{ label: 'Configuração', to: '/settings', icon: Settings },
{ label: 'Carregadores', to: '/', icon: BatteryCharging },
];
export default function BottomNav() {
return (
{tabs.map(({ label, to, icon: Icon }) => {
const resolved = useResolvedPath(to);
const match = useMatch({ path: resolved.pathname, end: true });
return (
{label}
);
})}
);
}
// === Fim de: ./src/components/BottomNav.jsx ===
// === Início de: ./src/components/ToastContext.jsx ===
// src/components/ToastContext.jsx
import { createContext, useContext, useState } from 'react';
const ToastContext = createContext();
export function ToastProvider({ children }) {
const [toast, setToast] = useState(null);
function showToast(message, type = 'success') {
setToast({ message, type });
setTimeout(() => setToast(null), 2500);
}
return (
{children}
{toast && (
{toast.message}
)}
);
}
export function useToast() {
return useContext(ToastContext);
}
// === Fim de: ./src/components/ToastContext.jsx ===
// === Início de: ./src/components/ui/SectionCard.jsx ===
// src/components/ui/SectionCard.jsx
import { motion } from 'framer-motion';
export function SectionCard({ title, icon, children }) {
return (
{icon &&
{icon}
}
{title}
{children}
);
}
// === Fim de: ./src/components/ui/SectionCard.jsx ===
// === Início de: ./src/pages/ChargersPage.jsx ===
// src/pages/ChargersPage.jsx
import React, { useState } from 'react';
import { Plus, Loader2, X, Trash2 } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { useChargersContext } from '@/contexts/ChargersContext';
import { useToast } from '@/components/ToastContext';
export default function ChargersPage() {
const { chargers, selectedCharger, selectChargerById, fetchChargers, loading, error } = useChargersContext();
const [deletingId, setDeletingId] = useState(null);
const [showAdd, setShowAdd] = useState(false);
const toast = useToast();
const handleOpenAdd = () => setShowAdd(true);
const handleCloseAdd = () => setShowAdd(false);
const handleSelect = (charger) => {
selectChargerById(charger.id);
};
const handleDelete = async (id) => {
if (!window.confirm('Are you sure you want to delete this charger? This action cannot be undone.')) return;
setDeletingId(id);
try {
const res = await fetch(`/api/chargers/${id}`, { method: 'DELETE' });
if (!res.ok) throw new Error('Failed to delete charger');
toast('Charger deleted', 'success');
await fetchChargers();
} catch (err) {
console.error(err);
toast(err.message, 'error');
} finally {
setDeletingId(null);
}
};
if (loading) {
return (