Implement active Location Sharing Stopped/Ended state with red dot pin

This commit is contained in:
Shanmuga Krishnan S M
2026-07-27 13:51:41 +05:30
parent def3a7c9ce
commit a3e1f1e826
4 changed files with 42 additions and 9 deletions

View File

@@ -37,6 +37,20 @@ public class BusLocationController {
return ResponseEntity.ok(Map.of("status", "success", "message", "Location updated successfully")); return ResponseEntity.ok(Map.of("status", "success", "message", "Location updated successfully"));
} }
@PostMapping("/{routeNumber}/stop")
public ResponseEntity<Map<String, String>> stopLocation(
@PathVariable String routeNumber,
@RequestBody Map<String, String> body) {
String pin = body != null ? body.get("pin") : null;
if (!REQUIRED_PIN.equals(pin)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("error", "Invalid driver PIN"));
}
busLocationService.stopLocation(routeNumber);
return ResponseEntity.ok(Map.of("status", "success", "message", "Location sharing stopped"));
}
@GetMapping @GetMapping
public ResponseEntity<List<BusLocation>> getActiveLocations() { public ResponseEntity<List<BusLocation>> getActiveLocations() {
return ResponseEntity.ok(busLocationService.getActiveLocations()); return ResponseEntity.ok(busLocationService.getActiveLocations());

View File

@@ -13,4 +13,5 @@ public class BusLocation {
private Double latitude; private Double latitude;
private Double longitude; private Double longitude;
private Instant lastUpdated; private Instant lastUpdated;
private boolean stopped = false;
} }

View File

@@ -19,10 +19,19 @@ public class BusLocationService {
routeNumber, routeNumber,
latitude, latitude,
longitude, longitude,
Instant.now() Instant.now(),
false
)); ));
} }
public void stopLocation(String routeNumber) {
BusLocation loc = locationCache.get(routeNumber);
if (loc != null) {
loc.setStopped(true);
loc.setLastUpdated(Instant.now());
}
}
public BusLocation getLocation(String routeNumber) { public BusLocation getLocation(String routeNumber) {
BusLocation loc = locationCache.get(routeNumber); BusLocation loc = locationCache.get(routeNumber);
if (loc != null) { if (loc != null) {

View File

@@ -56,7 +56,7 @@ const liveBusIcon = L.divIcon({
}); });
const stoppedBusIcon = L.divIcon({ const stoppedBusIcon = L.divIcon({
html: `<div class="relative flex items-center justify-center w-9 h-9 rounded-full bg-slate-500 border-2 border-white shadow-lg text-white text-sm font-bold">🚌</div>`, html: `<div class="relative flex items-center justify-center w-9 h-9 rounded-full bg-red-500 border-2 border-white shadow-lg text-white text-sm font-bold">🚌</div>`,
className: '', className: '',
iconSize: [36, 36], iconSize: [36, 36],
iconAnchor: [18, 18], iconAnchor: [18, 18],
@@ -81,7 +81,7 @@ interface BusRouteMapProps {
const DEFAULT_CENTER = [13.0118, 80.0214]; // RIT Campus default const DEFAULT_CENTER = [13.0118, 80.0214]; // RIT Campus default
export default function BusRouteMap({ selectedRoute, allRoutes }: BusRouteMapProps) { export default function BusRouteMap({ selectedRoute, allRoutes }: BusRouteMapProps) {
const [allLiveLocations, setAllLiveLocations] = useState<Record<string, { latitude: number; longitude: number; lastUpdated: string }>>({}); const [allLiveLocations, setAllLiveLocations] = useState<Record<string, { latitude: number; longitude: number; lastUpdated: string; stopped: boolean }>>({});
useEffect(() => { useEffect(() => {
const fetchLiveLocations = () => { const fetchLiveLocations = () => {
@@ -90,12 +90,17 @@ export default function BusRouteMap({ selectedRoute, allRoutes }: BusRouteMapPro
if (res.ok) return res.json(); if (res.ok) return res.json();
return []; return [];
}) })
.then((data: Array<{ routeNumber: string; latitude: number; longitude: number; lastUpdated: string }>) => { .then((data: Array<{ routeNumber: string; latitude: number; longitude: number; lastUpdated: string; stopped: boolean }>) => {
if (Array.isArray(data)) { if (Array.isArray(data)) {
const locMap: Record<string, { latitude: number; longitude: number; lastUpdated: string }> = {}; const locMap: Record<string, { latitude: number; longitude: number; lastUpdated: string; stopped: boolean }> = {};
data.forEach((item) => { data.forEach((item) => {
if (item.routeNumber && item.latitude && item.longitude) { if (item.routeNumber && item.latitude && item.longitude) {
locMap[item.routeNumber] = { latitude: item.latitude, longitude: item.longitude, lastUpdated: item.lastUpdated }; locMap[item.routeNumber] = {
latitude: item.latitude,
longitude: item.longitude,
lastUpdated: item.lastUpdated,
stopped: item.stopped
};
} }
}); });
setAllLiveLocations(locMap); setAllLiveLocations(locMap);
@@ -180,7 +185,7 @@ export default function BusRouteMap({ selectedRoute, allRoutes }: BusRouteMapPro
{Object.entries(allLiveLocations).map(([rNum, loc]) => { {Object.entries(allLiveLocations).map(([rNum, loc]) => {
if (selectedRoute && selectedRoute.number !== rNum) return null; if (selectedRoute && selectedRoute.number !== rNum) return null;
const isStale = loc.lastUpdated ? (new Date().getTime() - new Date(loc.lastUpdated).getTime() > 120000) : false; const isStale = loc.stopped || (loc.lastUpdated ? (new Date().getTime() - new Date(loc.lastUpdated).getTime() > 120000) : false);
return ( return (
<Marker <Marker
@@ -190,7 +195,11 @@ export default function BusRouteMap({ selectedRoute, allRoutes }: BusRouteMapPro
> >
<Popup> <Popup>
<div className="p-1 font-sans text-center"> <div className="p-1 font-sans text-center">
{isStale ? ( {loc.stopped ? (
<span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-red-100 text-red-800 text-[10px] font-bold">
LOCATION SHARING ENDED
</span>
) : isStale ? (
<span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-slate-100 text-slate-800 text-[10px] font-bold"> <span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-slate-100 text-slate-800 text-[10px] font-bold">
LOCATION SHARING STOPPED LOCATION SHARING STOPPED
</span> </span>
@@ -201,7 +210,7 @@ export default function BusRouteMap({ selectedRoute, allRoutes }: BusRouteMapPro
)} )}
<h4 className="font-bold text-slate-800 text-xs mt-1">Bus {rNum}</h4> <h4 className="font-bold text-slate-800 text-xs mt-1">Bus {rNum}</h4>
<p className="text-[10px] text-slate-500 mt-0.5"> <p className="text-[10px] text-slate-500 mt-0.5">
{isStale ? "Last known location" : "Broadcasting live coordinates"} {loc.stopped ? "Sharing ended by driver" : isStale ? "Last known location" : "Broadcasting live coordinates"}
</p> </p>
</div> </div>
</Popup> </Popup>