refactor(group): remove deprecated user group components

Remove unused user group management components as user groups have been deprecated in favor of node groups only.
This commit is contained in:
EUForest
2026-03-08 23:42:39 +08:00
parent ae31019477
commit 4b4edd48e3
4 changed files with 0 additions and 824 deletions
@@ -1,210 +0,0 @@
"use client";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@workspace/ui/components/dialog";
import { Input } from "@workspace/ui/components/input";
import { Label } from "@workspace/ui/components/label";
import { Textarea } from "@workspace/ui/components/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@workspace/ui/components/select";
import { Switch } from "@workspace/ui/components/switch";
import { Loader2 } from "lucide-react";
import { forwardRef, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
interface UserGroupFormProps {
initialValues?: Partial<API.UserGroup>;
loading?: boolean;
nodeGroups?: API.NodeGroup[];
onSubmit: (values: Record<string, unknown>) => Promise<boolean>;
title: string;
trigger: React.ReactNode;
}
const UserGroupForm = forwardRef<
HTMLButtonElement,
UserGroupFormProps
>(({ initialValues, loading, nodeGroups = [], onSubmit, title, trigger }, ref) => {
const { t } = useTranslation("group");
const [open, setOpen] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [values, setValues] = useState({
name: "",
description: "",
sort: 0,
node_group_id: null as number | null,
for_calculation: true,
});
useEffect(() => {
if (open) {
if (initialValues) {
setValues({
name: initialValues.name || "",
description: initialValues.description || "",
sort: initialValues.sort ?? 0,
node_group_id: initialValues.node_group_id || null,
for_calculation: initialValues.for_calculation ?? true,
});
} else {
setValues({
name: "",
description: "",
sort: 0,
node_group_id: null,
for_calculation: true,
});
}
}
}, [initialValues, open]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSubmitting(true);
const success = await onSubmit(values);
setSubmitting(false);
if (success) {
setOpen(false);
setValues({
name: "",
description: "",
sort: 0,
node_group_id: null,
for_calculation: true,
});
}
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild ref={ref}>
{trigger}
</DialogTrigger>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>
{t("userGroupFormDescription", "Configure user group settings")}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">
{t("name", "Name")} *
</Label>
<Input
id="name"
value={values.name}
onChange={(e) =>
setValues({ ...values, name: e.target.value })
}
placeholder={t("namePlaceholder", "Enter name")}
required
/>
</div>
<div className="flex items-center justify-between space-x-2">
<Label htmlFor="for_calculation">{t("forCalculation", "For Calculation")}</Label>
<Switch
id="for_calculation"
checked={values.for_calculation}
onCheckedChange={(checked) =>
setValues({ ...values, for_calculation: checked })
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="description">
{t("description", "Description")}
</Label>
<Textarea
id="description"
value={values.description}
onChange={(e) =>
setValues({ ...values, description: e.target.value })
}
placeholder={t("descriptionPlaceholder", "Enter description")}
rows={3}
/>
</div>
<div className="space-y-2">
<Label htmlFor="node_group_id">{t("nodeGroup", "Node Group")}</Label>
<Select
value={values.node_group_id ? String(values.node_group_id) : "0"}
onValueChange={(val) =>
setValues({
...values,
node_group_id: val === "0" ? null : parseInt(val),
})
}
>
<SelectTrigger id="node_group_id" className="w-full">
<SelectValue placeholder={t("selectNodeGroupPlaceholder", "Select a node group...")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="0">
{t("unbound", "Unbound")}
</SelectItem>
{nodeGroups.map((nodeGroup) => (
<SelectItem key={nodeGroup.id} value={String(nodeGroup.id)}>
{nodeGroup.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="sort">{t("sort", "Sort Order")}</Label>
<Input
id="sort"
type="number"
value={values.sort}
onChange={(e) =>
setValues({ ...values, sort: parseInt(e.target.value) || 0 })
}
min={0}
/>
</div>
<div className="flex justify-end gap-2">
<button
type="button"
onClick={() => setOpen(false)}
className="rounded-md border px-4 py-2 text-sm"
disabled={submitting || loading}
>
{t("cancel", "Cancel")}
</button>
<button
type="submit"
disabled={submitting || loading}
className="flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground disabled:opacity-50"
>
{submitting && <Loader2 className="h-4 w-4 animate-spin" />}
{t("save", "Save")}
</button>
</div>
</form>
</DialogContent>
</Dialog>
);
});
UserGroupForm.displayName = "UserGroupForm";
export default UserGroupForm;
@@ -1,200 +0,0 @@
"use client";
import { Button } from "@workspace/ui/components/button";
import { Badge } from "@workspace/ui/components/badge";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@workspace/ui/components/card";
import { ConfirmButton } from "@workspace/ui/composed/confirm-button";
import {
ProTable,
type ProTableActions,
} from "@workspace/ui/composed/pro-table/pro-table";
import {
createUserGroup,
deleteUserGroup,
getUserGroupList,
getNodeGroupList,
updateUserGroup,
} from "@workspace/ui/services/admin/group";
import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useQuery } from "@tanstack/react-query";
import { toast } from "sonner";
import UserGroupForm from "./user-group-form";
export default function UserGroups() {
const { t } = useTranslation("group");
const [loading, setLoading] = useState(false);
const ref = useRef<ProTableActions>(null);
const { data: nodeGroupsData } = useQuery({
queryKey: ["nodeGroups"],
queryFn: async () => {
const { data } = await getNodeGroupList({ page: 1, size: 1000 });
return data.data?.list || [];
},
});
return (
<div className="space-y-4">
<Card>
<CardHeader>
<CardTitle>{t("userGroups", "User Groups")}</CardTitle>
<CardDescription>
{t("userGroupsDescription", "Manage user groups for node access control")}
</CardDescription>
</CardHeader>
<CardContent>
<ProTable<API.UserGroup, API.GetUserGroupListRequest>
action={ref}
request={async (params) => {
const { data } = await getUserGroupList({
page: params.page || 1,
size: params.size || 10,
});
return {
list: data.data?.list || [],
total: data.data?.total || 0,
};
}}
columns={[
{
id: "id",
accessorKey: "id",
header: t("id", "ID"),
cell: ({ row }: { row: any }) => <span className="text-muted-foreground">#{row.getValue("id")}</span>,
},
{
id: "name",
accessorKey: "name",
header: t("name", "Name"),
},
{
id: "for_calculation",
accessorKey: "for_calculation",
header: t("forCalculation", "For Calculation"),
cell: ({ row }: { row: any }) => {
const forCalculation = row.getValue("for_calculation");
return forCalculation ? (
<Badge variant="default">{t("yes", "Yes")}</Badge>
) : (
<Badge variant="secondary">{t("no", "No")}</Badge>
);
},
},
{
id: "description",
accessorKey: "description",
header: t("description", "Description"),
cell: ({ row }: { row: any }) => row.getValue("description") || "--",
},
{
id: "node_group_id",
accessorKey: "node_group_id",
header: t("nodeGroup", "Node Group"),
cell: ({ row }: { row: any }) => {
const nodeGroupId = row.getValue("node_group_id");
const group = nodeGroupsData?.find((g) => g.id === nodeGroupId);
return group?.name || "--";
},
},
{
id: "sort",
accessorKey: "sort",
header: t("sort", "Sort"),
},
]}
actions={{
render: (row: any) => [
<UserGroupForm
key={`edit-${row.id}`}
initialValues={row}
loading={loading}
nodeGroups={nodeGroupsData || []}
onSubmit={async (values) => {
setLoading(true);
try {
await updateUserGroup({
id: row.id,
...values,
} as unknown as API.UpdateUserGroupRequest);
toast.success(t("updated", "Updated successfully"));
ref.current?.refresh();
setLoading(false);
return true;
} catch {
setLoading(false);
return false;
}
}}
title={t("editUserGroup", "Edit User Group")}
trigger={
<Button variant="outline" size="sm">
{t("edit", "Edit")}
</Button>
}
/>,
<ConfirmButton
key="delete"
cancelText={t("cancel", "Cancel")}
confirmText={t("confirm", "Confirm")}
description={t(
"deleteUserGroupConfirm",
"This will delete the user group. Users in this group will be reassigned to the default group."
)}
onConfirm={async () => {
await deleteUserGroup({ id: row.id });
toast.success(t("deleted", "Deleted successfully"));
ref.current?.refresh();
setLoading(false);
}}
title={t("confirmDelete", "Confirm Delete")}
trigger={
<Button variant="destructive" size="sm">
{t("delete", "Delete")}
</Button>
}
/>,
],
}}
header={{
title: t("userGroups", "User Groups"),
toolbar: (
<UserGroupForm
key="create"
initialValues={undefined}
loading={loading}
nodeGroups={nodeGroupsData || []}
onSubmit={async (values) => {
setLoading(true);
try {
await createUserGroup(values as API.CreateUserGroupRequest);
toast.success(t("created", "Created successfully"));
ref.current?.refresh();
setLoading(false);
return true;
} catch {
setLoading(false);
return false;
}
}}
title={t("createUserGroup", "Create User Group")}
trigger={
<Button>
{t("create", "Create")}
</Button>
}
/>
),
}}
/>
</CardContent>
</Card>
</div>
);
}