diff --git a/app/(dashboard)/users/page.tsx b/app/(dashboard)/users/page.tsx index dc2d389..ec607a1 100644 --- a/app/(dashboard)/users/page.tsx +++ b/app/(dashboard)/users/page.tsx @@ -16,6 +16,7 @@ import { useDataTable } from "@/hooks/use-data-table" import { usePermissions } from "@/hooks/use-permissions" import { UserNewForm } from "@/components/user/new-form" import { UserEditForm } from "@/components/user/edit-form" +import { UserAddToGroupForm } from "@/components/user/add-to-group-form" import { useUsers } from "@/hooks/use-users" import { useDialog } from "@/lib/feedback/dialog" import { useMessage } from "@/lib/feedback/message" @@ -64,6 +65,7 @@ export default function UsersPage() { const [searchTerm, setSearchTerm] = useState("") const [newFormOpen, setNewFormOpen] = useState(false) const [editFormOpen, setEditFormOpen] = useState(false) + const [addToGroupOpen, setAddToGroupOpen] = useState(false) const [editRow, setEditRow] = useState(null) const getDataList = React.useCallback(async () => { @@ -212,7 +214,7 @@ export default function UsersPage() { } const addToGroup = () => { - message.error(t("Add to Group") + " - " + t("Coming soon")) + setAddToGroupOpen(true) } return ( @@ -269,6 +271,15 @@ export default function UsersPage() { + { + table.resetRowSelection() + getDataList() + }} + /> ) diff --git a/components/user/add-to-group-form.tsx b/components/user/add-to-group-form.tsx new file mode 100644 index 0000000..51ceb3b --- /dev/null +++ b/components/user/add-to-group-form.tsx @@ -0,0 +1,101 @@ +"use client" + +import * as React from "react" +import { useTranslation } from "react-i18next" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { Spinner } from "@/components/ui/spinner" +import { useGroups } from "@/hooks/use-groups" +import { useMessage } from "@/lib/feedback/message" +import { buildAddUsersToGroupsRequests } from "@/lib/user-group-memberships" +import { UserEditGroups } from "./edit/groups" + +interface UserAddToGroupFormProps { + open: boolean + onOpenChange: (open: boolean) => void + selectedUsers: string[] + onSuccess: () => void +} + +export function UserAddToGroupForm({ open, onOpenChange, selectedUsers, onSuccess }: UserAddToGroupFormProps) { + const { t } = useTranslation() + const message = useMessage() + const { listGroup, updateGroupMembers } = useGroups() + const [groups, setGroups] = React.useState([]) + const [groupsList, setGroupsList] = React.useState<{ label: string; value: string }[]>([]) + const [loading, setLoading] = React.useState(false) + const [submitting, setSubmitting] = React.useState(false) + + React.useEffect(() => { + if (!open) return + + setGroups([]) + setLoading(true) + listGroup() + .then((res) => { + setGroupsList((res as string[] | undefined)?.map((group) => ({ label: group, value: group })) ?? []) + }) + .catch(() => { + message.error(t("Failed to get data")) + }) + .finally(() => setLoading(false)) + }, [listGroup, message, open, t]) + + const closeModal = () => onOpenChange(false) + + const submitForm = async () => { + const requests = buildAddUsersToGroupsRequests(selectedUsers, groups) + if (!requests.length) { + message.error(t("Please select at least one item")) + return + } + + setSubmitting(true) + try { + await Promise.all(requests.map((request) => updateGroupMembers(request))) + message.success(t("Edit Success")) + onOpenChange(false) + onSuccess() + } catch (error) { + console.error(error) + message.error((error as Error)?.message || t("Edit Failed")) + } finally { + setSubmitting(false) + } + } + + return ( + + e.preventDefault()}> + + {t("Add to Group")} + + +
+
+
{t("Users")}
+
+ {selectedUsers.map((user) => ( + + {user} + + ))} +
+
+ +
+ + + + + +
+
+ ) +} diff --git a/lib/user-group-memberships.js b/lib/user-group-memberships.js new file mode 100644 index 0000000..a607798 --- /dev/null +++ b/lib/user-group-memberships.js @@ -0,0 +1,13 @@ +export function buildAddUsersToGroupsRequests(users, groups) { + const normalizedUsers = users.map((user) => user.trim()).filter(Boolean) + const normalizedGroups = groups.map((group) => group.trim()).filter(Boolean) + + return normalizedUsers.flatMap((user) => + normalizedGroups.map((group) => ({ + group, + members: [user], + isRemove: false, + groupStatus: "enabled", + })), + ) +} diff --git a/lib/user-group-memberships.ts b/lib/user-group-memberships.ts new file mode 100644 index 0000000..29f3d1e --- /dev/null +++ b/lib/user-group-memberships.ts @@ -0,0 +1,20 @@ +export interface UpdateGroupMembersRequest { + group: string + members: string[] + isRemove: boolean + groupStatus: string +} + +export function buildAddUsersToGroupsRequests(users: string[], groups: string[]): UpdateGroupMembersRequest[] { + const normalizedUsers = users.map((user) => user.trim()).filter(Boolean) + const normalizedGroups = groups.map((group) => group.trim()).filter(Boolean) + + return normalizedUsers.flatMap((user) => + normalizedGroups.map((group) => ({ + group, + members: [user], + isRemove: false, + groupStatus: "enabled", + })), + ) +} diff --git a/tests/lib/user-group-memberships.test.js b/tests/lib/user-group-memberships.test.js new file mode 100644 index 0000000..e89b6b2 --- /dev/null +++ b/tests/lib/user-group-memberships.test.js @@ -0,0 +1,44 @@ +import test from "node:test" +import assert from "node:assert/strict" + +import { buildAddUsersToGroupsRequests } from "../../lib/user-group-memberships.js" + +test("buildAddUsersToGroupsRequests creates one add-members request per selected user and group", () => { + assert.deepEqual(buildAddUsersToGroupsRequests(["alice", "bob"], ["admins", "auditors"]), [ + { + group: "admins", + members: ["alice"], + isRemove: false, + groupStatus: "enabled", + }, + { + group: "auditors", + members: ["alice"], + isRemove: false, + groupStatus: "enabled", + }, + { + group: "admins", + members: ["bob"], + isRemove: false, + groupStatus: "enabled", + }, + { + group: "auditors", + members: ["bob"], + isRemove: false, + groupStatus: "enabled", + }, + ]) +}) + +test("buildAddUsersToGroupsRequests ignores blank users and groups", () => { + assert.deepEqual(buildAddUsersToGroupsRequests([" alice ", ""], [" admins ", ""]), [ + { + group: "admins", + members: ["alice"], + isRemove: false, + groupStatus: "enabled", + }, + ]) +})