diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 933ec7c..e3fd58d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -6,6 +6,15 @@ model Device { @@map("devices") } +model CommandTemplate { + id Int @id @default(autoincrement()) + name String // e.g., "Lights On", "Reboot Device" + type String // e.g., "lights", "reboot" + payload Json // The JSON payload, e.g., {"on": true} + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + model Telemetry { id String @id @default(uuid()) deviceId String diff --git a/public/leaflet/layers-2x.png b/public/leaflet/layers-2x.png new file mode 100644 index 0000000..200c333 Binary files /dev/null and b/public/leaflet/layers-2x.png differ diff --git a/public/leaflet/layers.png b/public/leaflet/layers.png new file mode 100644 index 0000000..1a72e57 Binary files /dev/null and b/public/leaflet/layers.png differ diff --git a/public/leaflet/marker-icon-2x.png b/public/leaflet/marker-icon-2x.png new file mode 100644 index 0000000..88f9e50 Binary files /dev/null and b/public/leaflet/marker-icon-2x.png differ diff --git a/public/leaflet/marker-icon.png b/public/leaflet/marker-icon.png new file mode 100644 index 0000000..950edf2 Binary files /dev/null and b/public/leaflet/marker-icon.png differ diff --git a/public/leaflet/marker-shadow.png b/public/leaflet/marker-shadow.png new file mode 100644 index 0000000..9fd2979 Binary files /dev/null and b/public/leaflet/marker-shadow.png differ diff --git a/src/app/_components/CommandManager.tsx b/src/app/_components/CommandManager.tsx new file mode 100644 index 0000000..816b90d --- /dev/null +++ b/src/app/_components/CommandManager.tsx @@ -0,0 +1,121 @@ +// In file: src/app/_components/CommandManager.tsx +"use client"; + +import { useState } from "react"; +import { api } from "~/trpc/react"; +import { postCommand, type DeviceCommandType } from "~/lib/api"; // Your Laravel API helper + +interface CommandManagerProps { + selectedImei: string; +} + +export function CommandManager({ selectedImei }: CommandManagerProps) { + const [name, setName] = useState(""); + const [type, setType] = useState("reboot"); + const [payload, setPayload] = useState(""); // Stored as a string for the textarea + + const utils = api.useUtils(); + const templatesQuery = api.commandTemplate.getAll.useQuery(); + + const createTemplateMutation = api.commandTemplate.create.useMutation({ + onSuccess: () => { + utils.commandTemplate.getAll.invalidate(); // Invalidate cache to refetch list + setName(""); + setPayload(""); + }, + }); + + const deleteTemplateMutation = api.commandTemplate.delete.useMutation({ + onSuccess: () => { + utils.commandTemplate.getAll.invalidate(); // Refetch list after deleting + }, + }); + + const handleCreateTemplate = () => { + try { + const parsedPayload = payload ? JSON.parse(payload) : {}; + createTemplateMutation.mutate({ name, type, payload: parsedPayload }); + } catch { + alert("Invalid JSON in payload!"); + } + }; + + const handleSendCommand = async (template: { type: string; payload: any }) => { + try { + await postCommand(selectedImei, { + type: template.type as DeviceCommandType, + payload: template.payload, + }); + alert(`Command "${template.type}" sent to ${selectedImei}!`); + } catch (e) { + alert(`Failed to send command: ${e instanceof Error ? e.message : "Unknown error"}`); + } + }; + + return ( +
+ {/* Form to add new templates */} +
+

Create New Command Template

+ setName(e.target.value)} + className="p-2 text-white rounded-md bg-white/10" + /> + +