Add Historico
This commit is contained in:
@@ -45,6 +45,37 @@ 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)
|
// Detection Endpoint (from Python)
|
||||||
app.post('/api/detect', async (req, res) => {
|
app.post('/api/detect', async (req, res) => {
|
||||||
const { plate_number } = req.body;
|
const { plate_number } = req.body;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import io from 'socket.io-client'
|
import io from 'socket.io-client'
|
||||||
import axios from 'axios'
|
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
|
// Env var logic for Vite
|
||||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000';
|
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000';
|
||||||
@@ -14,6 +14,12 @@ function App() {
|
|||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [newPlate, setNewPlate] = useState({ number: '', owner: '' });
|
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) => {
|
const handleRegister = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
try {
|
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 StatusBadge = ({ status }) => {
|
||||||
const colors = {
|
const colors = {
|
||||||
GRANTED: 'bg-green-500/20 text-green-400 border-green-500/50',
|
GRANTED: 'bg-green-500/20 text-green-400 border-green-500/50',
|
||||||
@@ -142,15 +168,41 @@ function App() {
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
|
||||||
{/* Live Detections Feed */}
|
{/* Main Feed Section (Live / History) */}
|
||||||
<div className="lg:col-span-2 space-y-6">
|
<div className="lg:col-span-2 space-y-6">
|
||||||
<h2 className="text-xl font-semibold flex items-center space-x-2">
|
<h2 className="text-xl font-semibold flex items-center justify-between">
|
||||||
<Clock className="text-blue-400" />
|
<div className="flex items-center space-x-2">
|
||||||
<span>Live Detections</span>
|
{viewMode === 'live' ? <Clock className="text-blue-400" /> : <Calendar className="text-purple-400" />}
|
||||||
|
<span>{viewMode === 'live' ? 'Live Detections' : 'History Log'}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Toggle Switch */}
|
||||||
|
<div className="flex bg-slate-800 rounded-lg p-1 border border-slate-700">
|
||||||
|
<button
|
||||||
|
onClick={() => setViewMode('live')}
|
||||||
|
className={`px-4 py-1 rounded-md text-sm font-medium transition-all ${viewMode === 'live'
|
||||||
|
? 'bg-blue-600 text-white shadow-lg'
|
||||||
|
: 'text-slate-400 hover:text-slate-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Live
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setViewMode('history')}
|
||||||
|
className={`px-4 py-1 rounded-md text-sm font-medium transition-all ${viewMode === 'history'
|
||||||
|
? 'bg-purple-600 text-white shadow-lg'
|
||||||
|
: 'text-slate-400 hover:text-slate-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
History
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="bg-slate-800/50 rounded-2xl p-6 border border-slate-700/50 backdrop-blur-sm min-h-[400px]">
|
<div className="bg-slate-800/50 rounded-2xl p-6 border border-slate-700/50 backdrop-blur-sm min-h-[400px]">
|
||||||
|
|
||||||
|
{viewMode === 'live' ? (
|
||||||
|
<>
|
||||||
{/* Video Feed */}
|
{/* Video Feed */}
|
||||||
<div className="mb-6 rounded-xl overflow-hidden bg-black aspect-video relative border border-slate-700 shadow-lg">
|
<div className="mb-6 rounded-xl overflow-hidden bg-black aspect-video relative border border-slate-700 shadow-lg">
|
||||||
<img
|
<img
|
||||||
@@ -196,6 +248,45 @@ function App() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
/* History View */
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center space-x-4 bg-slate-800 p-4 rounded-xl border border-slate-700">
|
||||||
|
<label className="text-slate-400 text-sm font-medium">Select Date:</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={selectedDate}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{loadingHistory ? (
|
||||||
|
<p className="text-center text-slate-500 py-8">Loading history...</p>
|
||||||
|
) : historyLogs.length === 0 ? (
|
||||||
|
<div className="text-center text-slate-500 py-8">
|
||||||
|
No records found for {selectedDate}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
historyLogs.map((log) => (
|
||||||
|
<div key={log.id} className="flex items-center justify-between p-4 bg-slate-800 border border-slate-700 rounded-xl">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="p-2 bg-slate-700 rounded-lg font-mono text-xl tracking-wider font-bold">
|
||||||
|
{log.plateNumber}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-slate-400">
|
||||||
|
{new Date(log.timestamp).toLocaleTimeString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<StatusBadge status={log.accessStatus} />
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user