ghép API sendManifest
All checks were successful
CI / build-test (push) Successful in 53s
Deploy Staging (Docker) / deploy (push) Successful in 34s

This commit is contained in:
Do Manh Phuong 2026-06-05 20:23:14 +07:00
parent 4accb1dfc5
commit bd21d18c21
5 changed files with 97 additions and 63 deletions

View File

@ -74,6 +74,9 @@ export const API_ENDPOINTS = {
DELETE_REQUIRED_FILE: `${BASE_URL}/AppVersion/requirefile/delete`,
DELETE_FILES: `${BASE_URL}/AppVersion/delete`,
},
MANIFEST: {
SEND_ALL: `${BASE_URL}/Manifest/sendall`,
},
DEVICE_COMM: {
DOWNLOAD_FILES: (roomName: string) => `${BASE_URL}/DeviceComm/downloadfile/${roomName}`,
INSTALL_MSI: (roomName: string) => `${BASE_URL}/DeviceComm/installmsi/${roomName}`,

View File

@ -186,11 +186,10 @@ export function useDeleteFile() {
}
/**
* Hook đ gửi manifest [MOCK]
* Hook đ gửi manifest
*/
export function useSendManifest() {
return useMutation({
mutationFn: (data: { targets: string[]; MsiFileIds: number[] }) =>
appVersionService.sendManifest(data),
mutationFn: () => appVersionService.sendManifest(),
});
}

View File

@ -191,23 +191,16 @@ function AppsComponent() {
}
};
const handleSendManifest = async (roomNames: 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 file để gửi manifest!");
return;
}
const MsiFileIds = selectedRows.map((row: any) => row.original.id);
const handleSendManifest = async (targets: string[]) => {
// targets ignored for now — API sendall broadcasts to all devices via MQTT
// TODO: use targets when per-room/per-device manifest API is available
try {
await sendManifestMutation.mutateAsync({ targets: roomNames, MsiFileIds });
toast.success("Đã gửi manifest cho các phòng đã chọn!");
await sendManifestMutation.mutateAsync();
toast.success(
targets.length > 0
? `Đã gửi manifest cho ${targets.length} mục!`
: "Đã gửi manifest đến tất cả thiết bị!"
);
} catch (e) {
toast.error("Gửi manifest thất bại!");
}

View File

@ -131,14 +131,9 @@ export async function deleteFile(data: { MsiFileIds: number[] }): Promise<{ mess
}
/**
* [MOCK] Gửi manifest đến các phòng/thiết bị
* @param data - { targets: string[]; MsiFileIds: number[] }
* Gửi manifest (build + publish MQTT tới tất cả required files)
*/
export async function sendManifest(data: {
targets: string[];
MsiFileIds: number[];
}): Promise<{ message: string }> {
// TODO: replace with real endpoint
await new Promise((resolve) => setTimeout(resolve, 800));
return { message: `[MOCK] Manifest sent to ${data.targets.join(", ")}` };
export async function sendManifest(): Promise<{ status: string; message: string }> {
const response = await axios.post(API_ENDPOINTS.MANIFEST.SEND_ALL);
return response.data;
}

View File

@ -82,7 +82,7 @@ export function AppManagerTemplate<TData>({
pageSizeOptions = [5, 10, 15, 20],
}: AppManagerTemplateProps<TData>) {
const [dialogOpen, setDialogOpen] = useState(false);
const [dialogType, setDialogType] = useState<"room" | "device" | "download-room" | "download-device" | "manifest-room" | null>(null);
const [dialogType, setDialogType] = useState<"room" | "device" | "download-room" | "download-device" | "manifest-room" | "manifest-device" | null>(null);
const sortedData = useMemo(() => {
const firstItem = data?.[0] as { fileName?: string } | undefined;
@ -128,6 +128,7 @@ export function AppManagerTemplate<TData>({
}
};
const openManifestRoomDialog = () => {
if (rooms.length > 0 && onSendManifest) {
setDialogType("manifest-room");
@ -135,6 +136,18 @@ export function AppManagerTemplate<TData>({
}
};
const openManifestDeviceDialog = () => {
if (onSendManifest) {
setDialogType("manifest-device");
setDialogOpen(true);
}
};
const handleManifestAll = async () => {
if (!onSendManifest) return;
await onSendManifest([]);
};
const handleUpdateAll = async () => {
if (!onUpdate) return;
try {
@ -229,14 +242,16 @@ export function AppManagerTemplate<TData>({
</Button>
)}
{onSendManifest && (
<Button
onClick={openManifestRoomDialog}
disabled={sendManifestLoading}
variant="outline"
className="gap-2"
>
{sendManifestLoading ? "Đang gửi..." : "Gửi Manifest"}
</Button>
<RequestUpdateMenu
onUpdateAll={handleManifestAll}
onUpdateRoom={openManifestRoomDialog}
onUpdateDevice={openManifestDeviceDialog}
loading={sendManifestLoading}
label="Gửi Manifest"
allLabel="Gửi tất cả"
roomLabel="Gửi theo phòng"
deviceLabel="Gửi theo thiết bị"
/>
)}
</div>
{onDeleteFromServer && onDeleteFromRequired && (
@ -336,33 +351,8 @@ export function AppManagerTemplate<TData>({
/>
)}
{/* Dialog gửi manifest - chọn phòng */}
{dialogType === "manifest-room" && (
<SelectDialog
open={dialogOpen}
onClose={() => {
setDialogOpen(false);
setDialogType(null);
}}
title="Chọn phòng"
description="Chọn các phòng để gửi manifest"
icon={<Building2 className="w-6 h-6 text-primary" />}
items={mapRoomsToSelectItems(rooms)}
onConfirm={async (selectedItems) => {
if (!onSendManifest) return;
try {
await onSendManifest(selectedItems);
} catch (e) {
console.error("Send manifest error:", e);
} finally {
setDialogOpen(false);
setDialogType(null);
}
}}
/>
)}
{/* Dialog tải file - tìm thiết bị */}
{/* Dialog tải file - tìm thiết bị */}
{dialogType === "download-device" && (
<DeviceSearchDialog
open={dialogOpen && dialogType === "download-device"}
@ -391,6 +381,60 @@ export function AppManagerTemplate<TData>({
}}
/>
)}
{/* Dialog gửi manifest - chọn phòng */}
{dialogType === "manifest-room" && (
<SelectDialog
open={dialogOpen}
onClose={() => {
setDialogOpen(false);
setDialogType(null);
}}
title="Chọn phòng"
description="Chọn các phòng để gửi manifest"
icon={<Building2 className="w-6 h-6 text-primary" />}
items={mapRoomsToSelectItems(rooms)}
onConfirm={async (selectedItems) => {
if (!onSendManifest) return;
try {
await onSendManifest(selectedItems);
} catch (e) {
console.error("Send manifest error:", e);
} finally {
setDialogOpen(false);
setDialogType(null);
}
}}
/>
)}
{/* Dialog gửi manifest - chọn thiết bị */}
{dialogType === "manifest-device" && (
<DeviceSearchDialog
open={dialogOpen && dialogType === "manifest-device"}
onClose={() => {
setDialogOpen(false);
setDialogType(null);
}}
rooms={rooms}
fetchDevices={getDeviceFromRoom}
onSelect={async (deviceIds) => {
if (!onSendManifest) {
setDialogOpen(false);
setDialogType(null);
return;
}
try {
await onSendManifest(deviceIds);
} catch (e) {
console.error("Send manifest error:", e);
} finally {
setDialogOpen(false);
setDialogType(null);
}
}}
/>
)}
</div>
);
}