diff --git a/package-lock.json b/package-lock.json
index e8db8ff..60b65a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
         "@radix-ui/react-avatar": "^1.1.10",
         "@radix-ui/react-checkbox": "^1.3.3",
         "@radix-ui/react-dialog": "^1.1.14",
+        "@radix-ui/react-dropdown-menu": "^2.1.16",
         "@radix-ui/react-label": "^2.1.7",
         "@radix-ui/react-popover": "^1.1.15",
         "@radix-ui/react-progress": "^1.1.7",
@@ -1579,6 +1580,41 @@
         }
       }
     },
+    "node_modules/@radix-ui/react-dropdown-menu": {
+      "version": "2.1.16",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
+      "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.3",
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-context": "1.1.2",
+        "@radix-ui/react-id": "1.1.1",
+        "@radix-ui/react-menu": "2.1.16",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-use-controllable-state": "1.2.2"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "@types/react-dom": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+        "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "@types/react-dom": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+      "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+      "license": "MIT"
+    },
     "node_modules/@radix-ui/react-focus-guards": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz",
@@ -1660,6 +1696,150 @@
         }
       }
     },
+    "node_modules/@radix-ui/react-menu": {
+      "version": "2.1.16",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
+      "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.3",
+        "@radix-ui/react-collection": "1.1.7",
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-context": "1.1.2",
+        "@radix-ui/react-direction": "1.1.1",
+        "@radix-ui/react-dismissable-layer": "1.1.11",
+        "@radix-ui/react-focus-guards": "1.1.3",
+        "@radix-ui/react-focus-scope": "1.1.7",
+        "@radix-ui/react-id": "1.1.1",
+        "@radix-ui/react-popper": "1.2.8",
+        "@radix-ui/react-portal": "1.1.9",
+        "@radix-ui/react-presence": "1.1.5",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-roving-focus": "1.1.11",
+        "@radix-ui/react-slot": "1.2.3",
+        "@radix-ui/react-use-callback-ref": "1.1.1",
+        "aria-hidden": "^1.2.4",
+        "react-remove-scroll": "^2.6.3"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "@types/react-dom": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+        "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "@types/react-dom": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+      "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+      "license": "MIT"
+    },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
+      "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.3",
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-use-callback-ref": "1.1.1",
+        "@radix-ui/react-use-escape-keydown": "1.1.1"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "@types/react-dom": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+        "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "@types/react-dom": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
+      "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
+      "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/react-dom": "^2.0.0",
+        "@radix-ui/react-arrow": "1.1.7",
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-context": "1.1.2",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-use-callback-ref": "1.1.1",
+        "@radix-ui/react-use-layout-effect": "1.1.1",
+        "@radix-ui/react-use-rect": "1.1.1",
+        "@radix-ui/react-use-size": "1.1.1",
+        "@radix-ui/rect": "1.1.1"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "@types/react-dom": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+        "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "@types/react-dom": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+      "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-use-layout-effect": "1.1.1"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "@types/react-dom": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+        "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "@types/react-dom": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@radix-ui/react-popover": {
       "version": "1.1.15",
       "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
diff --git a/package.json b/package.json
index 2fc1438..9d68cd9 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
     "@radix-ui/react-avatar": "^1.1.10",
     "@radix-ui/react-checkbox": "^1.3.3",
     "@radix-ui/react-dialog": "^1.1.14",
+    "@radix-ui/react-dropdown-menu": "^2.1.16",
     "@radix-ui/react-label": "^2.1.7",
     "@radix-ui/react-popover": "^1.1.15",
     "@radix-ui/react-progress": "^1.1.7",
