import { createFileRoute } from "@tanstack/react-router"; import { useState } from "react"; import { CommandSubmitTemplate } from "@/template/command-submit-template"; import { CommandRegistryForm, type CommandRegistryFormData } from "@/components/forms/command-registry-form"; import { useQueryData } from "@/hooks/useQueryData"; import { useMutationData } from "@/hooks/useMutationData"; import { API_ENDPOINTS, BASE_URL } from "@/config/api"; import type { ColumnDef } from "@tanstack/react-table"; import { toast } from "sonner"; import { Check, X, Edit2, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import type { ShellCommandData } from "@/components/forms/command-form"; interface CommandRegistry { id: number; commandName: string; description?: string; commandContent: string; qoS: 0 | 1 | 2; isRetained: boolean; createdAt?: string; updatedAt?: string; } export const Route = createFileRoute("/_authenticated/command/")({ head: () => ({ meta: [{ title: "Gửi lệnh từ xa" }] }), component: CommandPage, }); function CommandPage() { const [isDialogOpen, setIsDialogOpen] = useState(false); const [selectedCommand, setSelectedCommand] = useState(null); const [table, setTable] = useState(); // Fetch commands const { data: commands = [], isLoading } = useQueryData({ queryKey: ["commands"], url: API_ENDPOINTS.COMMAND.GET_COMMANDS, }); // Fetch rooms const { data: roomData } = useQueryData({ queryKey: ["rooms"], url: API_ENDPOINTS.DEVICE_COMM.GET_ROOM_LIST, }); const commandList: CommandRegistry[] = Array.isArray(commands) ? commands.map((cmd: any) => ({ ...cmd, qoS: cmd.qoS ?? 0, isRetained: cmd.isRetained ?? false, })) : commands ? [{ ...commands, qoS: commands.qoS ?? 0, isRetained: commands.isRetained ?? false, }] : []; // Add command mutation const addCommandMutation = useMutationData({ url: API_ENDPOINTS.COMMAND.ADD_COMMAND, method: "POST", invalidate: [["commands"]], onSuccess: () => toast.success("Thêm lệnh thành công!"), onError: (error) => { console.error("Add command error:", error); toast.error("Thêm lệnh thất bại!"); }, }); // Update command mutation const updateCommandMutation = useMutationData({ url: "", method: "POST", invalidate: [["commands"]], onSuccess: () => toast.success("Cập nhật lệnh thành công!"), onError: (error) => { console.error("Update command error:", error); toast.error("Cập nhật lệnh thất bại!"); }, }); // Delete command mutation const deleteCommandMutation = useMutationData({ url: "", method: "DELETE", invalidate: [["commands"]], onSuccess: () => toast.success("Xóa lệnh thành công!"), onError: (error) => { console.error("Delete command error:", error); toast.error("Xóa lệnh thất bại!"); }, }); // Execute command mutation const executeCommandMutation = useMutationData<{ commandIds?: number[]; command?: ShellCommandData; }>({ url: "", method: "POST", onSuccess: () => toast.success("Gửi yêu cầu thực thi lệnh thành công!"), onError: (error) => { console.error("Execute command error:", error); toast.error("Gửi yêu cầu thực thi thất bại!"); }, }); // Columns for command table const columns: ColumnDef[] = [ { accessorKey: "commandName", header: "Tên lệnh", cell: ({ getValue }) => ( {getValue() as string} ), }, { accessorKey: "description", header: "Mô tả", cell: ({ getValue }) => ( {(getValue() as string) || "-"} ), }, { accessorKey: "commandContent", header: "Nội dung lệnh", cell: ({ getValue }) => ( {(getValue() as string).substring(0, 100)}... ), }, { accessorKey: "qoS", header: "QoS", cell: ({ getValue }) => { const qos = getValue() as number | undefined; const qosValue = qos !== undefined ? qos : 0; const colors = { 0: "text-blue-600", 1: "text-amber-600", 2: "text-red-600", }; return ( {qosValue} ); }, }, { accessorKey: "isRetained", header: "Lưu trữ", cell: ({ getValue }) => { const retained = getValue() as boolean; return retained ? (
) : (
Không
); }, }, { id: "select", header: () =>
Chọn để thực thi
, cell: ({ row }) => ( ), enableSorting: false, enableHiding: false, }, { id: "actions", header: () =>
Hành động
, cell: ({ row }) => (
), enableSorting: false, enableHiding: false, }, ]; // Handle form submit const handleFormSubmit = async (data: CommandRegistryFormData) => { if (selectedCommand) { // Update await updateCommandMutation.mutateAsync({ url: BASE_URL + API_ENDPOINTS.COMMAND.UPDATE_COMMAND(selectedCommand.id), data, }); } else { // Add await addCommandMutation.mutateAsync({ data, }); } setIsDialogOpen(false); setSelectedCommand(null); }; // Handle delete const handleDeleteCommand = async (commandId: number) => { if (!confirm("Bạn có chắc muốn xóa lệnh này?")) return; try { await deleteCommandMutation.mutateAsync({ url: API_ENDPOINTS.COMMAND.DELETE_COMMAND(commandId), data: null, }); } catch (error) { console.error("Delete error:", error); } }; // Handle execute commands from list const handleExecuteSelected = async (targets: string[]) => { if (!table) { toast.error("Không thể lấy thông tin bảng!"); return; } const selectedRows = table.getSelectedRowModel().rows; if (selectedRows.length === 0) { toast.error("Vui lòng chọn ít nhất một lệnh để thực thi!"); return; } try { for (const target of targets) { for (const row of selectedRows) { // API expects PascalCase directly const apiData = { Command: row.original.commandContent, QoS: row.original.qoS, IsRetained: row.original.isRetained, }; console.log("[DEBUG] Sending to:", target, "Data:", apiData); await executeCommandMutation.mutateAsync({ url: API_ENDPOINTS.DEVICE_COMM.SEND_COMMAND(target), data: apiData as any, }); } } toast.success("Đã gửi yêu cầu thực thi lệnh cho các mục đã chọn!"); if (table) { table.setRowSelection({}); } } catch (error) { console.error("[DEBUG] Execute error:", error); console.error("[DEBUG] Response:", (error as any)?.response?.data); toast.error("Có lỗi xảy ra khi thực thi!"); } }; // Handle execute custom command const handleExecuteCustom = async (targets: string[], commandData: ShellCommandData) => { try { for (const target of targets) { // API expects PascalCase directly const apiData = { Command: commandData.command, QoS: commandData.qos, IsRetained: commandData.isRetained, }; console.log("[DEBUG] Sending custom to:", target, "Data:", apiData); await executeCommandMutation.mutateAsync({ url: BASE_URL + API_ENDPOINTS.DEVICE_COMM.SEND_COMMAND(target), data: apiData as any, }); } toast.success("Đã gửi lệnh tùy chỉnh cho các mục đã chọn!"); } catch (error) { console.error("[DEBUG] Execute custom error:", error); console.error("[DEBUG] Response:", (error as any)?.response?.data); toast.error("Gửi lệnh tùy chỉnh thất bại!"); } }; return ( { setSelectedCommand(null); setIsDialogOpen(true); }} onTableInit={setTable} formContent={ setIsDialogOpen(false)} initialData={selectedCommand || undefined} title={selectedCommand ? "Sửa Lệnh" : "Thêm Lệnh Mới"} /> } onExecuteSelected={handleExecuteSelected} onExecuteCustom={handleExecuteCustom} isExecuting={executeCommandMutation.isPending} rooms={roomData} /> ); }