Skip to content

Commit 44d317a

Browse files
committed
Attempt fix filter options & optimise
1 parent 0b910b1 commit 44d317a

2 files changed

Lines changed: 27 additions & 19 deletions

File tree

app/Http/Controllers/Api/VesselController.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use App\Models\Vessel;
77
use App\Models\VesselPosition;
88
use App\Services\SanctionsService;
9+
use Carbon\Carbon;
910
use Illuminate\Http\JsonResponse;
1011
use Illuminate\Http\Request;
1112
use Illuminate\Support\Facades\Cache;
@@ -648,7 +649,14 @@ public function infractionsList(Request $request): JsonResponse
648649
->where('vessel_activities.started_at', '>=', $cutoff);
649650

650651
if ($severity && $severity !== 'all') {
651-
$query->where('vessel_activities.severity', $severity);
652+
if ($severity === 'high') {
653+
$query->havingRaw("SUM(CASE WHEN vessel_activities.severity = 'high' THEN 10 WHEN vessel_activities.severity = 'medium' THEN 3 ELSE 1 END) >= 75");
654+
} elseif ($severity === 'medium') {
655+
$query->havingRaw("SUM(CASE WHEN vessel_activities.severity = 'high' THEN 10 WHEN vessel_activities.severity = 'medium' THEN 3 ELSE 1 END) >= 50");
656+
$query->havingRaw("SUM(CASE WHEN vessel_activities.severity = 'high' THEN 10 WHEN vessel_activities.severity = 'medium' THEN 3 ELSE 1 END) < 75");
657+
} elseif ($severity === 'low') {
658+
$query->havingRaw("SUM(CASE WHEN vessel_activities.severity = 'high' THEN 10 WHEN vessel_activities.severity = 'medium' THEN 3 ELSE 1 END) < 50");
659+
}
652660
}
653661

654662
if ($search) {
@@ -660,7 +668,7 @@ public function infractionsList(Request $request): JsonResponse
660668
}
661669