diff --git a/src/components/add-new-dialog.tsx b/src/components/add-new-dialog.tsx
new file mode 100644
index 0000000..c83e554
--- /dev/null
+++ b/src/components/add-new-dialog.tsx
@@ -0,0 +1,140 @@
+import { useState } from "react";
+import {
+  Dialog,
+  DialogContent,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+  DialogTrigger,
+} from "@/components/ui/dialog";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { Label } from "@/components/ui/label";
+import { toast } from "sonner";
+import { useForm, formOptions } from "@tanstack/react-form";
+import axios from "axios";
+
+interface AddBlacklistDialogProps {
+  onAdded?: () => void; // callback để refresh danh sách sau khi thêm
+}
+
+const formOpts = formOptions({
+  defaultValues: { appName: "", processName: "" },
+});
+
+export function AddBlacklistDialog({ onAdded }: AddBlacklistDialogProps) {
+  const [isOpen, setIsOpen] = useState(false);
+  const [isSubmitting, setIsSubmitting] = useState(false);
+  const [isDone, setIsDone] = useState(false);
+
+  const form = useForm({
+    ...formOpts,
+    onSubmit: async ({ value }) => {
+      if (!value.appName || !value.processName) {
+        toast.error("Vui lòng nhập đầy đủ tên ứng dụng và tiến trình");
+        return;
+      }
+
+      try {
+        setIsSubmitting(true);
+
+        await axios.post("/api/appversions/add-blacklist", {
+          appName: value.appName,
+          processName: value.processName,
+        });
+
+        toast.success("Đã thêm vào blacklist!");
+        setIsDone(true);
+
+        if (onAdded) onAdded();
+      } catch (error) {
+        console.error(error);
+        toast.error("Không thể thêm vào blacklist");
+      } finally {
+        setIsSubmitting(false);
+      }
+    },
+  });
+
+  const handleDialogClose = (open: boolean) => {
+    if (isSubmitting) return;
+    setIsOpen(open);
+    if (!open) {
+      setIsDone(false);
+      form.reset();
+    }
+  };
+
+  return (
+    
+  );
+}
diff --git a/src/components/device-grid.tsx b/src/components/device-grid.tsx
index 254143c..75b3bdc 100644
--- a/src/components/device-grid.tsx
+++ b/src/components/device-grid.tsx
@@ -11,23 +11,35 @@ export function DeviceGrid({ devices }: { devices: any[] }) {
     if (number > 0 && number <= 40) deviceMap.set(number, device);
   });
 
