prisma migration and fix dropdowns
This commit is contained in:
@@ -62,18 +62,25 @@ export function CommandManager({ selectedImei }: CommandManagerProps) {
|
||||
placeholder="Template Name (e.g., Lights On)"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="p-2 text-white rounded-md bg-white/10"
|
||||
className="p-2 text-white rounded-md bg-gray-800 border border-gray-600"
|
||||
/>
|
||||
<select
|
||||
value={type}
|
||||
onChange={(e) => setType(e.target.value as DeviceCommandType)}
|
||||
className="p-2 text-white rounded-md bg-white/10"
|
||||
>
|
||||
<option value="reboot">Reboot Device</option>
|
||||
<option value="lights">Lights On/Off</option>
|
||||
{/* Add other types */}
|
||||
</select>
|
||||
<textarea
|
||||
|
||||
<select
|
||||
value={type} // This value is tied to the 'type' state
|
||||
onChange={(e) => setType(e.target.value as DeviceCommandType)} // This updates the state when you select an option
|
||||
className="p-2 text-white rounded-md bg-gray-800 border border-gray-600"
|
||||
>
|
||||
<option value="reboot" className="bg-gray-800 text-white">Reboot Device</option>
|
||||
<option value="lights" className="bg-gray-800 text-white">Lights On/Off</option>
|
||||
<option value="camera" className="bg-gray-800 text-white">Camera On/Off</option>
|
||||
<option value="sleep" className="bg-gray-800 text-white">Set Sleep Interval</option>
|
||||
<option value="telemetry_sec" className="bg-gray-800 text-white">Set Telemetry Interval</option>
|
||||
<option value="poll_sec" className="bg-gray-800 text-white">Set Poll Interval</option>
|
||||
<option value="wifi" className="bg-gray-800 text-white">Set WiFi Credentials</option>
|
||||
<option value="ring_fence" className="bg-gray-800 text-white">Set Geo-Fence</option>
|
||||
<option value="ota" className="bg-gray-800 text-white">Firmware OTA</option>
|
||||
</select>
|
||||
<textarea
|
||||
placeholder='JSON Payload, e.g., {"on": true}'
|
||||
value={payload}
|
||||
onChange={(e) => setPayload(e.target.value)}
|
||||
@@ -90,32 +97,41 @@ export function CommandManager({ selectedImei }: CommandManagerProps) {
|
||||
</div>
|
||||
|
||||
{/* List of saved templates */}
|
||||
<div className="flex flex-col gap-2 p-4 rounded-md bg-white/10">
|
||||
<h3 className="text-lg font-bold">Saved Commands</h3>
|
||||
{templatesQuery.isLoading && <p>Loading templates...</p>}
|
||||
{templatesQuery.data?.map((template) => (
|
||||
<div key={template.id} className="flex items-center justify-between p-2 rounded-md bg-black/20">
|
||||
<div>
|
||||
<p className="font-semibold">{template.name}</p>
|
||||
<p className="text-sm text-gray-400">Type: {template.type}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => handleSendCommand(template)}
|
||||
className="px-3 py-1 text-white bg-green-600 rounded-md hover:bg-green-700"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
<button
|
||||
onClick={() => deleteTemplateMutation.mutate({ id: template.id })}
|
||||
className="px-3 py-1 text-white bg-red-600 rounded-md hover:bg-red-700"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
// In file: src/app/_components/CommandManager.tsx
|
||||
|
||||
{/* List of saved templates */}
|
||||
<div className="flex flex-col gap-2 p-4 rounded-md bg-white/10">
|
||||
<h3 className="text-lg font-bold">Saved Commands</h3>
|
||||
{templatesQuery.isLoading && <p>Loading templates...</p>}
|
||||
|
||||
{/* ADD THIS CHECK for an empty list */}
|
||||
{templatesQuery.data && templatesQuery.data.length === 0 && (
|
||||
<p className="text-gray-400">No templates saved. Create one on the left!</p>
|
||||
)}
|
||||
|
||||
{templatesQuery.data?.map((template) => (
|
||||
<div key={template.id} className="flex items-center justify-between p-2 rounded-md bg-black/20">
|
||||
<div>
|
||||
<p className="font-semibold">{template.name}</p>
|
||||
<p className="text-sm text-gray-400">Type: {template.type}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => handleSendCommand(template)}
|
||||
className="px-3 py-1 text-white bg-green-600 rounded-md hover:bg-green-700"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
<button
|
||||
onClick={() => deleteTemplateMutation.mutate({ id: template.id })}
|
||||
className="px-3 py-1 text-white bg-red-600 rounded-md hover:bg-red-700"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
// In file: src/components/DeviceMapClient.tsx
|
||||
// In file: src/app/_components/DynamicDeviceMap.tsx
|
||||
"use client";
|
||||
|
||||
// This file now contains your DeviceMap component, ensuring it's always a client component.
|
||||
import { useState } from "react";
|
||||
import { MapContainer, TileLayer, Marker, Polyline, useMapEvents } from "react-leaflet";
|
||||
import { useState, useEffect } from "react";
|
||||
import { MapContainer, TileLayer, Marker, Polyline, useMap, useMapEvents } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
|
||||
// Fix default Leaflet icon issue in React
|
||||
// Fix default Leaflet icon paths
|
||||
delete (L.Icon.Default as any)._getIconUrl;
|
||||
L.Icon.Default.mergeOptions({
|
||||
iconRetinaUrl: '/leaflet/marker-icon-2x.png',
|
||||
iconUrl: '/leaflet/marker-icon.png',
|
||||
shadowUrl: '/leaflet/marker-shadow.png',
|
||||
iconRetinaUrl: '/leaflet/marker-icon-2x.png',
|
||||
iconUrl: '/leaflet/marker-icon.png',
|
||||
shadowUrl: '/leaflet/marker-shadow.png',
|
||||
});
|
||||
|
||||
// Helper component that forces the map to update its view
|
||||
function MapViewUpdater({ center }: { center: L.LatLngExpression }) {
|
||||
const map = useMap();
|
||||
useEffect(() => {
|
||||
// When the 'center' prop changes, fly the map to the new location
|
||||
map.flyTo(center, map.getZoom());
|
||||
}, [center, map]);
|
||||
return null;
|
||||
}
|
||||
|
||||
interface MapProps {
|
||||
interface FenceMapProps {
|
||||
latestGPS?: { lat: number; lng: number };
|
||||
onFenceChange: (fence: Array<{ lat: number; lng: number }>) => void;
|
||||
}
|
||||
|
||||
const FenceMap = ({ latestGPS, onFenceChange }: MapProps) => {
|
||||
const FenceMap = ({ latestGPS, onFenceChange }: FenceMapProps) => {
|
||||
const [fence, setFence] = useState<Array<{ lat: number; lng: number }>>([]);
|
||||
|
||||
useMapEvents({
|
||||
click: (e) => {
|
||||
const newFence = [...fence, { lat: e.latlng.lat, lng: e.latlng.lng }];
|
||||
@@ -31,7 +38,6 @@ const FenceMap = ({ latestGPS, onFenceChange }: MapProps) => {
|
||||
onFenceChange(newFence);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{fence.length > 1 && <Polyline pathOptions={{ color: "red" }} positions={fence} />}
|
||||
@@ -40,13 +46,18 @@ const FenceMap = ({ latestGPS, onFenceChange }: MapProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const DeviceMap = ({ latestGPS, onFenceChange }: { latestGPS?: { lat: number; lng: number }; onFenceChange: (fence: Array<{ lat: number; lng: number }>) => void; }) => {
|
||||
const center = latestGPS ? [latestGPS.lat, latestGPS.lng] : [-37.76, 145.04]; // Default center
|
||||
export const DeviceMap = ({ latestGPS, onFenceChange }: { latestGPS?: { lat: number; lng: number } | null; onFenceChange: (fence: Array<{ lat: number; lng: number }>) => void; }) => {
|
||||
// Use live GPS if available, otherwise default to a reasonable fallback
|
||||
const center: L.LatLngExpression = latestGPS ? [latestGPS.lat, latestGPS.lng] : [-37.76, 145.04];
|
||||
|
||||
// For debugging, let's see what coordinates this component is receiving
|
||||
console.log("DeviceMap received latestGPS:", latestGPS);
|
||||
|
||||
return (
|
||||
<MapContainer center={center} zoom={13} style={{ height: "300px", width: "100%" }}>
|
||||
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
|
||||
<FenceMap latestGPS={latestGPS} onFenceChange={onFenceChange} />
|
||||
<MapViewUpdater center={center} /> {/* This component will handle map movement */}
|
||||
</MapContainer>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -56,14 +56,18 @@ export default function Dashboard() {
|
||||
<p>Loading telemetry...</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold">Map & Geo-Fence</h2>
|
||||
<div className="mt-2 rounded-md overflow-hidden">
|
||||
<DeviceMap latestGPS={telemetry} onFenceChange={setFence} />
|
||||
</div>
|
||||
<p>Geo-fence points: {fence.length}</p>
|
||||
</div>
|
||||
</section>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold">Map & Geo-Fence</h2>
|
||||
<div className="mt-2 h-[300px] rounded-md overflow-hidden bg-gray-800 flex items-center justify-center">
|
||||
{telemetry ? (
|
||||
<DeviceMap latestGPS={telemetry} onFenceChange={setFence} />
|
||||
) : (
|
||||
<p className="text-gray-400">Waiting for GPS data...</p>
|
||||
)}
|
||||
</div>
|
||||
<p>Geo-fence points: {fence.length}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-6">
|
||||
<h2 className="text-xl font-bold">Queued Commands</h2>
|
||||
|
||||
Reference in New Issue
Block a user