d6616c5859
- feat: Add slider verification code (bd67997) - fix bug: Inventory cannot be zero (1f7a6ee) - fix: resolve merge conflicts and lint errors
193 lines
5.5 KiB
TypeScript
193 lines
5.5 KiB
TypeScript
"use client";
|
|
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { Button } from "@workspace/ui/components/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@workspace/ui/components/dialog";
|
|
import { Label } from "@workspace/ui/components/label";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@workspace/ui/components/select";
|
|
import {
|
|
bindNodeGroups,
|
|
getNodeGroupList,
|
|
} from "@workspace/ui/services/admin/group";
|
|
import { Loader2 } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { toast } from "sonner";
|
|
|
|
interface BindNodeGroupsDialogProps {
|
|
userGroupIds: number[];
|
|
userGroupNames: string[];
|
|
onOpenChange?: (open: boolean) => void;
|
|
onSuccess?: () => void;
|
|
}
|
|
|
|
export default function BindNodeGroupsDialog({
|
|
userGroupIds,
|
|
userGroupNames,
|
|
onOpenChange,
|
|
onSuccess,
|
|
}: BindNodeGroupsDialogProps) {
|
|
const { t } = useTranslation("group");
|
|
const [open, setOpen] = useState(false);
|
|
const [saving, setSaving] = useState(false);
|
|
const [selectedNodeGroupId, setSelectedNodeGroupId] = useState<
|
|
number | undefined
|
|
>();
|
|
|
|
const { data: nodeGroupsData, isLoading } = useQuery({
|
|
queryKey: ["nodeGroups"],
|
|
queryFn: async () => {
|
|
const { data } = await getNodeGroupList({ page: 1, size: 1000 });
|
|
return data.data?.list || [];
|
|
},
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (open && nodeGroupsData) {
|
|
// Load current binding when dialog opens
|
|
loadCurrentBinding();
|
|
}
|
|
}, [open]);
|
|
|
|
const loadCurrentBinding = () => {
|
|
// Get first user group's current node group binding
|
|
// For batch binding, we'll default to unbound
|
|
setSelectedNodeGroupId(undefined);
|
|
};
|
|
|
|
const handleBind = async () => {
|
|
if (selectedNodeGroupId === undefined) {
|
|
toast.error(t("selectNodeGroupRequired", "Please select a node group"));
|
|
return;
|
|
}
|
|
|
|
setSaving(true);
|
|
try {
|
|
await bindNodeGroups({
|
|
user_group_ids: userGroupIds,
|
|
node_group_id: selectedNodeGroupId === 0 ? null : selectedNodeGroupId,
|
|
} as API.BindNodeGroupsRequest);
|
|
|
|
toast.success(
|
|
t(
|
|
"bindSuccess",
|
|
"Successfully bound {{userGroupCount}} user groups to node group"
|
|
).replace(/{{userGroupCount}}/g, String(userGroupIds.length))
|
|
);
|
|
|
|
setOpen(false);
|
|
onOpenChange?.(false);
|
|
onSuccess?.();
|
|
} catch (error) {
|
|
console.error("Failed to bind node group:", error);
|
|
toast.error(t("bindFailed", "Failed to bind node group"));
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
const displayNames =
|
|
userGroupNames.length > 2
|
|
? `${userGroupNames.slice(0, 2).join(", ")}... (${userGroupIds.length})`
|
|
: userGroupNames.join(", ");
|
|
|
|
return (
|
|
<Dialog
|
|
onOpenChange={(newOpen) => {
|
|
setOpen(newOpen);
|
|
onOpenChange?.(newOpen);
|
|
}}
|
|
open={open}
|
|
>
|
|
<DialogTrigger asChild>
|
|
<Button size="sm" variant="outline">
|
|
{t("bindNodeGroup", "Bind Node Group")}
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent className="max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle>{t("bindNodeGroup", "Bind Node Group")}</DialogTitle>
|
|
<DialogDescription>
|
|
{t(
|
|
"bindNodeGroupDescription",
|
|
"Select a node group to bind to user groups: {{userGroups}}",
|
|
{ userGroups: displayNames }
|
|
).replace(/{{userGroups}}/g, displayNames)}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4 py-4">
|
|
{isLoading ? (
|
|
<div className="flex justify-center py-8">
|
|
<Loader2 className="h-6 w-6 animate-spin" />
|
|
</div>
|
|
) : (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="node-group">
|
|
{t("selectNodeGroup", "Select Node Group")}
|
|
</Label>
|
|
<Select
|
|
onValueChange={(val) =>
|
|
setSelectedNodeGroupId(Number.parseInt(val, 10) || undefined)
|
|
}
|
|
value={selectedNodeGroupId?.toString() || ""}
|
|
>
|
|
<SelectTrigger className="w-full" id="node-group">
|
|
<SelectValue
|
|
placeholder={t(
|
|
"selectNodeGroupPlaceholder",
|
|
"Select a node group..."
|
|
)}
|
|
/>
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="0">{t("unbound", "Unbound")}</SelectItem>
|
|
{nodeGroupsData?.map((nodeGroup) => (
|
|
<SelectItem key={nodeGroup.id} value={String(nodeGroup.id)}>
|
|
{nodeGroup.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
disabled={saving}
|
|
onClick={() => {
|
|
setOpen(false);
|
|
onOpenChange?.(false);
|
|
}}
|
|
variant="outline"
|
|
>
|
|
{t("cancel", "Cancel")}
|
|
</Button>
|
|
<Button
|
|
disabled={saving || selectedNodeGroupId === undefined}
|
|
onClick={handleBind}
|
|
>
|
|
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
{t("confirm", "Confirm")}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|