Skip to content

Commit 936b7e0

Browse files
Merge pull request #17 from akirachix/develop
Develop
2 parents e9abd1a + dfdca7f commit 936b7e0

File tree

39 files changed

+3457
-1370
lines changed

39 files changed

+3457
-1370
lines changed

biopima/package-lock.json

Lines changed: 654 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

biopima/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
"dependencies": {
1313
"@heroicons/react": "^2.2.0",
1414
"chart.js": "^4.5.0",
15+
"html2canvas-pro": "^1.5.11",
1516
"jest-canvas-mock": "^2.5.2",
17+
"jspdf": "^3.0.3",
1618
"lucide-react": "^0.544.0",
19+
"mqtt": "^5.14.1",
1720
"next": "15.5.3",
1821
"react": "19.1.0",
1922
"react-chartjs-2": "^5.3.0",

biopima/src/app/alerts/page.tsx

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
'use client';
2+
import React, { useState, useMemo, ChangeEvent } from 'react';
3+
import useLiveSensorReadings from '../hooks/useFetchSensorReadings';
4+
import InstitutionLayout from '../shared-components/Sidebar/InstitutionLayout';
5+
6+
interface Alert {
7+
id: string;
8+
type: 'warning' | 'critical';
9+
message: string;
10+
timestamp: Date;
11+
readingId: number;
12+
}
13+
14+
type FilterType = 'all' | 'warning' | 'critical';
15+
type FilterDate = 'all' | 'today' | 'week';
16+
17+
export default function AlertsPage() {
18+
const { sensorReadings } = useLiveSensorReadings();
19+
20+
const allAlerts = useMemo<Alert[]>(() => {
21+
return sensorReadings
22+
.map((reading) => {
23+
const alerts: Alert[] = [];
24+
const createdAt = new Date(reading.created_at);
25+
const temp = parseFloat(reading.temperature_level);
26+
const pressure = parseFloat(reading.pressure_level);
27+
const methane = parseFloat(reading.methane_level);
28+
29+
if (!isNaN(temp)) {
30+
if (temp < 35) {
31+
alerts.push({
32+
id: `temp-low-${reading.sensor_readings_id}`,
33+
type: 'warning',
34+
message: 'Temperature too low',
35+
timestamp: createdAt,
36+
readingId: reading.sensor_readings_id,
37+
});
38+
} else if (temp > 37) {
39+
alerts.push({
40+
id: `temp-high-${reading.sensor_readings_id}`,
41+
type: 'warning',
42+
message: 'Temperature too high',
43+
timestamp: createdAt,
44+
readingId: reading.sensor_readings_id,
45+
});
46+
}
47+
}
48+
49+
if (!isNaN(pressure)) {
50+
if (pressure < 8) {
51+
alerts.push({
52+
id: `pressure-low-${reading.sensor_readings_id}`,
53+
type: 'warning',
54+
message: 'Pressure too low',
55+
timestamp: createdAt,
56+
readingId: reading.sensor_readings_id,
57+
});
58+
} else if (pressure > 15) {
59+
alerts.push({
60+
id: `pressure-high-${reading.sensor_readings_id}`,
61+
type: 'warning',
62+
message: 'Pressure too high',
63+
timestamp: createdAt,
64+
readingId: reading.sensor_readings_id,
65+
});
66+
}
67+
}
68+
69+
if (!isNaN(methane) && methane > 2.0) {
70+
alerts.push({
71+
id: `methane-high-${reading.sensor_readings_id}`,
72+
type: 'critical',
73+
message: 'Methane levels too high',
74+
timestamp: createdAt,
75+
readingId: reading.sensor_readings_id,
76+
});
77+
}
78+
79+
return alerts;
80+
})
81+
.flat()
82+
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
83+
}, [sensorReadings]);
84+
85+
const [filterType, setFilterType] = useState<FilterType>('all');
86+
const [filterDate, setFilterDate] = useState<FilterDate>('today');
87+
const [currentPage, setCurrentPage] = useState(1);
88+
89+
const handleTypeChange = (e: ChangeEvent<HTMLSelectElement>) => {
90+
const value = e.target.value;
91+
if (value === 'all' || value === 'warning' || value === 'critical') {
92+
setFilterType(value);
93+
setCurrentPage(1);
94+
}
95+
};
96+
97+
const handleDateChange = (e: ChangeEvent<HTMLSelectElement>) => {
98+
const value = e.target.value;
99+
if (value === 'all' || value === 'today' || value === 'week') {
100+
setFilterDate(value);
101+
setCurrentPage(1);
102+
}
103+
};
104+
105+
const filteredAlerts = useMemo(() => {
106+
return allAlerts.filter((alert) => {
107+
if (filterType !== 'all' && alert.type !== filterType) return false;
108+
if (filterDate === 'today') {
109+
const today = new Date();
110+
return (
111+
alert.timestamp.getDate() === today.getDate() &&
112+
alert.timestamp.getMonth() === today.getMonth() &&
113+
alert.timestamp.getFullYear() === today.getFullYear()
114+
);
115+
}
116+
if (filterDate === 'week') {
117+
const oneWeekAgo = new Date();
118+
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
119+
return alert.timestamp >= oneWeekAgo;
120+
}
121+
return true;
122+
});
123+
}, [allAlerts, filterType, filterDate]);
124+
125+
const itemsPerPage = 8;
126+
const totalPages = Math.max(1, Math.ceil(filteredAlerts.length / itemsPerPage));
127+
const paginatedAlerts = filteredAlerts.slice(
128+
(currentPage - 1) * itemsPerPage,
129+
currentPage * itemsPerPage
130+
);
131+
132+
const handlePrevPage = () => {
133+
if (currentPage > 1) setCurrentPage(currentPage - 1);
134+
};
135+
136+
const handleNextPage = () => {
137+
if (currentPage < totalPages) setCurrentPage(currentPage + 1);
138+
};
139+
140+
return (
141+
<InstitutionLayout>
142+
<div className="flex min-h-screen bg-white px-4">
143+
<main className="flex-1 min-h-screen py-10">
144+
<div className="w-full flex flex-col items-center">
145+
146+
<header className="mb-6 w-full px-8" style={{ maxWidth: '100%' }}>
147+
<h1 className="text-4xl font-bold text-green-800">Alerts</h1>
148+
<p className="text-green-700 mt-2">A historical log of all systems alerts and events</p>
149+
</header>
150+
<section className="w-full flex justify-center">
151+
<div
152+
className="rounded-xl mx-auto w-full"
153+
style={{
154+
maxWidth: '100%',
155+
backgroundColor: '#014D00',
156+
border: '2px solid #68c080',
157+
minHeight: '420px',
158+
boxShadow: '0 2px 14px rgba(14,76,2,0.06)',
159+
borderRadius: '20px',
160+
overflow: 'hidden',
161+
}}
162+
>
163+
<div className="flex gap-6 mb-8 px-8 pt-8">
164+
<select
165+
value={filterType}
166+
onChange={handleTypeChange}
167+
className="rounded-lg px-4 py-2 border"
168+
style={{
169+
backgroundColor: 'white',
170+
borderColor: '#056d05',
171+
color: '#056d05',
172+
fontWeight: 600,
173+
minWidth: 120,
174+
}}
175+
>
176+
<option value="all">All Types</option>
177+
<option value="warning">Warning</option>
178+
<option value="critical">Critical</option>
179+
</select>
180+
<select
181+
value={filterDate}
182+
onChange={handleDateChange}
183+
className="rounded-lg px-4 py-2 border"
184+
style={{
185+
backgroundColor: 'white',
186+
borderColor: '#056d05',
187+
color: '#056d05',
188+
fontWeight: 600,
189+
minWidth: 120,
190+
}}
191+
>
192+
<option value="today">Today</option>
193+
<option value="week">Last 7 Days</option>
194+
<option value="all">All Time</option>
195+
</select>
196+
</div>
197+
<table className="w-full text-left text-white" style={{ fontSize: 15 }}>
198+
<thead>
199+
<tr className="border-b" style={{ borderColor: '#68c080' }}>
200+
<th className="py-4 px-8">Types</th>
201+
<th className="py-4">Message</th>
202+
<th className="py-4 text-right px-8">Timestamp</th>
203+
</tr>
204+
</thead>
205+
<tbody>
206+
{paginatedAlerts.length > 0 ? (
207+
paginatedAlerts.map((alert) => (
208+
<tr
209+
key={alert.id}
210+
className="border-b"
211+
style={{ borderColor: '#68c080' }}
212+
>
213+
<td className="py-4 px-8">
214+
<span
215+
style={{
216+
backgroundColor: 'white',
217+
borderRadius: 9999,
218+
padding: '4px 20px',
219+
display: 'inline-block',
220+
minWidth: 94,
221+
fontSize: 13,
222+
fontWeight: 600,
223+
textAlign: 'center',
224+
boxShadow:
225+
alert.type === 'warning'
226+
? '0 0 5px #cfdba1'
227+
: '0 0 5px #ffd2d2',
228+
textTransform: 'capitalize',
229+
color: alert.type === 'warning' ? '#3c4603' : '#8a0c0c',
230+
userSelect: 'none',
231+
}}
232+
>
233+
{alert.type}
234+
</span>
235+
</td>
236+
<td className="py-4">{alert.message}</td>
237+
<td className="py-4 px-8 text-right" style={{ opacity: 0.78 }}>
238+
{alert.timestamp.toLocaleString()}
239+
</td>
240+
</tr>
241+
))
242+
) : (
243+
<tr>
244+
<td colSpan={3} className="py-12 text-center text-gray-300">
245+
No alerts found
246+
</td>
247+
</tr>
248+
)}
249+
</tbody>
250+
</table>
251+
<div
252+
className="mt-6 flex justify-center items-center pb-6 px-8"
253+
style={{ color: 'white', fontSize: 15 }}
254+
>
255+
<div className="flex gap-2">
256+
<button
257+
onClick={handlePrevPage}
258+
disabled={currentPage === 1}
259+
className="px-6 py-2 rounded-md cursor-pointer select-none font-semibold"
260+
style={{
261+
backgroundColor: 'white',
262+
color: '#0a4602',
263+
borderRadius: 8,
264+
fontWeight: 600,
265+
opacity: currentPage === 1 ? 0.5 : 1,
266+
boxShadow: '0 1px 5px 0 rgba(0,0,0,0.07)',
267+
pointerEvents: currentPage === 1 ? 'none' : 'auto',
268+
border: 'none',
269+
}}
270+
aria-disabled={currentPage === 1}
271+
>
272+
Previous
273+
</button>
274+
<button
275+
onClick={handleNextPage}
276+
disabled={currentPage >= totalPages}
277+
className="px-6 py-2 rounded-md cursor-pointer select-none font-semibold"
278+
style={{
279+
backgroundColor: 'white',
280+
color: '#0a4602',
281+
borderRadius: 8,
282+
fontWeight: 600,
283+
opacity: currentPage >= totalPages ? 0.5 : 1,
284+
boxShadow: '0 1px 5px 0 rgba(0,0,0,0.07)',
285+
pointerEvents: currentPage >= totalPages ? 'none' : 'auto',
286+
border: 'none',
287+
}}
288+
aria-disabled={currentPage >= totalPages}
289+
>
290+
Next
291+
</button>
292+
</div>
293+
</div>
294+
</div>
295+
</section>
296+
</div>
297+
</main>
298+
</div>
299+
</InstitutionLayout>
300+
);
301+
}

biopima/src/app/api/forgot-password/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export async function POST(request: Request) {
77
});
88
}
99
try {
10-
const response = await fetch(`${baseUrl}/forgot-password/`, {
10+
const response = await fetch(`${baseUrl}/api/forgot-password/`, {
1111
method: "POST",
1212
headers: { "Content-Type": "application/json" },
1313
body: JSON.stringify({ email }),

biopima/src/app/api/reset-password/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export async function POST(request: Request) {
1111
});
1212
}
1313
try {
14-
const response = await fetch(`${baseUrl}/reset-password/`, {
14+
const response = await fetch(`${baseUrl}/api/reset-password/`, {
1515
method: "POST",
1616
headers: {
1717
"Content-Type": "application/json",

biopima/src/app/api/verify-code/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export async function POST(request: Request) {
1111
);
1212
}
1313

14-
const response = await fetch(`${baseUrl}/verify-code/`, {
14+
const response = await fetch(`${baseUrl}/api/verify-code/`, {
1515
method: "POST",
1616
headers: { "Content-Type": "application/json" },
1717
body: JSON.stringify({ email, otp }),

biopima/src/app/chooserole/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default function RolePage() {
99
if (role === "institution") {
1010
router.push("/login?role=institution");
1111
} else {
12-
router.push("/signup");
12+
router.push("/login");
1313
}
1414
};
1515

0 commit comments

Comments
 (0)