247 lines
7.4 KiB
TypeScript
247 lines
7.4 KiB
TypeScript
|
import { createFileRoute } from "@tanstack/react-router";
|
||
|
import {
|
||
|
Card,
|
||
|
CardContent,
|
||
|
CardDescription,
|
||
|
CardFooter,
|
||
|
CardHeader,
|
||
|
CardTitle,
|
||
|
} from "@/components/ui/card";
|
||
|
import { Input } from "@/components/ui/input";
|
||
|
import { Button } from "@/components/ui/button";
|
||
|
import { useQueryData } from "@/hooks/useQueryData";
|
||
|
import { useMutationData } from "@/hooks/useMutationData";
|
||
|
import { formOptions, useForm } from "@tanstack/react-form";
|
||
|
import { toast } from "sonner";
|
||
|
import { Label } from "@/components/ui/label";
|
||
|
import { useState } from "react";
|
||
|
import {
|
||
|
Dialog,
|
||
|
DialogContent,
|
||
|
DialogDescription,
|
||
|
DialogFooter,
|
||
|
DialogHeader,
|
||
|
DialogTitle,
|
||
|
DialogTrigger,
|
||
|
} from "@/components/ui/dialog";
|
||
|
import { FileText, Plus } from "lucide-react";
|
||
|
import {
|
||
|
Table,
|
||
|
TableBody,
|
||
|
TableCell,
|
||
|
TableHead,
|
||
|
TableHeader,
|
||
|
TableRow,
|
||
|
} from "@/components/ui/table";
|
||
|
|
||
|
import { BASE_URL, API_ENDPOINTS } from "@/config/api";
|
||
|
|
||
|
interface UploadAppFormProps {
|
||
|
files: FileList;
|
||
|
newVersion: string;
|
||
|
}
|
||
|
|
||
|
const defaultInput: UploadAppFormProps = {
|
||
|
files: new DataTransfer().files,
|
||
|
newVersion: "",
|
||
|
};
|
||
|
|
||
|
const formOpts = formOptions({
|
||
|
defaultValues: defaultInput,
|
||
|
});
|
||
|
|
||
|
export const Route = createFileRoute("/_authenticated/apps/")({
|
||
|
head: () => ({
|
||
|
meta: [{ title: "Quản lý Agent" }],
|
||
|
}),
|
||
|
component: AppsComponent,
|
||
|
});
|
||
|
|
||
|
function AppsComponent() {
|
||
|
const { data: versionData, isLoading } = useQueryData({
|
||
|
queryKey: ["app-version"],
|
||
|
url: BASE_URL + API_ENDPOINTS.APP_VERSION.GET_VERSION,
|
||
|
});
|
||
|
|
||
|
const versionList = Array.isArray(versionData)
|
||
|
? versionData
|
||
|
: versionData
|
||
|
? [versionData]
|
||
|
: [];
|
||
|
|
||
|
const [isUploadOpen, setIsUploadOpen] = useState(false);
|
||
|
|
||
|
const uploadMutation = useMutationData<FormData>({
|
||
|
url: BASE_URL + API_ENDPOINTS.APP_VERSION.UPLOAD,
|
||
|
method: "POST",
|
||
|
onSuccess: () => {
|
||
|
toast.success("Cập nhật thành công!");
|
||
|
setIsUploadOpen(false);
|
||
|
form.reset();
|
||
|
},
|
||
|
onError: () => toast.error("Lỗi khi cập nhật phiên bản!"),
|
||
|
});
|
||
|
|
||
|
const updateAgentMutation = useMutationData<void>({
|
||
|
url: BASE_URL + API_ENDPOINTS.DEVICE_COMM.UPDATE_AGENT,
|
||
|
method: "POST",
|
||
|
onSuccess: () => toast.success("Đã gửi yêu cầu cập nhật đến thiết bị!"),
|
||
|
onError: () => toast.error("Gửi yêu cầu thất bại!"),
|
||
|
});
|
||
|
|
||
|
const form = useForm({
|
||
|
...formOpts,
|
||
|
onSubmit: async ({ value }) => {
|
||
|
const typedValue = value as UploadAppFormProps;
|
||
|
if (!typedValue.newVersion || typedValue.files.length === 0) {
|
||
|
toast.error("Vui lòng điền đầy đủ thông tin");
|
||
|
return;
|
||
|
}
|
||
|
const formData = new FormData();
|
||
|
Array.from(typedValue.files).forEach((file) =>
|
||
|
formData.append("files", file)
|
||
|
);
|
||
|
formData.append("newVersion", typedValue.newVersion);
|
||
|
await uploadMutation.mutateAsync(formData);
|
||
|
},
|
||
|
});
|
||
|
|
||
|
return (
|
||
|
<div className="w-full px-6 space-y-4">
|
||
|
<div className="flex items-center justify-between">
|
||
|
<div>
|
||
|
<h1 className="text-3xl font-bold">Quản lý Agent</h1>
|
||
|
<p className="text-muted-foreground mt-2">
|
||
|
Quản lý và theo dõi các phiên bản Agent
|
||
|
</p>
|
||
|
</div>
|
||
|
|
||
|
<Dialog open={isUploadOpen} onOpenChange={setIsUploadOpen}>
|
||
|
<DialogTrigger asChild>
|
||
|
<Button className="gap-2">
|
||
|
<Plus className="h-4 w-4" />
|
||
|
Cập nhật phiên bản mới
|
||
|
</Button>
|
||
|
</DialogTrigger>
|
||
|
<DialogContent className="sm:max-w-md">
|
||
|
<DialogHeader>
|
||
|
<DialogTitle>Cập nhật phiên bản mới</DialogTitle>
|
||
|
<DialogDescription>
|
||
|
Chọn tệp và nhập số phiên bản
|
||
|
</DialogDescription>
|
||
|
</DialogHeader>
|
||
|
|
||
|
<form
|
||
|
className="space-y-4"
|
||
|
onSubmit={(e) => {
|
||
|
e.preventDefault();
|
||
|
form.handleSubmit();
|
||
|
}}
|
||
|
>
|
||
|
<form.Field name="newVersion">
|
||
|
{(field) => (
|
||
|
<div className="space-y-2">
|
||
|
<Label>Phiên bản</Label>
|
||
|
<Input
|
||
|
value={field.state.value}
|
||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||
|
placeholder="e.g., 1.0.0"
|
||
|
/>
|
||
|
</div>
|
||
|
)}
|
||
|
</form.Field>
|
||
|
|
||
|
<form.Field name="files">
|
||
|
{(field) => (
|
||
|
<div className="space-y-2">
|
||
|
<Label>File ứng dụng</Label>
|
||
|
<Input
|
||
|
type="file"
|
||
|
accept=".exe,.zip,.apk"
|
||
|
onChange={(e) => {
|
||
|
if (e.target.files) {
|
||
|
field.handleChange(e.target.files);
|
||
|
}
|
||
|
}}
|
||
|
/>
|
||
|
</div>
|
||
|
)}
|
||
|
</form.Field>
|
||
|
|
||
|
<DialogFooter>
|
||
|
<Button
|
||
|
type="button"
|
||
|
variant="outline"
|
||
|
onClick={() => setIsUploadOpen(false)}
|
||
|
>
|
||
|
Hủy
|
||
|
</Button>
|
||
|
<Button type="submit">Tải lên</Button>
|
||
|
</DialogFooter>
|
||
|
</form>
|
||
|
</DialogContent>
|
||
|
</Dialog>
|
||
|
</div>
|
||
|
|
||
|
<Card className="w-full">
|
||
|
<CardHeader>
|
||
|
<CardTitle className="flex items-center gap-2">
|
||
|
<FileText className="h-5 w-5" />
|
||
|
Lịch sử phiên bản
|
||
|
</CardTitle>
|
||
|
<CardDescription>
|
||
|
Tất cả các phiên bản đã tải lên của Agent
|
||
|
</CardDescription>
|
||
|
</CardHeader>
|
||
|
<CardContent>
|
||
|
<Table>
|
||
|
<TableHeader>
|
||
|
<TableRow>
|
||
|
<TableHead>Phiên bản</TableHead>
|
||
|
<TableHead>Tên tệp</TableHead>
|
||
|
<TableHead>Đường dẫn thư mục</TableHead>
|
||
|
<TableHead>Thời gian cập nhật</TableHead>
|
||
|
</TableRow>
|
||
|
</TableHeader>
|
||
|
<TableBody>
|
||
|
{isLoading ? (
|
||
|
<TableRow>
|
||
|
<TableCell colSpan={4}>Đang tải dữ liệu...</TableCell>
|
||
|
</TableRow>
|
||
|
) : versionList.length === 0 ? (
|
||
|
<TableRow>
|
||
|
<TableCell colSpan={4}>Không có dữ liệu phiên bản.</TableCell>
|
||
|
</TableRow>
|
||
|
) : (
|
||
|
versionList.map((v: any) => (
|
||
|
<TableRow key={v.id || v.version}>
|
||
|
<TableCell>{v.version}</TableCell>
|
||
|
<TableCell>{v.fileName}</TableCell>
|
||
|
<TableCell>{v.folderPath}</TableCell>
|
||
|
<TableCell>
|
||
|
{v.updatedAt
|
||
|
? new Date(v.updatedAt).toLocaleString("vi-VN")
|
||
|
: "N/A"}
|
||
|
</TableCell>
|
||
|
</TableRow>
|
||
|
))
|
||
|
)}
|
||
|
</TableBody>
|
||
|
</Table>
|
||
|
</CardContent>
|
||
|
<CardFooter>
|
||
|
<Button
|
||
|
variant="outline"
|
||
|
onClick={() => updateAgentMutation.mutateAsync()}
|
||
|
disabled={updateAgentMutation.isPending}
|
||
|
>
|
||
|
{updateAgentMutation.isPending
|
||
|
? "Đang gửi..."
|
||
|
: "Yêu cầu thiết bị cập nhật"}
|
||
|
</Button>
|
||
|
</CardFooter>
|
||
|
</Card>
|
||
|
</div>
|
||
|
);
|
||
|
}
|