From d5b32fb4a28638ae4acfc1c716439d996a37907b Mon Sep 17 00:00:00 2001 From: raven Date: Fri, 26 Dec 2025 14:16:25 -0300 Subject: [PATCH] Add Historico --- backend/src/index.js | 45 +++++++++-- frontend/src/App.jsx | 185 ++++++++++++++++++++++++++++++++----------- 2 files changed, 176 insertions(+), 54 deletions(-) diff --git a/backend/src/index.js b/backend/src/index.js index 576bf56..546b5da 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -13,7 +13,7 @@ app.use(express.json()); const server = http.createServer(app); const io = new Server(server, { cors: { - origin: "*", + origin: "*", methods: ["GET", "POST"] } }); @@ -45,11 +45,42 @@ app.post('/api/plates', async (req, res) => { } }); +// History Endpoint +app.get('/api/history', async (req, res) => { + const { date } = req.query; // Format: YYYY-MM-DD + if (!date) { + return res.status(400).json({ error: 'Date is required' }); + } + + const startDate = new Date(date); + startDate.setHours(0, 0, 0, 0); + + const endDate = new Date(date); + endDate.setHours(23, 59, 59, 999); + + try { + const logs = await prisma.accessLog.findMany({ + where: { + timestamp: { + gte: startDate, + lte: endDate + } + }, + orderBy: { + timestamp: 'desc' + } + }); + res.json(logs); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + // Detection Endpoint (from Python) app.post('/api/detect', async (req, res) => { const { plate_number } = req.body; console.log(`Detected: ${plate_number}`); - + try { // Check if plate exists let plate = await prisma.plate.findUnique({ @@ -57,15 +88,15 @@ app.post('/api/detect', async (req, res) => { }); let accessStatus = 'DENIED'; - + if (plate && plate.status === 'ALLOWED') { accessStatus = 'GRANTED'; - } + } if (!plate) { - // Optional: Auto-create unknown plates? - // For now, treat as UNKNOWN (Denied) - accessStatus = 'UNKNOWN'; + // Optional: Auto-create unknown plates? + // For now, treat as UNKNOWN (Denied) + accessStatus = 'UNKNOWN'; } // Log the access attempt diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index abdcc52..6d27f0d 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react' import io from 'socket.io-client' import axios from 'axios' -import { Car, AlertCircle, CheckCircle, XCircle, Clock } from 'lucide-react' +import { Car, AlertCircle, CheckCircle, XCircle, Clock, Calendar } from 'lucide-react' // Env var logic for Vite const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000'; @@ -14,6 +14,12 @@ function App() { const [showModal, setShowModal] = useState(false); const [newPlate, setNewPlate] = useState({ number: '', owner: '' }); + // History State + const [viewMode, setViewMode] = useState('live'); // 'live' | 'history' + const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]); + const [historyLogs, setHistoryLogs] = useState([]); + const [loadingHistory, setLoadingHistory] = useState(false); + const handleRegister = async (e) => { e.preventDefault(); try { @@ -56,6 +62,26 @@ function App() { } }; + const fetchHistory = async (date) => { + setLoadingHistory(true); + try { + const res = await axios.get(`${API_URL}/api/history?date=${date}`); + setHistoryLogs(res.data); + } catch (err) { + console.error("Error fetching history:", err); + // alert("Failed to fetch history"); + } finally { + setLoadingHistory(false); + } + }; + + // Fetch history when date changes or when switching to history view + useEffect(() => { + if (viewMode === 'history') { + fetchHistory(selectedDate); + } + }, [viewMode, selectedDate]); + const StatusBadge = ({ status }) => { const colors = { GRANTED: 'bg-green-500/20 text-green-400 border-green-500/50', @@ -142,58 +168,123 @@ function App() {
- {/* Live Detections Feed */} + {/* Main Feed Section (Live / History) */}
-

- - Live Detections +

+
+ {viewMode === 'live' ? : } + {viewMode === 'live' ? 'Live Detections' : 'History Log'} +
+ + {/* Toggle Switch */} +
+ + +

- {/* Video Feed */} -
- Live Camera Feed { - e.target.style.display = 'none'; - e.target.nextSibling.style.display = 'flex'; - }} - /> -
-

Camera Offline or Connecting...

-
-
-
- LIVE -
-
-
- - {/* Detections List */} -

Recent Scans

- - {detections.length === 0 ? ( -
-

No detections yet...

-
- ) : ( -
- {detections.map((d, i) => ( -
-
-
- {d.plate} -
-
- {new Date(d.timestamp).toLocaleTimeString()} -
-
- + {viewMode === 'live' ? ( + <> + {/* Video Feed */} +
+ Live Camera Feed { + e.target.style.display = 'none'; + e.target.nextSibling.style.display = 'flex'; + }} + /> +
+

Camera Offline or Connecting...

- ))} +
+
+ LIVE +
+
+
+ + {/* Detections List */} +

Recent Scans

+ + {detections.length === 0 ? ( +
+

No detections yet...

+
+ ) : ( +
+ {detections.map((d, i) => ( +
+
+
+ {d.plate} +
+
+ {new Date(d.timestamp).toLocaleTimeString()} +
+
+ +
+ ))} +
+ )} + + ) : ( + /* History View */ +
+
+ + setSelectedDate(e.target.value)} + className="bg-slate-900 border border-slate-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-purple-500 outline-none" + /> +
+ +
+ {loadingHistory ? ( +

Loading history...

+ ) : historyLogs.length === 0 ? ( +
+ No records found for {selectedDate} +
+ ) : ( + historyLogs.map((log) => ( +
+
+
+ {log.plateNumber} +
+
+ {new Date(log.timestamp).toLocaleTimeString()} +
+
+ +
+ )) + )} +
)}