From 26bb177d54300a4033cafb88cd5e7f75efc674fc Mon Sep 17 00:00:00 2001 From: phuongdm Date: Fri, 26 Sep 2025 17:56:55 +0700 Subject: [PATCH] add css --- package-lock.json | 202 ++++++++++++ package.json | 2 + src/components/app-sidebar.tsx | 69 ++-- src/components/command-form.tsx | 124 +++---- src/components/room-select-dialog.tsx | 98 ++++++ src/components/ui/avatar.tsx | 51 +++ src/components/ui/radio-group.tsx | 43 +++ src/components/update-button.tsx | 34 +- src/config/api.ts | 6 +- src/hooks/useMutationData.ts | 23 +- src/layouts/app-layout.tsx | 43 ++- src/routes/_authenticated/agent/index.tsx | 64 ++-- src/routes/_authenticated/apps/index.tsx | 42 ++- src/routes/_authenticated/command/index.tsx | 49 ++- .../_authenticated/room/$roomName/index.tsx | 302 ++++++++++++++---- src/routes/_authenticated/room/index.tsx | 269 ++++++++++++---- src/template/app-manager-template.tsx | 37 ++- src/template/form-submit-template.tsx | 69 +++- src/types/room.ts | 5 + 19 files changed, 1222 insertions(+), 310 deletions(-) create mode 100644 src/components/room-select-dialog.tsx create mode 100644 src/components/ui/avatar.tsx create mode 100644 src/components/ui/radio-group.tsx create mode 100644 src/types/room.ts diff --git a/package-lock.json b/package-lock.json index 67e4174..192f319 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,9 +6,11 @@ "": { "name": ".", "dependencies": { + "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-radio-group": "^1.3.8", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tooltip": "^1.2.7", @@ -1346,6 +1348,59 @@ } } }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", + "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", + "license": "MIT", + "dependencies": { + "@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-is-hydrated": "0.1.0", + "@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-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@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-slot": "1.2.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-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -1412,6 +1467,21 @@ } } }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "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-dismissable-layer": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", @@ -1647,6 +1717,105 @@ } } }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "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-direction": "1.1.1", + "@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-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "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-radio-group/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-radio-group/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-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "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-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@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-roving-focus/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-separator": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", @@ -1792,6 +1961,24 @@ } } }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "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-use-layout-effect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", @@ -1807,6 +1994,21 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "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-use-rect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", diff --git a/package.json b/package.json index f3d9afd..8e15669 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,11 @@ "test": "vitest run" }, "dependencies": { + "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-radio-group": "^1.3.8", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tooltip": "^1.2.7", diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index 7130615..1ae25cf 100644 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -1,6 +1,6 @@ -// app-sidebar.tsx - Đã hỗ trợ onPointerEnter +import type React from "react"; import { Link } from "@tanstack/react-router"; -import { Building } from "lucide-react"; +import { Building2, Cpu } from "lucide-react"; import { Sidebar, SidebarContent, @@ -13,6 +13,7 @@ import { SidebarMenuButton, SidebarMenuItem, } from "@/components/ui/sidebar"; +import { cn } from "@/lib/utils"; type MenuItem = { title: string; @@ -27,34 +28,58 @@ type AppSidebarProps = { export function AppSidebar({ items }: AppSidebarProps) { return ( - - -
-
- + + +
+
+
- TTMT Computer Management - v1.0.0 + + TTMT Computer Management + + + + v1.0.0 +
- - + + - Navigation + + Navigation + - + {items.map((item) => ( - - - - {item.title} + + + + {item.title} + @@ -63,10 +88,10 @@ export function AppSidebar({ items }: AppSidebarProps) { - - -
- © 2025 NAVIS Centre + + +
+ © 2025 NAVIS Centre
diff --git a/src/components/command-form.tsx b/src/components/command-form.tsx index 315620b..f187809 100644 --- a/src/components/command-form.tsx +++ b/src/components/command-form.tsx @@ -1,90 +1,68 @@ "use client"; -import { useState } from "react"; import { useForm } from "@tanstack/react-form"; import { z } from "zod"; - -import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { Loader2, Terminal, AlertTriangle, CheckCircle } from "lucide-react"; interface ShellCommandFormProps { - onExecute: (command: string) => Promise<{ success: boolean; output: string }>; + command: string; + onCommandChange: (value: string) => void; + disabled?: boolean; } -export function ShellCommandForm({ onExecute }: ShellCommandFormProps) { - const [isLoading, setIsLoading] = useState(false); - - // init form +export function ShellCommandForm({ + command, + onCommandChange, + disabled, +}: ShellCommandFormProps) { const form = useForm({ - defaultValues: { - command: "", - }, - onSubmit: async ({ value }) => { - setIsLoading(true); - try { - const res = await onExecute(value.command); - if (res.success) { - form.reset(); - } - } finally { - setIsLoading(false); - } - }, + defaultValues: { command }, + onSubmit: () => {}, }); return ( -
- - {/* Form */} -
{ - e.preventDefault(); - form.handleSubmit(); - }} - className="space-y-5" - > - {/* Field: command */} - { + e.preventDefault(); + form.handleSubmit(); + }} + className="space-y-5" + > + { + const schema = z + .string() + .min(1, "Nhập command để thực thi") + .max(500, "Command quá dài"); + const result = schema.safeParse(value); + if (!result.success) { + return result.error.issues.map((i) => i.message); + } + return []; + }, + }} + children={(field) => ( +
+