662670
if ($status && $status !== 'all') {
663-
$onlineThreshold = now()->subMinutes(5);
671+
$onlineThreshold = now()->subMinutes(15);
664672
if ($status === 'online') {
665673
$query->where('vessels.last_seen_at', '>=', $onlineThreshold);
666674
} elseif ($status === 'offline') {
@@ -699,6 +707,7 @@ public function infractionsList(Request $request): JsonResponse
699707
'lat' => (float) $vessel->lat,
700708
'lng' => (float) $vessel->lng,
701709
'course' => (float) $vessel->course,
710+
'last_seen_at' => Carbon::parse($vessel->last_seen_at)->toIso8601String(),
702711
'infractions_count' => (int) $vessel->activities_count,
703712
'highest_severity' => $highestSeverity,
704713
'risk_score' => min(100, (int) $vessel->raw_score),

resources/js/Components/InfractionsPanel.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
1+
import { useState, useEffect, useCallback, useRef } from 'react';
22
import { FaEye, FaXmark, FaMagnifyingGlass, FaChevronLeft, FaChevronRight } from 'react-icons/fa6';
33
import axios from 'axios';
44
import L from 'leaflet';
@@ -16,6 +16,7 @@ interface InfractionVessel {
1616
infractions_count: number;
1717
highest_severity: 'low' | 'medium' | 'high';
1818
risk_score: number;
19+
last_seen_at: string;
1920
}
2021

2122
interface InfractionsPanelProps {
@@ -26,7 +27,6 @@ interface InfractionsPanelProps {
2627
onOpen?: () => void;
2728
onClose?: () => void;
2829
hideTrigger?: boolean;
29-
trackedVessels?: MapVessel[];
3030
isCompact?: boolean;
3131
}
3232

@@ -38,7 +38,6 @@ export default function InfractionsPanel({
3838
onOpen,
3939
onClose,
4040
hideTrigger = false,
41-
trackedVessels = [],
4241
isCompact = false,
4342
}: InfractionsPanelProps) {
4443
const [isOpenInternal, setIsOpenInternal] = useState(false);
@@ -47,14 +46,15 @@ export default function InfractionsPanel({
4746
const closePanel = onClose ?? (() => setIsOpenInternal(false));
4847

4948
const [searchQuery, setSearchQuery] = useState('');
50-
const [severityFilter, setSeverityFilter] = useState<'all' | 'low' | 'medium' | 'high'>('all');
49+
const [severityFilter, setSeverityFilter] = useState<'low' | 'medium' | 'high'>('high');
5150
const [statusFilter, setStatusFilter] = useState<'all' | 'online' | 'offline'>('all');
5251
const [vessels, setVessels] = useState<InfractionVessel[]>([]);
5352
const [loading, setLoading] = useState(false);
5453
const [currentPage, setCurrentPage] = useState(1);
5554
const [totalPages, setTotalPages] = useState(1);
5655
const [totalVessels, setTotalVessels] = useState(0);
5756
const [checkingVesselMmsi, setCheckingVesselMmsi] = useState<number | null>(null);
57+
const [currentTime, setCurrentTime] = useState(() => Date.now());
5858
const ITEMS_PER_PAGE = 20;
5959

6060
const searchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -67,6 +67,8 @@ export default function InfractionsPanel({
6767
L.DomEvent.disableScrollPropagation(panelRef.current);
6868
L.DomEvent.disableClickPropagation(panelRef.current);
6969
}
70+
const interval = setInterval(() => setCurrentTime(Date.now()), 60000);
71+
return () => clearInterval(interval);
7072
}, [isOpen]);
7173

7274
const fetchVessels = useCallback(
@@ -84,6 +86,10 @@ export default function InfractionsPanel({
8486
const controller = new AbortController();
8587
abortControllerRef.current = controller;
8688

89+
if (page === 1) {
90+
setVessels([]);
91+
}
92+
8793
try {
8894
const response = await axios.get(`${API_BASE_URL}/vessels/infractions`, {
8995
params: {
@@ -135,20 +141,9 @@ export default function InfractionsPanel({
135141
if (abortControllerRef.current) abortControllerRef.current.abort();
136142
};
137143
}, [searchQuery, severityFilter, statusFilter, isOpen, fetchVessels, currentPage]);
138-
139-
const onlineMmsis = useMemo(() => {
140-
const set = new Set<number>();
141-
trackedVessels.forEach((v) => {
142-
const val = Number(v.mmsi);
143-
if (!isNaN(val)) set.add(val);
144-
});
145-
return set;
146-
}, [trackedVessels]);
147-
148144
const handleVesselClick = async (vessel: InfractionVessel) => {
149145
setCheckingVesselMmsi(vessel.mmsi);
150146
try {
151-
// Artificial delay to match the "intelligence verification" feel of the sanctions panel
152147
await new Promise((resolve) => setTimeout(resolve, 600));
153148

154149
if (onVesselSelect) {
@@ -224,10 +219,11 @@ export default function InfractionsPanel({
224219

225220
<div className="flex flex-col gap-2">
226221
<div className="flex bg-zinc-950 border border-white/20 divide-x divide-white/10 overflow-hidden w-full">
227-
{(['all', 'low', 'medium', 'high'] as const).map((sev) => (
222+
{(['low', 'medium', 'high'] as const).map((sev) => (
228223
<button
229224
key={sev}
230225
onClick={() => {
226+
if (severityFilter !== sev) setVessels([]);
231227
setSeverityFilter(sev);
232228
setCurrentPage(1);
233229
}}
@@ -246,6 +242,7 @@ export default function InfractionsPanel({
246242
<button
247243
key={status}
248244
onClick={() => {
245+
if (statusFilter !== status) setVessels([]);
249246
setStatusFilter(status);
250247
setCurrentPage(1);
251248
}}
@@ -278,7 +275,9 @@ export default function InfractionsPanel({
278275

279276
<div className="space-y-1">
280277
{vessels.map((vessel) => {
281-
const isOnline = onlineMmsis.has(vessel.mmsi);
278+
const isOnline =
279+
new Date(vessel.last_seen_at).getTime() >=
280+
currentTime - 15 * 60 * 1000;
282281
return (
283282
<button
284283
key={vessel.mmsi}

0 commit comments

Comments
 (0)