-  const computersPerRow = 8;
   const totalRows = 5;
 
   const renderRow = (rowIndex: number) => {
-    const start = rowIndex * computersPerRow + 1;
+    // Trái: 1–20
+    const leftStart = rowIndex * 4 + 1;
+    // Phải: 21–40
+    const rightStart = 21 + rowIndex * 4;
+
     return (
       
+        {/* Bên trái (1–20) */}
         {Array.from({ length: 4 }).map((_, i) => {
-          const pos = start + i;
-          return 
;
+          const pos = leftStart + i;
+          return (
+            
+          );
         })}
+
+        {/* Đường chia giữa */}
         
+
+        {/* Bên phải (21–40) */}
         {Array.from({ length: 4 }).map((_, i) => {
-          const pos = start + i + 4;
-          return 
;
+          const pos = rightStart + i;
+          return (
+            
+          );
         })}
       
 
     );
diff --git a/src/components/request-update-menu.tsx b/src/components/request-update-menu.tsx
new file mode 100644
index 0000000..ef51d71
--- /dev/null
+++ b/src/components/request-update-menu.tsx
@@ -0,0 +1,65 @@
+import { Button } from "@/components/ui/button";
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuTrigger,
+  DropdownMenuSeparator,
+} from "@/components/ui/dropdown-menu";
+import { Loader2, RefreshCw, ChevronDown } from "lucide-react";
+
+interface RequestUpdateMenuProps {
+  onUpdateDevice: () => void;
+  onUpdateRoom: () => void;
+  onUpdateAll: () => void;
+  loading?: boolean;
+}
+
+export function RequestUpdateMenu({
+  onUpdateDevice,
+  onUpdateRoom,
+  onUpdateAll,
+  loading,
+}: RequestUpdateMenuProps) {
+  return (
+    
+      
+        
+      
+      
+        
+          
+          Cập nhật thiết bị cụ thể
+        
+        
+        
+          
+          Cập nhật theo phòng
+        
+        
+        
+          
+          Cập nhật tất cả thiết bị
+        
+      
+    
+  );
+}
diff --git a/src/components/room-select-dialog.tsx b/src/components/room-select-dialog.tsx
deleted file mode 100644
index 937addf..0000000
--- a/src/components/room-select-dialog.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-"use client"
-
-import { useState } from "react"
-import {
-  Dialog,
-  DialogContent,
-  DialogHeader,
-  DialogTitle,
-  DialogFooter,
-} from "@/components/ui/dialog"
-import { Button } from "@/components/ui/button"
-import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
-import { Label } from "@/components/ui/label"
-import { Check, Home } from "lucide-react"
-
-interface RoomSelectDialogProps {
-  open: boolean
-  onClose: () => void
-  rooms: string[]
-  onConfirm: (roomName: string) => void
-}
-
-export function RoomSelectDialog({
-  open,
-  onClose,
-  rooms,
-  onConfirm,
-}: RoomSelectDialogProps) {
-  const [selectedRoom, setSelectedRoom] = useState("")
-
-  return (
-    
-  )
-}
diff --git a/src/components/select-dialog.tsx b/src/components/select-dialog.tsx
new file mode 100644
index 0000000..b8fe53f
--- /dev/null
+++ b/src/components/select-dialog.tsx
@@ -0,0 +1,135 @@
+import { useEffect, useState, useMemo } from "react"
+import {
+  Dialog,
+  DialogContent,
+  DialogHeader,
+  DialogTitle,
+  DialogFooter,
+} from "@/components/ui/dialog"
+import { Button } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import { Label } from "@/components/ui/label"
+import { Check, Search } from "lucide-react"
+import { Input } from "@/components/ui/input"
+
+interface SelectDialogProps {
+  open: boolean
+  onClose: () => void
+  items: string[]           // danh sách chung: có thể là devices hoặc rooms
+  title?: string            // tiêu đề động
+  description?: string      // mô tả ngắn
+  icon?: React.ReactNode    // icon thay đổi tùy loại
+  onConfirm: (selected: string[]) => void
+}
+
+export function SelectDialog({
+  open,
+  onClose,
+  items,
+  title = "Chọn mục",
+  description = "Bạn có thể chọn nhiều mục để thao tác",
+  icon,
+  onConfirm,
+}: SelectDialogProps) {
+  const [selectedItems, setSelectedItems] = useState([])
+  const [search, setSearch] = useState("")
+
+  useEffect(() => {
+    if (!open) {
+      setSelectedItems([])
+      setSearch("")
+    }
+  }, [open])
+
+  const toggleItem = (item: string) => {
+    setSelectedItems((prev) =>
+      prev.includes(item)
+        ? prev.filter((i) => i !== item)
+        : [...prev, item]
+    )
+  }
+
+  // Lọc danh sách theo từ khóa
+  const filteredItems = useMemo(() => {
+    return items.filter((item) =>
+      item.toLowerCase().includes(search.toLowerCase())
+    )
+  }, [items, search])
+
+  return (
+    
+  )
+}
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..eaed9ba
--- /dev/null
+++ b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,255 @@
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function DropdownMenu({
+  ...props
+}: React.ComponentProps) {
+  return 
+}
+
+function DropdownMenuPortal({
+  ...props
+}: React.ComponentProps) {
+  return (
+    
+  )
+}
+
+function DropdownMenuTrigger({
+  ...props
+}: React.ComponentProps) {
+  return (
+    
+  )
+}
+
+function DropdownMenuContent({
+  className,
+  sideOffset = 4,
+  ...props
+}: React.ComponentProps) {
+  return (
+    
+      
+    
+  )
+}
+
+function DropdownMenuGroup({
+  ...props
+}: React.ComponentProps) {
+  return (
+    
+  )
+}
+
+function DropdownMenuItem({
+  className,
+  inset,
+  variant = "default",
+  ...props
+}: React.ComponentProps & {
+  inset?: boolean
+  variant?: "default" | "destructive"
+}) {
+  return (
+    
+  )
+}
+
+function DropdownMenuCheckboxItem({
+  className,
+  children,
+  checked,
+  ...props
+}: React.ComponentProps) {
+  return (
+    
+      
+        
+          
+        
+      
+      {children}
+    
+  )
+}
+
+function DropdownMenuRadioGroup({
+  ...props
+}: React.ComponentProps) {
+  return (
+    
+  )
+}
+
+function DropdownMenuRadioItem({
+  className,
+  children,
+  ...props
+}: React.ComponentProps) {
+  return (
+    
+      
+        
+          
+        
+      
+      {children}
+    
+  )
+}
+
+function DropdownMenuLabel({
+  className,
+  inset,
+  ...props
+}: React.ComponentProps & {
+  inset?: boolean
+}) {
+  return (
+    
+  )
+}
+
+function DropdownMenuSeparator({
+  className,
+  ...props
+}: React.ComponentProps) {
+  return (
+    
+  )
+}
+
+function DropdownMenuShortcut({
+  className,
+  ...props
+}: React.ComponentProps<"span">) {
+  return (
+    
+  )
+}
+
+function DropdownMenuSub({
+  ...props
+}: React.ComponentProps) {
+  return 
+}
+
+function DropdownMenuSubTrigger({
+  className,
+  inset,
+  children,
+  ...props
+}: React.ComponentProps & {
+  inset?: boolean
+}) {
+  return (
+    
+      {children}
+      
+    
+  )
+}
+
+function DropdownMenuSubContent({
+  className,
+  ...props
+}: React.ComponentProps) {
+  return (
+    
+  )
+}
+
+export {
+  DropdownMenu,
+  DropdownMenuPortal,
+  DropdownMenuTrigger,
+  DropdownMenuContent,
+  DropdownMenuGroup,
+  DropdownMenuLabel,
+  DropdownMenuItem,
+  DropdownMenuCheckboxItem,
+  DropdownMenuRadioGroup,
+  DropdownMenuRadioItem,
+  DropdownMenuSeparator,
+  DropdownMenuShortcut,
+  DropdownMenuSub,
+  DropdownMenuSubTrigger,
+  DropdownMenuSubContent,
+}
diff --git a/src/config/api.ts b/src/config/api.ts
index de4e355..c076a3e 100644
--- a/src/config/api.ts
+++ b/src/config/api.ts
@@ -29,6 +29,5 @@ export const API_ENDPOINTS = {
     DEVICE_ONLINE: `${BASE_URL}/Sse/events/onlineDevices`,
     DEVICE_OFFLINE: `${BASE_URL}/Sse/events/offlineDevices`,
     GET_PROCESSES_LISTS:  `${BASE_URL}/Sse/events/processLists`,
-    
   },
 };
