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_REQUIRED_FILE: `${BASE_URL}/AppVersion/requirefile/delete`,
DELETE_FILES: `${BASE_URL}/AppVersion/delete`, DELETE_FILES: `${BASE_URL}/AppVersion/delete`,
}, },
MANIFEST: {
SEND_ALL: `${BASE_URL}/Manifest/sendall`,
},
DEVICE_COMM: { DEVICE_COMM: {
DOWNLOAD_FILES: (roomName: string) => `${BASE_URL}/DeviceComm/downloadfile/${roomName}`, DOWNLOAD_FILES: (roomName: string) => `${BASE_URL}/DeviceComm/downloadfile/${roomName}`,
INSTALL_MSI: (roomName: string) => `${BASE_URL}/DeviceComm/installmsi/${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() { export function useSendManifest() {
return useMutation({ return useMutation({
mutationFn: (data: { targets: string[]; MsiFileIds: number[] }) => mutationFn: () => appVersionService.sendManifest(),
appVersionService.sendManifest(data),
}); });
} }

View File

@ -191,23 +191,16 @@ function AppsComponent() {
} }
}; };
const handleSendManifest = async (roomNames: string[]) => { const handleSendManifest = async (targets: string[]) => {
if (!table) { // targets ignored for now — API sendall broadcasts to all devices via MQTT
toast.error("Không thể lấy thông tin bảng!"); // TODO: use targets when per-room/per-device manifest API is available
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);
try { try {
await sendManifestMutation.mutateAsync({ targets: roomNames, MsiFileIds }); await sendManifestMutation.mutateAsync();
toast.success("Đã gửi manifest cho các phòng đã chọn!"); 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) { } catch (e) {
toast.error("Gửi manifest thất bại!"); 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ị * Gửi manifest (build + publish MQTT tới tất cả required files)
* @param data - { targets: string[]; MsiFileIds: number[] }
*/ */
export async function sendManifest(data: { export async function sendManifest(): Promise<{ status: string; message: string }> {
targets: string[]; const response = await axios.post(API_ENDPOINTS.MANIFEST.SEND_ALL);
MsiFileIds: number[]; return response.data;
}): Promise<{ message: string }> {
// TODO: replace with real endpoint
await new Promise((resolve) => setTimeout(resolve, 800));
return { message: `[MOCK] Manifest sent to ${data.targets.join(", ")}` };
} }

View File

@ -82,7 +82,7 @@ export function AppManagerTemplate<TData>({
pageSizeOptions = [5, 10, 15, 20], pageSizeOptions = [5, 10, 15, 20],
}: AppManagerTemplateProps<TData>) { }: AppManagerTemplateProps<TData>) {
const [dialogOpen, setDialogOpen] = useState(false); 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 sortedData = useMemo(() => {
const firstItem = data?.[0] as { fileName?: string } | undefined; const firstItem = data?.[0] as { fileName?: string } | undefined;
@ -128,6 +128,7 @@ export function AppManagerTemplate<TData>({
} }
}; };
const openManifestRoomDialog = () => { const openManifestRoomDialog = () => {
if (rooms.length > 0 && onSendManifest) { if (rooms.length > 0 && onSendManifest) {
setDialogType("manifest-room"); 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 () => { const handleUpdateAll = async () => {
if (!onUpdate) return; if (!onUpdate) return;
try { try {
@ -229,14 +242,16 @@ export function AppManagerTemplate<TData>({
</Button> </Button>
)} )}
{onSendManifest && ( {onSendManifest && (
<Button <RequestUpdateMenu
onClick={openManifestRoomDialog} onUpdateAll={handleManifestAll}
disabled={sendManifestLoading} onUpdateRoom={openManifestRoomDialog}
variant="outline" onUpdateDevice={openManifestDeviceDialog}
className="gap-2" loading={sendManifestLoading}
> label="Gửi Manifest"
{sendManifestLoading ? "Đang gửi..." : "Gửi Manifest"} allLabel="Gửi tất cả"
</Button> roomLabel="Gửi theo phòng"
deviceLabel="Gửi theo thiết bị"
/>
)} )}
</div> </div>
{onDeleteFromServer && onDeleteFromRequired && ( {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" && ( {dialogType === "download-device" && (
<DeviceSearchDialog <DeviceSearchDialog
open={dialogOpen && dialogType === "download-device"} 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> </div>
); );
} }