TTMT.ManageWebGUI/src/components/buttons/command-action-buttons.tsx

276 lines
8.4 KiB
TypeScript

import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useGetCommandsByTypes } from "@/hooks/queries/useCommandQueries";
import { useSendCommand } from "@/hooks/queries";
import { CommandType } from "@/types/command-registry";
import {
Power,
PowerOff,
XCircle,
ShieldBan,
ChevronDown,
Loader2,
AlertTriangle
} from "lucide-react";
import { toast } from "sonner";
interface CommandActionButtonsProps {
roomName: string;
selectedDevices?: string[]; // Các thiết bị đã chọn
}
const COMMAND_TYPE_CONFIG = {
[CommandType.RESTART]: {
label: "Khởi động lại",
icon: Power,
color: "text-blue-600",
bgColor: "bg-blue-50 hover:bg-blue-100",
},
[CommandType.SHUTDOWN]: {
label: "Tắt máy",
icon: PowerOff,
color: "text-red-600",
bgColor: "bg-red-50 hover:bg-red-100",
},
[CommandType.TASKKILL]: {
label: "Kết thúc tác vụ",
icon: XCircle,
color: "text-orange-600",
bgColor: "bg-orange-50 hover:bg-orange-100",
},
[CommandType.BLOCK]: {
label: "Chặn",
icon: ShieldBan,
color: "text-purple-600",
bgColor: "bg-purple-50 hover:bg-purple-100",
},
};
export function CommandActionButtons({ roomName, selectedDevices = [] }: CommandActionButtonsProps) {
const [confirmDialog, setConfirmDialog] = useState<{
open: boolean;
command: any;
commandType: CommandType;
}>({
open: false,
command: null,
commandType: CommandType.RESTART,
});
const [isExecuting, setIsExecuting] = useState(false);
// Query commands for each type
const { data: restartCommands = [] } = useGetCommandsByTypes(CommandType.RESTART.toString());
const { data: shutdownCommands = [] } = useGetCommandsByTypes(CommandType.SHUTDOWN.toString());
const { data: taskkillCommands = [] } = useGetCommandsByTypes(CommandType.TASKKILL.toString());
const { data: blockCommands = [] } = useGetCommandsByTypes(CommandType.BLOCK.toString());
// Send command mutation
const sendCommandMutation = useSendCommand();
const commandsByType = {
[CommandType.RESTART]: restartCommands,
[CommandType.SHUTDOWN]: shutdownCommands,
[CommandType.TASKKILL]: taskkillCommands,
[CommandType.BLOCK]: blockCommands,
};
const handleCommandClick = (command: any, commandType: CommandType) => {
setConfirmDialog({
open: true,
command,
commandType,
});
};
const handleConfirmExecute = async () => {
setIsExecuting(true);
try {
// Chuẩn bị data theo format API (PascalCase)
const apiData = {
Command: confirmDialog.command.commandContent,
QoS: confirmDialog.command.qoS,
IsRetained: confirmDialog.command.isRetained,
};
// Gửi lệnh đến phòng
await sendCommandMutation.mutateAsync({
roomName,
data: apiData as any,
});
toast.success(`Đã gửi lệnh: ${confirmDialog.command.commandName}`);
setConfirmDialog({ open: false, command: null, commandType: CommandType.RESTART });
// Reload page để tránh freeze
setTimeout(() => {
window.location.reload();
}, 500);
} catch (error) {
console.error("Execute command error:", error);
toast.error("Lỗi khi gửi lệnh!");
setIsExecuting(false);
}
};
const handleCloseDialog = () => {
if (!isExecuting) {
setConfirmDialog({ open: false, command: null, commandType: CommandType.RESTART });
// Reload để tránh freeze
setTimeout(() => {
window.location.reload();
}, 300);
}
};
const renderCommandButton = (commandType: CommandType) => {
const config = COMMAND_TYPE_CONFIG[commandType];
const commands = commandsByType[commandType];
const Icon = config.icon;
if (!commands || commands.length === 0) {
return (
<Button
key={commandType}
variant="outline"
disabled
size="sm"
className="gap-2"
>
<Icon className={`h-4 w-4 ${config.color}`} />
{config.label}
<span className="text-xs text-muted-foreground ml-1">(0)</span>
</Button>
);
}
return (
<DropdownMenu key={commandType}>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className={`gap-2 ${config.bgColor} border-${config.color.split('-')[1]}-200`}
>
<Icon className={`h-4 w-4 ${config.color}`} />
{config.label}
<span className="text-xs text-muted-foreground ml-1">({commands.length})</span>
<ChevronDown className="h-3 w-3 ml-1" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
side="bottom"
sideOffset={4}
alignOffset={0}
className="w-64"
avoidCollisions={true}
>
{commands.map((command: any) => (
<DropdownMenuItem
key={command.id}
onClick={() => handleCommandClick(command, commandType)}
className="cursor-pointer"
>
<div className="flex flex-col gap-1">
<span className="font-medium">{command.commandName}</span>
{command.description && (
<span className="text-xs text-muted-foreground">
{command.description}
</span>
)}
</div>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
};
return (
<>
<div className="flex flex-wrap gap-2">
{Object.values(CommandType)
.filter((value) => typeof value === "number")
.map((commandType) => renderCommandButton(commandType as CommandType))}
</div>
{/* Confirm Dialog */}
<Dialog open={confirmDialog.open} onOpenChange={handleCloseDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5 text-orange-600" />
Xác nhận thực thi lệnh
</DialogTitle>
<DialogDescription className="text-left space-y-3">
<p>
Bạn chắc chắn muốn thực thi lệnh <strong>{confirmDialog.command?.commandName}</strong>?
</p>
{confirmDialog.command?.description && (
<p className="text-sm text-muted-foreground">
{confirmDialog.command.description}
</p>
)}
<div className="bg-muted p-3 rounded-md space-y-1">
<p className="text-sm">
<span className="font-medium">Phòng:</span> {roomName}
</p>
<p className="text-sm">
<span className="font-medium">Loại lệnh:</span>{" "}
{COMMAND_TYPE_CONFIG[confirmDialog.commandType]?.label}
</p>
{selectedDevices.length > 0 && (
<p className="text-sm">
<span className="font-medium">Thiết bị đã chọn:</span>{" "}
{selectedDevices.length} thiết bị
</p>
)}
</div>
<p className="text-sm text-orange-600 font-medium">
Lệnh sẽ đưc thực thi ngay lập tức không thể hoàn tác.
</p>
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={handleCloseDialog}
disabled={isExecuting}
>
Hủy
</Button>
<Button
onClick={handleConfirmExecute}
disabled={isExecuting}
className="gap-2"
>
{isExecuting ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
Đang thực thi...
</>
) : (
"Xác nhận"
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}