diff --git a/src/routes/_authenticated/agent/index.tsx b/src/routes/_authenticated/agent/index.tsx
index acaf9ae..5e52b7d 100644
--- a/src/routes/_authenticated/agent/index.tsx
+++ b/src/routes/_authenticated/agent/index.tsx
@@ -59,7 +59,7 @@ function AgentsPage() {
   });
 
   const updateMutation = useMutationData({
-    url: "", 
+    url: "",
     method: "POST",
     onSuccess: () => toast.success("Đã gửi yêu cầu update!"),
     onError: () => toast.error("Gửi yêu cầu thất bại!"),
@@ -75,13 +75,19 @@ function AgentsPage() {
     });
   };
 
-  // Callback khi chọn phòng update
-  const handleUpdate = async (roomName: string) => {
-    return updateMutation.mutateAsync({
-      url: BASE_URL + API_ENDPOINTS.DEVICE_COMM.UPDATE_AGENT(roomName),
-      method: "POST",
-      data: undefined,
-    });
+  const handleUpdate = async (roomNames: string[]) => {
+    for (const roomName of roomNames) {
+      try {
+        await updateMutation.mutateAsync({
+          url: BASE_URL + API_ENDPOINTS.DEVICE_COMM.UPDATE_AGENT(roomName),
+          method: "POST",
+          data: undefined
+        });
+      } catch {
+        toast.error(`Gửi yêu cầu thất bại cho ${roomName}`);
+      }
+    }
+    toast.success("Đã gửi yêu cầu update cho các phòng đã chọn!");
   };
 
   // Cột bảng
diff --git a/src/routes/_authenticated/apps/index.tsx b/src/routes/_authenticated/apps/index.tsx
index 9533523..f6053bb 100644
--- a/src/routes/_authenticated/apps/index.tsx
+++ b/src/routes/_authenticated/apps/index.tsx
@@ -113,7 +113,7 @@ function AppsComponent() {
   };
 
   // Callback khi chọn phòng
-  const handleInstall = async (roomName: string) => {
+  const handleInstall = async (roomNames: string[]) => {
     if (!table) {
       toast.error("Không thể lấy thông tin bảng!");
       return;
@@ -127,10 +127,14 @@ function AppsComponent() {
 
     const MsiFileIds = selectedRows.map((row: any) => row.original.id);
 
-    return installMutation.mutateAsync({
-      url: BASE_URL + API_ENDPOINTS.DEVICE_COMM.DOWNLOAD_MSI(roomName), // set url động
-      data: { MsiFileIds },
-    });
+    for (const roomName of roomNames) {
+      await installMutation.mutateAsync({
+        url: BASE_URL + API_ENDPOINTS.DEVICE_COMM.DOWNLOAD_MSI(roomName),
+        data: { MsiFileIds },
+      });
+    }
+
+    toast.success("Đã gửi yêu cầu cài đặt phần mềm cho các phòng đã chọn!");
   };
 
   return (
diff --git a/src/routes/_authenticated/room/$roomName/index.tsx b/src/routes/_authenticated/room/$roomName/index.tsx
index 00b2369..2cb9c7b 100644
--- a/src/routes/_authenticated/room/$roomName/index.tsx
+++ b/src/routes/_authenticated/room/$roomName/index.tsx
@@ -17,7 +17,7 @@ export const Route = createFileRoute("/_authenticated/room/$roomName/")({
 
 function RoomDetailPage() {
   const { roomName } = useParams({ from: "/_authenticated/room/$roomName/" });
-  const [viewMode, setViewMode] = useState<"table" | "grid">("table");
+  const [viewMode, setViewMode] = useState<"grid" | "table">("grid");
   const { data: devices = [] } = useQueryData({
     queryKey: ["devices", roomName],
     url: BASE_URL + API_ENDPOINTS.DEVICE_COMM.GET_DEVICE_FROM_ROOM(roomName),
@@ -33,15 +33,6 @@ function RoomDetailPage() {
           
 
           
-            
             
               Sơ đồ
             
+            
            
         
 
diff --git a/src/template/app-manager-template.tsx b/src/template/app-manager-template.tsx
index 907ef08..18a1205 100644
--- a/src/template/app-manager-template.tsx
+++ b/src/template/app-manager-template.tsx
@@ -7,13 +7,13 @@ import {
   CardHeader,
   CardTitle,
 } from "@/components/ui/card";
-import { FileText } from "lucide-react";
+import { FileText, Building2, Monitor } from "lucide-react";
 import { UploadDialog } from "@/components/upload-dialog";
 import { VersionTable } from "@/components/version-table";
-import { UpdateButton } from "@/components/update-button";
-import { RoomSelectDialog } from "@/components/room-select-dialog";
+import { RequestUpdateMenu } from "@/components/request-update-menu";
 import type { AxiosProgressEvent } from "axios";
 import { useState } from "react";
+import { SelectDialog } from "@/components/select-dialog"; // <-- dùng dialog chung
 
 interface AppManagerTemplateProps {
   title: string;
@@ -25,10 +25,11 @@ interface AppManagerTemplateProps {
     fd: FormData,
     config?: { onUploadProgress?: (e: AxiosProgressEvent) => void }
   ) => Promise;
-  onUpdate?: (roomName: string) => void;
+  onUpdate?: (targetNames: string[]) => Promise | void;
   updateLoading?: boolean;
   onTableInit?: (table: any) => void;
-  rooms: string[];
+  rooms?: string[];
+  devices?: string[];
 }
 
 export function AppManagerTemplate({
@@ -41,16 +42,57 @@ export function AppManagerTemplate({
   onUpdate,
   updateLoading,
   onTableInit,
-  rooms,
+  rooms = [],
+  devices = [],
 }: AppManagerTemplateProps) {
   const [dialogOpen, setDialogOpen] = useState(false);
-  const handleUpdateClick = () => {
-    if (rooms && onUpdate) {
+  const [dialogType, setDialogType] = useState<"room" | "device" | null>(null);
+
+  const openRoomDialog = () => {
+    if (rooms.length > 0 && onUpdate) {
+      setDialogType("room");
       setDialogOpen(true);
     }
   };
+
+  const openDeviceDialog = () => {
+    if (devices.length > 0 && onUpdate) {
+      setDialogType("device");
+      setDialogOpen(true);
+    }
+  };
+
+  const handleUpdateAll = async () => {
+    if (!onUpdate) return;
+    const allTargets = [...rooms, ...devices];
+    await onUpdate(allTargets); 
+  };
+
+  const getDialogProps = () => {
+    if (dialogType === "room") {
+      return {
+        title: "Chọn phòng",
+        description: "Chọn các phòng cần cập nhật",
+        icon: ,
+        items: rooms,
+      };
+    }
+    if (dialogType === "device") {
+      return {
+        title: "Chọn thiết bị",
+        description: "Chọn các thiết bị cần cập nhật",
+        icon: ,
+        items: devices,
+      };
+    }
+    return null;
+  };
+
+  const dialogProps = getDialogProps();
+
   return (
     
+      {/* Header */}
       
         
           
{title}
@@ -59,6 +101,7 @@ export function AppManagerTemplate({
         
       
 
+      {/* Table */}
       
         
           
@@ -66,6 +109,7 @@ export function AppManagerTemplate({
           
           Tất cả các phiên bản đã tải lên
         
+
         
           ({
             onTableInit={onTableInit}
           />
         
+
         {onUpdate && (
           
-            
-             onUpdate("All")}
+            
           
         )}
-        {rooms && onUpdate && (
-           setDialogOpen(false)}
-            rooms={rooms}
-            onConfirm={(roomName) => {
-              onUpdate(roomName);
-              setDialogOpen(false);
-            }}
-          />
-        )}
       
+
+      {/* 🧩 SelectDialog tái sử dụng */}
+      {dialogProps && (
+        
 setDialogOpen(false)}
+          title={dialogProps.title}
+          description={dialogProps.description}
+          icon={dialogProps.icon}
+          items={dialogProps.items}
+          onConfirm={async (selectedItems) => {
+            if (!onUpdate) return;
+            await onUpdate(selectedItems);
+            setDialogOpen(false);
+          }}
+        />
+      )}
      
   );
 }
diff --git a/src/template/form-submit-template.tsx b/src/template/form-submit-template.tsx
index e0c7817..885eecb 100644
--- a/src/template/form-submit-template.tsx
+++ b/src/template/form-submit-template.tsx
@@ -1,5 +1,3 @@
-"use client"
-
 import { useState } from "react"
 import {
   Card,
@@ -9,9 +7,9 @@ import {
   CardHeader,
   CardTitle,
 } from "@/components/ui/card"
-import { UpdateButton } from "@/components/update-button"
-import { Terminal } from "lucide-react"
-import { RoomSelectDialog } from "@/components/room-select-dialog"
+import { Terminal, Building2, Monitor } from "lucide-react"
+import { RequestUpdateMenu } from "@/components/request-update-menu"
+import { SelectDialog } from "@/components/select-dialog"
 
 interface FormSubmitTemplateProps {
   title: string
@@ -21,9 +19,10 @@ interface FormSubmitTemplateProps {
     command: string
     setCommand: (val: string) => void
   }) => React.ReactNode
-  onSubmit?: (roomName: string, command: string) => void
+  onSubmit?: (target: string, command: string) => void | Promise
   submitLoading?: boolean
   rooms?: string[]
+  devices?: string[]
 }
 
 export function FormSubmitTemplate({
@@ -34,16 +33,56 @@ export function FormSubmitTemplate({
   onSubmit,
   submitLoading,
   rooms = [],
+  devices = [],
 }: FormSubmitTemplateProps) {
-  const [dialogOpen, setDialogOpen] = useState(false)
   const [command, setCommand] = useState("")
+  const [dialogOpen, setDialogOpen] = useState(false)
+  const [dialogType, setDialogType] = useState<"room" | "device" | null>(null)
 
-  const handleClick = () => {
+  const openRoomDialog = () => {
     if (rooms.length > 0 && onSubmit) {
+      setDialogType("room")
       setDialogOpen(true)
     }
   }
 
+  const openDeviceDialog = () => {
+    if (devices.length > 0 && onSubmit) {
+      setDialogType("device")
+      setDialogOpen(true)
+    }
+  }
+
+  const handleSubmitAll = () => {
+    if (!onSubmit) return
+    const allTargets = [...rooms, ...devices]
+    for (const target of allTargets) {
+      onSubmit(target, command)
+    }
+  }
+
+  const getDialogProps = () => {
+    if (dialogType === "room") {
+      return {
+        title: "Chọn phòng để gửi lệnh",
+        description: "Chọn các phòng muốn gửi lệnh CMD tới",
+        icon: ,
+        items: rooms,
+      }
+    }
+    if (dialogType === "device") {
+      return {
+        title: "Chọn thiết bị để gửi lệnh",
+        description: "Chọn các thiết bị muốn gửi lệnh CMD tới",
+        icon: ,
+        items: devices,
+      }
+    }
+    return null
+  }
+
+  const dialogProps = getDialogProps()
+
   return (
     
       
@@ -58,33 +97,35 @@ export function FormSubmitTemplate({
           
           
Nhập và gửi lệnh xuống thiết bị
         
-        
-          {children({ command, setCommand })}
-        
+
+        
{children({ command, setCommand })}
 
         {onSubmit && (
-          
-            
+            
-             onSubmit("All", command)}
-              loading={submitLoading}
-              label="Cập nhật tất cả thiết bị"
             />
           
         )}
       
 
-      {onSubmit && rooms.length > 0 && (
-        
 setDialogOpen(false)}
-          rooms={rooms}
-          onConfirm={(roomName) => {
-            onSubmit(roomName, command)
+          title={dialogProps.title}
+          description={dialogProps.description}
+          icon={dialogProps.icon}
+          items={dialogProps.items}
+          onConfirm={async (selectedItems) => {
+            if (!onSubmit) return
+            for (const item of selectedItems) {
+              await onSubmit(item, command)
+            }
             setDialogOpen(false)
           }}
         />
diff --git a/src/template/table-manager-template.tsx b/src/template/table-manager-template.tsx
new file mode 100644
index 0000000..f758338
--- /dev/null
+++ b/src/template/table-manager-template.tsx
@@ -0,0 +1,153 @@
+import { RequestUpdateMenu } from "@/components/request-update-menu";
+import { SelectDialog } from "@/components/select-dialog";
+import {
+  Card,
+  CardContent,
+  CardDescription,
+  CardFooter,
+  CardHeader,
+  CardTitle,
+} from "@/components/ui/card";
+import { UploadDialog } from "@/components/upload-dialog";
+import { VersionTable } from "@/components/version-table";
+import type { ColumnDef } from "@tanstack/react-table";
+import type { AxiosProgressEvent } from "axios";
+import { FileText, Building2, Monitor } from "lucide-react";
+import { useState } from "react";
+
+interface BlackListManagerTemplateProps {
+  title: string;
+  description: string;
+  data: TData[];
+  isLoading: boolean;
+  columns: ColumnDef[];
+  onUpload: (
+    fd: FormData,
+    config?: { onUploadProgress?: (e: AxiosProgressEvent) => void }
+  ) => Promise;
+  onUpdate?: (roomName: string) => void;
+  updateLoading?: boolean;
+  onTableInit?: (table: any) => void;
+  rooms: string[];
+  devices?: string[];
+}
+
+export function BlackListManagerTemplate({
+  title,
+  description,
+  data,
+  isLoading,
+  columns,
+  onUpload,
+  onUpdate,
+  updateLoading,
+  onTableInit,
+  rooms = [],
+  devices = [],
+}: BlackListManagerTemplateProps) {
+  const [dialogOpen, setDialogOpen] = useState(false);
+  const [dialogType, setDialogType] = useState<"room" | "device" | null>(null);
+
+  const handleUpdateAll = () => {
+    if (onUpdate) onUpdate("All");
+  };
+
+  const openRoomDialog = () => {
+    if (rooms.length > 0 && onUpdate) {
+      setDialogType("room");
+      setDialogOpen(true);
+    }
+  };
+
+  const openDeviceDialog = () => {
+    if (devices.length > 0 && onUpdate) {
+      setDialogType("device");
+      setDialogOpen(true);
+    }
+  };
+
+  const getDialogProps = () => {
+    if (dialogType === "room") {
+      return {
+        title: "Chọn phòng",
+        description: "Chọn các phòng cần cập nhật",
+        icon: ,
+        items: rooms,
+      };
+    }
+    if (dialogType === "device") {
+      return {
+        title: "Chọn thiết bị",
+        description: "Chọn các thiết bị cần cập nhật",
+        icon: ,
+        items: devices,
+      };
+    }
+    return null;
+  };
+  const dialogProps = getDialogProps();
+
+  return (
+    
+      {/* Header */}
+      
+        
+          
{title}
+          
{description}
+        
+        
+      
+
+      {/* Table */}
+      
+        
+          
+             Danh sách phần mềm bị chặn
+          
+          
+            Các phần mềm không được cho phép trong hệ thống
+          
+        
+
+        
+          
+        
+
+        {/* Footer */}
+        {onUpdate && (
+          
+            
+          
+        )}
+      
+
+      {dialogProps && (
+        
 setDialogOpen(false)}
+          title={dialogProps.title}
+          description={dialogProps.description}
+          icon={dialogProps.icon}
+          items={dialogProps.items}
+          onConfirm={async (selectedItems) => {
+            if (!onUpdate) return;
+            for (const item of selectedItems) {
+              onUpdate(item);
+            }
+            setDialogOpen(false);
+          }}
+        />
+      )}
+     
+  );
+}
diff --git a/src/types/device.ts b/src/types/device.ts
new file mode 100644
index 0000000..53096da
--- /dev/null
+++ b/src/types/device.ts
@@ -0,0 +1,13 @@
+export interface NetworkInfo {
+  macAddress?: string;
+  ipAddress?: string;
+}
+
+export interface DeviceHealthCheck {
+  id: string;
+  deviceTime: string; 
+  version?: string;
+  room?: string;
+  isOffline: boolean;
+  networkInfos: NetworkInfo[];
+}