This commit is contained in:
natib21
2026-07-07 07:55:15 +00:00
parent 75427ea63a
commit 66167e8ac5

View File

@@ -23,7 +23,7 @@ import {
Marker,
useMap,
} from "@vis.gl/react-google-maps";
import { Activity, Plus, Radio, Trash2 } from "lucide-react";
import { Activity, Pencil, Plus, Radio, Trash2 } from "lucide-react";
import Breadcrumbs from "@/components/ui/Breadcrumbs";
import { useToast } from "@/hooks/use-toast";
import { vehiclesService } from "@/services/vehicles.service";
@@ -97,9 +97,21 @@ export function TrackingPage() {
const { toast } = useToast();
const qc = useQueryClient();
const [selectedId, setSelectedId] = useState<string | null>(null);
const [registerOpen, setRegisterOpen] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [editDevice, setEditDevice] = useState<GpsDevice | null>(null);
const [form, setForm] = useState({ imei: "", name: "", vehicleId: "" });
const openRegister = () => {
setEditDevice(null);
setForm({ imei: "", name: "", vehicleId: "" });
setModalOpen(true);
};
const openEdit = (d: GpsDevice) => {
setEditDevice(d);
setForm({ imei: d.imei, name: d.name ?? "", vehicleId: d.vehicleId ?? "" });
setModalOpen(true);
};
// Poll every 10s so the map tracks live movement.
const { data: devices = [] } = useQuery({
queryKey: ["gps", "devices"],
@@ -155,23 +167,29 @@ export function TrackingPage() {
} as google.maps.Symbol;
};
const registerMutation = useMutation({
const saveMutation = useMutation({
mutationFn: () =>
gpsTrackingService.register({
imei: form.imei.trim(),
name: form.name.trim() || undefined,
vehicleId: form.vehicleId || null,
}),
editDevice
? gpsTrackingService.update(editDevice.id, {
name: form.name.trim() || undefined,
vehicleId: form.vehicleId || null,
})
: gpsTrackingService.register({
imei: form.imei.trim(),
name: form.name.trim() || undefined,
vehicleId: form.vehicleId || null,
}),
onSuccess: () => {
toast({ title: "Tracker registered" });
setRegisterOpen(false);
toast({ title: editDevice ? "Tracker updated" : "Tracker registered" });
setModalOpen(false);
setEditDevice(null);
setForm({ imei: "", name: "", vehicleId: "" });
void qc.invalidateQueries({ queryKey: ["gps", "devices"] });
},
onError: (err: unknown) => {
const description =
(err as { response?: { data?: { message?: string } } })?.response?.data?.message ?? "Failed";
toast({ title: "Registration failed", description, variant: "destructive" });
toast({ title: editDevice ? "Update failed" : "Registration failed", description, variant: "destructive" });
},
});
@@ -204,7 +222,7 @@ export function TrackingPage() {
<Text fw={700} size="xl">Real-Time Vehicle Tracking</Text>
<Text c="dimmed" size="sm">Live GPS positions from GT06 trackers</Text>
</div>
<Button leftSection={<Plus size={16} />} color="edr-green" onClick={() => setRegisterOpen(true)}>
<Button leftSection={<Plus size={16} />} color="edr-green" onClick={openRegister}>
Register tracker
</Button>
</Group>
@@ -327,7 +345,17 @@ export function TrackingPage() {
</Stack>
</Table.Td>
<Table.Td align="right">
<Badge color={d.online ? "edr-green" : "gray"} size="sm">{d.online ? "Live" : "Offline"}</Badge>
<Group gap={6} justify="flex-end" wrap="nowrap">
<Badge color={d.online ? "edr-green" : "gray"} size="sm">{d.online ? "Live" : "Offline"}</Badge>
<ActionIcon
variant="subtle"
size="sm"
aria-label="Edit tracker"
onClick={(e) => { e.stopPropagation(); openEdit(d); }}
>
<Pencil size={15} />
</ActionIcon>
</Group>
</Table.Td>
</Table.Tr>
))}
@@ -347,13 +375,20 @@ export function TrackingPage() {
</Grid.Col>
</Grid>
{/* Register modal */}
<Modal opened={registerOpen} onClose={() => setRegisterOpen(false)} title="Register GPS tracker" radius="lg" centered>
{/* Register / edit modal */}
<Modal
opened={modalOpen}
onClose={() => setModalOpen(false)}
title={editDevice ? "Edit GPS tracker" : "Register GPS tracker"}
radius="lg"
centered
>
<Stack gap="md">
<TextInput
label="IMEI"
placeholder="15-digit device IMEI"
required
disabled={Boolean(editDevice)}
value={form.imei}
onChange={(e) => setForm({ ...form, imei: e.currentTarget.value })}
/>
@@ -373,13 +408,13 @@ export function TrackingPage() {
clearable
/>
<Group justify="flex-end">
<Button variant="default" onClick={() => setRegisterOpen(false)}>Cancel</Button>
<Button variant="default" onClick={() => setModalOpen(false)}>Cancel</Button>
<Button
loading={registerMutation.isPending}
loading={saveMutation.isPending}
disabled={!form.imei.trim()}
onClick={() => registerMutation.mutate()}
onClick={() => saveMutation.mutate()}
>
Register
{editDevice ? "Save" : "Register"}
</Button>
</Group>
</Stack>