From 10185d4ef13b29b55d813d89e8d2818deed13dd9 Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Mon, 10 Feb 2025 14:20:06 +0300 Subject: [PATCH 01/74] Add organization page component for authenticated users --- .../app/(authenticated)/organization/page.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 netmanager-app/app/(authenticated)/organization/page.tsx diff --git a/netmanager-app/app/(authenticated)/organization/page.tsx b/netmanager-app/app/(authenticated)/organization/page.tsx new file mode 100644 index 0000000000..0d19ad04c6 --- /dev/null +++ b/netmanager-app/app/(authenticated)/organization/page.tsx @@ -0,0 +1,19 @@ +import { useAppSelector } from "@/core/redux/hooks" +import { redirect } from "next/navigation" +import { OrganizationList } from "./organization-list" + +export const OrganizationSettingsPage =() => { + const user = useAppSelector((state) => state.user.userDetails) + + if (!user || user.role !== "AIRQO_SUPER_ADMIN") { + redirect("/") + } + + return ( +
+

Organization Settings

+ +
+ ) +} + From e006f8fe55d5db2ac7e14ff7c46873be60cb7555 Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Mon, 10 Feb 2025 19:08:47 +0300 Subject: [PATCH 02/74] Add organization management components and API integration --- .../organizations/[id]/page.tsx | 37 +++++++ .../{organization => organizations}/page.tsx | 0 .../components/Organization/List.tsx | 71 ++++++++++++ .../Organization/organization-profile.tsx | 0 .../Organization/organization-roles.tsx | 87 +++++++++++++++ .../components/Organization/team-members.tsx | 101 ++++++++++++++++++ netmanager-app/core/apis/organizations.ts | 66 ++++++++++++ 7 files changed, 362 insertions(+) create mode 100644 netmanager-app/app/(authenticated)/organizations/[id]/page.tsx rename netmanager-app/app/(authenticated)/{organization => organizations}/page.tsx (100%) create mode 100644 netmanager-app/components/Organization/List.tsx create mode 100644 netmanager-app/components/Organization/organization-profile.tsx create mode 100644 netmanager-app/components/Organization/organization-roles.tsx create mode 100644 netmanager-app/components/Organization/team-members.tsx create mode 100644 netmanager-app/core/apis/organizations.ts diff --git a/netmanager-app/app/(authenticated)/organizations/[id]/page.tsx b/netmanager-app/app/(authenticated)/organizations/[id]/page.tsx new file mode 100644 index 0000000000..e5a15a8765 --- /dev/null +++ b/netmanager-app/app/(authenticated)/organizations/[id]/page.tsx @@ -0,0 +1,37 @@ +import { redirect } from "next/navigation" +import { getCurrentUser } from "@/lib/session" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { OrganizationProfile } from "./organization-profile" +import { TeamMembers } from "./team-members" +import { OrganizationRoles } from "./organization-roles" + +export default async function OrganizationDetailsPage({ params }: { params: { id: string } }) { + const user = await getCurrentUser() + + if (!user || user.role !== "AIRQO_SUPER_ADMIN") { + redirect("/") + } + + return ( +
+

Organization Details

+ + + Organization Profile + Team Members + Organization Roles + + + + + + + + + + + +
+ ) +} + diff --git a/netmanager-app/app/(authenticated)/organization/page.tsx b/netmanager-app/app/(authenticated)/organizations/page.tsx similarity index 100% rename from netmanager-app/app/(authenticated)/organization/page.tsx rename to netmanager-app/app/(authenticated)/organizations/page.tsx diff --git a/netmanager-app/components/Organization/List.tsx b/netmanager-app/components/Organization/List.tsx new file mode 100644 index 0000000000..365b7f3763 --- /dev/null +++ b/netmanager-app/components/Organization/List.tsx @@ -0,0 +1,71 @@ +"use client" + +import { useEffect, useState } from "react" +import Link from "next/link" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { useAppSelector, useAppDispatch } from "@/core/redux/hooks" +import { fetchOrganizations } from "@/lib/slices/organizationsSlice" + +export function OrganizationList() { + const dispatch = useAppDispatch() + const organizations = useAppSelector((state) => state.organizations.list) + const status = useAppSelector((state) => state.organizations.status) + const [searchTerm, setSearchTerm] = useState("") + + useEffect(() => { + if (status === "idle") { + dispatch(fetchOrganizations()) + } + }, [status, dispatch]) + + const filteredOrganizations = organizations.filter((org) => org.name.toLowerCase().includes(searchTerm.toLowerCase())) + + if (status === "loading") { + return
Loading...
+ } + + if (status === "failed") { + return
Error loading organizations. Please try again.
+ } + + return ( +
+
+ setSearchTerm(e.target.value)} + /> + +
+ + + + Name + Created At + Actions + + + + {filteredOrganizations.map((org) => ( + + {org.name} + {new Date(org.createdAt).toLocaleDateString()} + + + + + ))} + +
+
+ ) +} + diff --git a/netmanager-app/components/Organization/organization-profile.tsx b/netmanager-app/components/Organization/organization-profile.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/netmanager-app/components/Organization/organization-roles.tsx b/netmanager-app/components/Organization/organization-roles.tsx new file mode 100644 index 0000000000..2a826ea53c --- /dev/null +++ b/netmanager-app/components/Organization/organization-roles.tsx @@ -0,0 +1,87 @@ +"use client" + +import { useEffect, useState } from "react" +import { useDispatch, useSelector } from "react-redux" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import type { AppDispatch, RootState } from "@/lib/store" +import { fetchRoles, createRole, updateRole } from "@/lib/slices/rolesSlice" + +type OrganizationRolesProps = { + organizationId: string +} + +export function OrganizationRoles({ organizationId }: OrganizationRolesProps) { + const dispatch = useDispatch() + const roles = useSelector((state: RootState) => state.roles.list) + const status = useSelector((state: RootState) => state.roles.status) + const [newRoleName, setNewRoleName] = useState("") + + useEffect(() => { + if (status === "idle") { + dispatch(fetchRoles(organizationId)) + } + }, [status, dispatch, organizationId]) + + const handleCreateRole = (e: React.FormEvent) => { + e.preventDefault() + dispatch(createRole({ organizationId, name: newRoleName })) + setNewRoleName("") + } + + const handleUpdateRole = (roleId: string, newName: string) => { + dispatch(updateRole({ id: roleId, name: newName })) + } + + if (status === "loading") { + return
Loading...
+ } + + if (status === "failed") { + return
Error loading roles. Please try again.
+ } + + return ( +
+
+
+ + setNewRoleName(e.target.value)} required /> +
+ +
+ + + + + Role Name + Permissions + Actions + + + + {roles.map((role) => ( + + {role.name} + {role.permissions.join(", ")} + + + + + ))} + +
+
+ ) +} + diff --git a/netmanager-app/components/Organization/team-members.tsx b/netmanager-app/components/Organization/team-members.tsx new file mode 100644 index 0000000000..b3c46e3bd0 --- /dev/null +++ b/netmanager-app/components/Organization/team-members.tsx @@ -0,0 +1,101 @@ +"use client" + +import { useEffect, useState } from "react" +import { useDispatch, useSelector } from "react-redux" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import type { AppDispatch, RootState } from "@/lib/store" +import { fetchMembers, inviteMember, updateMemberRole } from "@/lib/slices/membersSlice" + +type TeamMembersProps = { + organizationId: string +} + +export function TeamMembers({ organizationId }: TeamMembersProps) { + const dispatch = useDispatch() + const members = useSelector((state: RootState) => state.members.list) + const status = useSelector((state: RootState) => state.members.status) + const [newMemberEmail, setNewMemberEmail] = useState("") + + useEffect(() => { + if (status === "idle") { + dispatch(fetchMembers(organizationId)) + } + }, [status, dispatch, organizationId]) + + const handleInvite = (e: React.FormEvent) => { + e.preventDefault() + dispatch(inviteMember({ organizationId, email: newMemberEmail })) + setNewMemberEmail("") + } + + const handleUpdateRole = (memberId: string, newRole: string) => { + dispatch(updateMemberRole({ memberId, role: newRole })) + } + + if (status === "loading") { + return
Loading...
+ } + + if (status === "failed") { + return
Error loading members. Please try again.
+ } + + return ( +
+
+ setNewMemberEmail(e.target.value)} + required + /> + +
+ + + + + Name + Email + Role + Actions + + + + {members.map((member) => ( + + {member.name} + {member.email} + + + + + + + + ))} + +
+
+ ) +} + diff --git a/netmanager-app/core/apis/organizations.ts b/netmanager-app/core/apis/organizations.ts new file mode 100644 index 0000000000..97762b3807 --- /dev/null +++ b/netmanager-app/core/apis/organizations.ts @@ -0,0 +1,66 @@ +import createAxiosInstance from "./axiosConfig"; +import { DEVICES_MGT_URL } from "../urls"; +import { AxiosError } from "axios"; +import { CreateGrid } from "@/app/types/grids"; + +const axiosInstance = createAxiosInstance(); + +interface ErrorResponse { + message: string; +} + +export const grids = { + getGridsApi: async (networkId: string) => { + try { + const response = await axiosInstance.get( + `${DEVICES_MGT_URL}/grids/summary?network=${networkId}` + ); + return response.data; + } catch (error) { + const axiosError = error as AxiosError; + throw new Error( + axiosError.response?.data?.message || "Failed to fetch grids summary" + ); + } + }, + getGridDetailsApi: async (gridId: string) => { + try { + const response = await axiosInstance.get( + `${DEVICES_MGT_URL}/grids/${gridId}` + ); + return response.data; + } catch (error) { + const axiosError = error as AxiosError; + throw new Error( + axiosError.response?.data?.message || "Failed to fetch grid details" + ); + } + }, + updateGridDetailsApi: async (gridId: string) => { + try { + const response = await axiosInstance.put( + `${DEVICES_MGT_URL}/grids/${gridId}` + ); + return response.data; + } catch (error) { + const axiosError = error as AxiosError; + throw new Error( + axiosError.response?.data?.message || "Failed to update grid details" + ); + } + }, + createGridApi: async (data: CreateGrid) => { + try { + const response = await axiosInstance.post( + `${DEVICES_MGT_URL}/grids`, + data + ); + return response.data; + } catch (error) { + const axiosError = error as AxiosError; + throw new Error( + axiosError.response?.data?.message || "Failed to create grid" + ); + } + }, +}; From 9be6e0b6e17ce357a326a7238cc9b4c31e7a4893 Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Mon, 10 Feb 2025 19:38:32 +0300 Subject: [PATCH 03/74] Refactor API endpoints to use USERS_MGT_URL for group management --- netmanager-app/core/apis/organizations.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/netmanager-app/core/apis/organizations.ts b/netmanager-app/core/apis/organizations.ts index 97762b3807..ecf7270868 100644 --- a/netmanager-app/core/apis/organizations.ts +++ b/netmanager-app/core/apis/organizations.ts @@ -1,5 +1,5 @@ import createAxiosInstance from "./axiosConfig"; -import { DEVICES_MGT_URL } from "../urls"; +import { USERS_MGT_URL } from "../urls"; import { AxiosError } from "axios"; import { CreateGrid } from "@/app/types/grids"; @@ -10,10 +10,10 @@ interface ErrorResponse { } export const grids = { - getGridsApi: async (networkId: string) => { + getGroupsApi: async () => { try { const response = await axiosInstance.get( - `${DEVICES_MGT_URL}/grids/summary?network=${networkId}` + `${USERS_MGT_URL}/groups/summary` ); return response.data; } catch (error) { @@ -23,10 +23,10 @@ export const grids = { ); } }, - getGridDetailsApi: async (gridId: string) => { + getGroupDetailsApi: async (gridId: string) => { try { const response = await axiosInstance.get( - `${DEVICES_MGT_URL}/grids/${gridId}` + `${USERS_MGT_URL}/grids/${gridId}` ); return response.data; } catch (error) { @@ -36,10 +36,10 @@ export const grids = { ); } }, - updateGridDetailsApi: async (gridId: string) => { + updateGroupDetailsApi: async (gridId: string) => { try { const response = await axiosInstance.put( - `${DEVICES_MGT_URL}/grids/${gridId}` + `${USERS_MGT_URL}/grids/${gridId}` ); return response.data; } catch (error) { @@ -49,10 +49,10 @@ export const grids = { ); } }, - createGridApi: async (data: CreateGrid) => { + createGroupApi: async (data: CreateGrid) => { try { const response = await axiosInstance.post( - `${DEVICES_MGT_URL}/grids`, + `${USERS_MGT_URL}/groups`, data ); return response.data; From b1a6c64ba8446d5f35dc0c5c047094fe2e1ae335 Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Mon, 10 Feb 2025 19:40:11 +0300 Subject: [PATCH 04/74] Fix API endpoints to use 'groups' instead of 'grids' for group details retrieval and updates --- netmanager-app/core/apis/organizations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netmanager-app/core/apis/organizations.ts b/netmanager-app/core/apis/organizations.ts index ecf7270868..94ec42e207 100644 --- a/netmanager-app/core/apis/organizations.ts +++ b/netmanager-app/core/apis/organizations.ts @@ -26,7 +26,7 @@ export const grids = { getGroupDetailsApi: async (gridId: string) => { try { const response = await axiosInstance.get( - `${USERS_MGT_URL}/grids/${gridId}` + `${USERS_MGT_URL}/groups/${gridId}` ); return response.data; } catch (error) { @@ -39,7 +39,7 @@ export const grids = { updateGroupDetailsApi: async (gridId: string) => { try { const response = await axiosInstance.put( - `${USERS_MGT_URL}/grids/${gridId}` + `${USERS_MGT_URL}/groups/${gridId}` ); return response.data; } catch (error) { From 3de29c1c3dcc27bfda9fb671058c45bc9a78c7af Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Mon, 10 Feb 2025 23:16:27 +0300 Subject: [PATCH 05/74] Add group types and Redux slice for group management --- netmanager-app/app/types/groups.ts | 25 +++++++++ .../core/redux/slices/groupsSlice.ts | 53 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 netmanager-app/app/types/groups.ts create mode 100644 netmanager-app/core/redux/slices/groupsSlice.ts diff --git a/netmanager-app/app/types/groups.ts b/netmanager-app/app/types/groups.ts new file mode 100644 index 0000000000..b7de37ba08 --- /dev/null +++ b/netmanager-app/app/types/groups.ts @@ -0,0 +1,25 @@ +import { UserDetails } from "@/app/types/users"; + + export interface Group { + _id: string + grp_status: "ACTIVE" | "INACTIVE" + grp_profile_picture: string + grp_title: string + grp_description: string + grp_website: string + grp_industry: string + grp_country: string + grp_timezone: string + createdAt: string + numberOfGroupUsers: number + grp_users: UserDetails[] + grp_manager: UserDetails + } + + export interface GroupResponse { + success: boolean + message: string + group: Group + } + + \ No newline at end of file diff --git a/netmanager-app/core/redux/slices/groupsSlice.ts b/netmanager-app/core/redux/slices/groupsSlice.ts new file mode 100644 index 0000000000..82c3e0db83 --- /dev/null +++ b/netmanager-app/core/redux/slices/groupsSlice.ts @@ -0,0 +1,53 @@ +import { createSlice } from "@reduxjs/toolkit"; +import type { PayloadAction } from "@reduxjs/toolkit"; +import { UserDetails } from "@/app/types/users"; + +interface Groups { + _id: string + grp_status: "ACTIVE" | "INACTIVE" + grp_profile_picture: string + grp_title: string + grp_description: string + grp_website: string + grp_industry: string + grp_country: string + grp_timezone: string + createdAt: string + numberOfGroupUsers: number + grp_users: UserDetails[] + grp_manager: UserDetails + } + +interface GroupsState { + groups: Groups[]; + isLoading: boolean; + error: string | null; +} + +const initialState: GroupsState = { + groups: [], + isLoading: false, + error: null, +}; + +const sitesSlice = createSlice({ + name: "groups", + initialState, + reducers: { + setGroups(state, action: PayloadAction) { + state.groups = action.payload; + state.isLoading = false; + state.error = null; + }, + setLoading(state, action: PayloadAction) { + state.isLoading = action.payload; + }, + setError(state, action: PayloadAction) { + state.error = action.payload; + state.isLoading = false; + }, + }, +}); + +export const { setGroups, setLoading, setError } = sitesSlice.actions; +export default sitesSlice.reducer; From 4fa10fcb3b171df523a930c638ee8d596294591d Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Mon, 10 Feb 2025 23:25:56 +0300 Subject: [PATCH 06/74] Rename 'grids' to 'groups' in API and hooks for consistency in group management --- netmanager-app/core/apis/organizations.ts | 2 +- netmanager-app/core/hooks/useGroups.ts | 109 ++++++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 netmanager-app/core/hooks/useGroups.ts diff --git a/netmanager-app/core/apis/organizations.ts b/netmanager-app/core/apis/organizations.ts index 94ec42e207..cdabbb4a99 100644 --- a/netmanager-app/core/apis/organizations.ts +++ b/netmanager-app/core/apis/organizations.ts @@ -9,7 +9,7 @@ interface ErrorResponse { message: string; } -export const grids = { +export const groups = { getGroupsApi: async () => { try { const response = await axiosInstance.get( diff --git a/netmanager-app/core/hooks/useGroups.ts b/netmanager-app/core/hooks/useGroups.ts new file mode 100644 index 0000000000..abf4accc7e --- /dev/null +++ b/netmanager-app/core/hooks/useGroups.ts @@ -0,0 +1,109 @@ +import { + useMutation, + useQuery, + useQueryClient, + UseQueryOptions, + } from "@tanstack/react-query"; +import { AxiosError } from "axios"; +import { groups } from "../apis/organizations"; +import { Group } from "@/app/types/groups"; +import { GroupsState, setError, setGroups } from "../redux/slices/groupsSlice"; +import { useDispatch } from "react-redux"; + + interface ErrorResponse { + message: string; + } + + // Hook to get the grid summary + export const useGrids = () => { + const dispatch = useDispatch(); + + const { data, isLoading, error } = useQuery< + GroupsState, + AxiosError + >({ + queryKey: ["groups"], + queryFn: () => groups.getGroupsApi(), + onSuccess: (data: GroupsState) => { + dispatch(setGroups(data.groups)); + }, + onError: (error: AxiosError) => { + dispatch(setError(error.message)); + }, + } as UseQueryOptions>); + + return { + grids: data?.groups ?? [], + isLoading, + error, + }; + }; + + // Hook to get grid details by gridId + export const useGridDetails = (groupId: string) => { + const mutation = useMutation({ + mutationFn: async () => await groups.getGroupDetailsApi(groupId), + onSuccess: () => { + console.log("Grid details fetched successfully"); + }, + onError: (error: AxiosError) => { + console.error( + "Failed to fetch grid details:", + error.response?.data?.message + ); + }, + }); + + return { + getGroupDetails: mutation.mutate || [], + isLoading: mutation.isPending, + error: mutation.error as Error | null, + }; + }; + + // Hook to update grid details + export const useUpdateGridDetails = (gridId: string) => { + const queryClient = useQueryClient(); + const mutation = useMutation({ + mutationFn: async () => await grids.updateGridDetailsApi(gridId), + onSuccess: () => { + // Invalidate and refetch the grid details + queryClient.invalidateQueries({ queryKey: ["gridDetails", gridId] }); + }, + onError: (error: AxiosError) => { + console.error( + "Failed to update grid details:", + error.response?.data?.message + ); + }, + }); + + return { + updateGridDetails: mutation.mutate, + isLoading: mutation.isPending, + error: mutation.error, + }; + }; + + // Hook to create a new grid + export const useCreateGrid = () => { + const queryClient = useQueryClient(); + const mutation = useMutation({ + mutationFn: async (newGrid: CreateGrid) => + await grids.createGridApi(newGrid), + onSuccess: () => { + // Invalidate and refetch the grid summary after creating a new grid + queryClient.invalidateQueries({ queryKey: ["gridSummary"] }); + }, + onError: (error: AxiosError) => { + console.error("Failed to create grid:", error.response?.data?.message); + }, + }); + + return { + createGrid: mutation.mutate, + isLoading: mutation.isPending, + error: mutation.error, + }; + }; + \ No newline at end of file From 51c5daae1e492f5c391e85ceb39bb711e700d05f Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Tue, 11 Feb 2025 09:56:09 +0300 Subject: [PATCH 07/74] Refactor group-related types and hooks for consistency in group management --- netmanager-app/core/apis/organizations.ts | 4 ++-- netmanager-app/core/hooks/useGroups.ts | 20 ++++++++----------- .../core/redux/slices/groupsSlice.ts | 2 +- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/netmanager-app/core/apis/organizations.ts b/netmanager-app/core/apis/organizations.ts index cdabbb4a99..ebc8bfc3d5 100644 --- a/netmanager-app/core/apis/organizations.ts +++ b/netmanager-app/core/apis/organizations.ts @@ -1,7 +1,7 @@ import createAxiosInstance from "./axiosConfig"; import { USERS_MGT_URL } from "../urls"; import { AxiosError } from "axios"; -import { CreateGrid } from "@/app/types/grids"; +import { Group } from "@/app/types/groups"; const axiosInstance = createAxiosInstance(); @@ -49,7 +49,7 @@ export const groups = { ); } }, - createGroupApi: async (data: CreateGrid) => { + createGroupApi: async (data: Group) => { try { const response = await axiosInstance.post( `${USERS_MGT_URL}/groups`, diff --git a/netmanager-app/core/hooks/useGroups.ts b/netmanager-app/core/hooks/useGroups.ts index abf4accc7e..eb9aa0870a 100644 --- a/netmanager-app/core/hooks/useGroups.ts +++ b/netmanager-app/core/hooks/useGroups.ts @@ -40,7 +40,7 @@ import { useDispatch } from "react-redux"; }; // Hook to get grid details by gridId - export const useGridDetails = (groupId: string) => { + export const useGroupsDetails = (groupId: string) => { const mutation = useMutation({ mutationFn: async () => await groups.getGroupDetailsApi(groupId), onSuccess: () => { @@ -60,14 +60,12 @@ import { useDispatch } from "react-redux"; error: mutation.error as Error | null, }; }; - - // Hook to update grid details + export const useUpdateGridDetails = (gridId: string) => { const queryClient = useQueryClient(); const mutation = useMutation({ - mutationFn: async () => await grids.updateGridDetailsApi(gridId), + mutationFn: async () => await groups.getGroupDetailsApi(gridId), onSuccess: () => { - // Invalidate and refetch the grid details queryClient.invalidateQueries({ queryKey: ["gridDetails", gridId] }); }, onError: (error: AxiosError) => { @@ -83,17 +81,15 @@ import { useDispatch } from "react-redux"; isLoading: mutation.isPending, error: mutation.error, }; - }; - - // Hook to create a new grid +}; + export const useCreateGrid = () => { const queryClient = useQueryClient(); const mutation = useMutation({ - mutationFn: async (newGrid: CreateGrid) => - await grids.createGridApi(newGrid), + mutationFn: async (newGroup: Group) => + await groups.createGroupApi(newGroup), onSuccess: () => { - // Invalidate and refetch the grid summary after creating a new grid - queryClient.invalidateQueries({ queryKey: ["gridSummary"] }); + queryClient.invalidateQueries({ queryKey: ["groups"] }); }, onError: (error: AxiosError) => { console.error("Failed to create grid:", error.response?.data?.message); diff --git a/netmanager-app/core/redux/slices/groupsSlice.ts b/netmanager-app/core/redux/slices/groupsSlice.ts index 82c3e0db83..44efc3550d 100644 --- a/netmanager-app/core/redux/slices/groupsSlice.ts +++ b/netmanager-app/core/redux/slices/groupsSlice.ts @@ -18,7 +18,7 @@ interface Groups { grp_manager: UserDetails } -interface GroupsState { +export interface GroupsState { groups: Groups[]; isLoading: boolean; error: string | null; From f363f30e630675d0f0b91ae596537ed31555f884 Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Tue, 11 Feb 2025 10:03:15 +0300 Subject: [PATCH 08/74] Refactor useGroups hook for improved readability and consistency --- netmanager-app/core/hooks/useGroups.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/netmanager-app/core/hooks/useGroups.ts b/netmanager-app/core/hooks/useGroups.ts index eb9aa0870a..1bca41b621 100644 --- a/netmanager-app/core/hooks/useGroups.ts +++ b/netmanager-app/core/hooks/useGroups.ts @@ -10,11 +10,10 @@ import { Group } from "@/app/types/groups"; import { GroupsState, setError, setGroups } from "../redux/slices/groupsSlice"; import { useDispatch } from "react-redux"; - interface ErrorResponse { +interface ErrorResponse { message: string; - } - - // Hook to get the grid summary +} + export const useGrids = () => { const dispatch = useDispatch(); @@ -30,16 +29,15 @@ import { useDispatch } from "react-redux"; onError: (error: AxiosError) => { dispatch(setError(error.message)); }, - } as UseQueryOptions>); - + } as UseQueryOptions>) + return { grids: data?.groups ?? [], isLoading, error, }; - }; - - // Hook to get grid details by gridId +}; + export const useGroupsDetails = (groupId: string) => { const mutation = useMutation({ mutationFn: async () => await groups.getGroupDetailsApi(groupId), From 1b3359b5ea42510be029b46c1358c97e4741a46f Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Tue, 11 Feb 2025 10:17:32 +0300 Subject: [PATCH 09/74] Rename useGrids hook to useGroups for consistency in group management --- netmanager-app/core/hooks/useGroups.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netmanager-app/core/hooks/useGroups.ts b/netmanager-app/core/hooks/useGroups.ts index 1bca41b621..6f36344176 100644 --- a/netmanager-app/core/hooks/useGroups.ts +++ b/netmanager-app/core/hooks/useGroups.ts @@ -14,7 +14,7 @@ interface ErrorResponse { message: string; } - export const useGrids = () => { + export const useGroups = () => { const dispatch = useDispatch(); const { data, isLoading, error } = useQuery< From a03c267af4da7afa41a8de549efcfb098a2397cf Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Tue, 11 Feb 2025 10:28:19 +0300 Subject: [PATCH 10/74] Refactor useGroups hook for improved readability and add groups reducer to Redux store --- netmanager-app/core/hooks/useGroups.ts | 58 +++++++++++++------------- netmanager-app/core/redux/store.ts | 2 + 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/netmanager-app/core/hooks/useGroups.ts b/netmanager-app/core/hooks/useGroups.ts index 6f36344176..924e5d6717 100644 --- a/netmanager-app/core/hooks/useGroups.ts +++ b/netmanager-app/core/hooks/useGroups.ts @@ -14,21 +14,21 @@ interface ErrorResponse { message: string; } - export const useGroups = () => { +export const useGroups = () => { const dispatch = useDispatch(); - + const { data, isLoading, error } = useQuery< GroupsState, - AxiosError + AxiosError >({ - queryKey: ["groups"], - queryFn: () => groups.getGroupsApi(), - onSuccess: (data: GroupsState) => { - dispatch(setGroups(data.groups)); - }, - onError: (error: AxiosError) => { - dispatch(setError(error.message)); - }, + queryKey: ["groups"], + queryFn: () => groups.getGroupsApi(), + onSuccess: (data: GroupsState) => { + dispatch(setGroups(data.groups)); + }, + onError: (error: AxiosError) => { + dispatch(setError(error.message)); + }, } as UseQueryOptions>) return { @@ -51,33 +51,33 @@ interface ErrorResponse { ); }, }); - + return { - getGroupDetails: mutation.mutate || [], - isLoading: mutation.isPending, - error: mutation.error as Error | null, + getGroupDetails: mutation.mutate || [], + isLoading: mutation.isPending, + error: mutation.error as Error | null, }; - }; +}; - export const useUpdateGridDetails = (gridId: string) => { +export const useUpdateGridDetails = (gridId: string) => { const queryClient = useQueryClient(); const mutation = useMutation({ - mutationFn: async () => await groups.getGroupDetailsApi(gridId), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["gridDetails", gridId] }); - }, - onError: (error: AxiosError) => { + mutationFn: async () => await groups.getGroupDetailsApi(gridId), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["gridDetails", gridId] }); + }, + onError: (error: AxiosError) => { console.error( - "Failed to update grid details:", - error.response?.data?.message + "Failed to update grid details:", + error.response?.data?.message ); - }, + }, }); - + return { - updateGridDetails: mutation.mutate, - isLoading: mutation.isPending, - error: mutation.error, + updateGridDetails: mutation.mutate, + isLoading: mutation.isPending, + error: mutation.error, }; }; diff --git a/netmanager-app/core/redux/store.ts b/netmanager-app/core/redux/store.ts index 9b887c5220..912ef707c6 100644 --- a/netmanager-app/core/redux/store.ts +++ b/netmanager-app/core/redux/store.ts @@ -5,6 +5,7 @@ import devicesReducer from "./slices/devicesSlice"; import cohortsReducer from "./slices/cohortsSlice"; import gridsReducer from "./slices/gridsSlice"; import clientsRudcer from "./slices/clientsSlice"; +import groupsReducer from "./slices/groupsSlice"; export const store = configureStore({ reducer: { @@ -14,6 +15,7 @@ export const store = configureStore({ grids: gridsReducer, cohorts: cohortsReducer, clients: clientsRudcer, + groups: groupsReducer, }, }); From 16ad0022659e2b467d2d4430b0a4b48bfd041be2 Mon Sep 17 00:00:00 2001 From: Daniel Ntege Date: Tue, 11 Feb 2025 14:25:02 +0300 Subject: [PATCH 11/74] Refactor organization components and hooks for improved consistency and structure --- .../organizations/[id]/page.tsx | 18 +++--- .../(authenticated)/organizations/page.tsx | 14 ++--- .../components/Organization/List.tsx | 26 +++----- .../Organization/organization-profile.tsx | 62 +++++++++++++++++++ .../Organization/organization-roles.tsx | 8 +-- netmanager-app/core/apis/organizations.ts | 4 +- netmanager-app/core/hooks/useGroups.ts | 10 +-- 7 files changed, 94 insertions(+), 48 deletions(-) diff --git a/netmanager-app/app/(authenticated)/organizations/[id]/page.tsx b/netmanager-app/app/(authenticated)/organizations/[id]/page.tsx index e5a15a8765..ab20da5c0e 100644 --- a/netmanager-app/app/(authenticated)/organizations/[id]/page.tsx +++ b/netmanager-app/app/(authenticated)/organizations/[id]/page.tsx @@ -1,16 +1,12 @@ -import { redirect } from "next/navigation" -import { getCurrentUser } from "@/lib/session" +// import { redirect } from "next/navigation" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" -import { OrganizationProfile } from "./organization-profile" -import { TeamMembers } from "./team-members" -import { OrganizationRoles } from "./organization-roles" +import { OrganizationProfile } from "@/components/Organization/organization-profile" +import { TeamMembers } from "@/components/Organization/team-members" +import { OrganizationRoles } from "@/components/Organization/organization-roles" +// import { useAppSelector } from "@/core/redux/hooks" -export default async function OrganizationDetailsPage({ params }: { params: { id: string } }) { - const user = await getCurrentUser() - - if (!user || user.role !== "AIRQO_SUPER_ADMIN") { - redirect("/") - } +export const OrganizationDetailsPage = ({ params }: { params: { id: string } }) => { + // const user = useAppSelector((state) => state.user.userDetails) return (
diff --git a/netmanager-app/app/(authenticated)/organizations/page.tsx b/netmanager-app/app/(authenticated)/organizations/page.tsx index 0d19ad04c6..3aa7f77529 100644 --- a/netmanager-app/app/(authenticated)/organizations/page.tsx +++ b/netmanager-app/app/(authenticated)/organizations/page.tsx @@ -1,13 +1,10 @@ -import { useAppSelector } from "@/core/redux/hooks" -import { redirect } from "next/navigation" -import { OrganizationList } from "./organization-list" -export const OrganizationSettingsPage =() => { - const user = useAppSelector((state) => state.user.userDetails) +// import { useAppSelector } from "@/core/redux/hooks" +// import { redirect } from "next/navigation" +import { OrganizationList } from "@/components/Organization/List" - if (!user || user.role !== "AIRQO_SUPER_ADMIN") { - redirect("/") - } +const OrganizationSettingsPage = () => { + // const user = useAppSelector((state) => state.user.userDetails) return (
@@ -17,3 +14,4 @@ export const OrganizationSettingsPage =() => { ) } +export default OrganizationSettingsPage; diff --git a/netmanager-app/components/Organization/List.tsx b/netmanager-app/components/Organization/List.tsx index 365b7f3763..4a8d5386a0 100644 --- a/netmanager-app/components/Organization/List.tsx +++ b/netmanager-app/components/Organization/List.tsx @@ -6,25 +6,15 @@ import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { useAppSelector, useAppDispatch } from "@/core/redux/hooks" -import { fetchOrganizations } from "@/lib/slices/organizationsSlice" +import { useGroups } from "@/core/hooks/useGroups" export function OrganizationList() { - const dispatch = useAppDispatch() - const organizations = useAppSelector((state) => state.organizations.list) - const status = useAppSelector((state) => state.organizations.status) + // const dispatch = useAppDispatch() const [searchTerm, setSearchTerm] = useState("") + const { groups, isLoading, error } = useGroups() - useEffect(() => { - if (status === "idle") { - dispatch(fetchOrganizations()) - } - }, [status, dispatch]) - - const filteredOrganizations = organizations.filter((org) => org.name.toLowerCase().includes(searchTerm.toLowerCase())) - - if (status === "loading") { - return
Loading...
- } + const filteredOrganizations = groups.filter((org) => org.grp_title.toLowerCase().includes(searchTerm.toLowerCase())) + const status = isLoading ? "loading" : error ? "failed" : "success" if (status === "failed") { return
Error loading organizations. Please try again.
@@ -53,12 +43,12 @@ export function OrganizationList() { {filteredOrganizations.map((org) => ( - - {org.name} + + {org.grp_title} {new Date(org.createdAt).toLocaleDateString()} diff --git a/netmanager-app/components/Organization/organization-profile.tsx b/netmanager-app/components/Organization/organization-profile.tsx index e69de29bb2..10ea880da5 100644 --- a/netmanager-app/components/Organization/organization-profile.tsx +++ b/netmanager-app/components/Organization/organization-profile.tsx @@ -0,0 +1,62 @@ +"use client" + +import { useState, useEffect } from "react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Textarea } from "@/components/ui/textarea" +import { useAppDispatch, useAppSelector } from "@/core/redux/hooks" +import { groups } from "@/core/apis/organizations" + +type OrganizationProfileProps = { + organizationId: string +} + +export function OrganizationProfile({ organizationId }: OrganizationProfileProps) { +// const dispatch = useAppDispatch() + const organization = useAppSelector((state) => + state.groups.groups.find((org) => org._id === organizationId), + ) + const [grp_title, setName] = useState(organization?.grp_title || "") + const [grp_description, setDescription] = useState(organization?.grp_description || "") + + useEffect(() => { + if (organization) { + setName(organization.grp_title) + setDescription(organization.grp_description) + } + }, [organization]) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + try { + await groups.updateGroupDetailsApi(organizationId, { + ...organization, + grp_title, + grp_description + }) + } catch (error) { + console.error("Failed to update organization profile", error) + + } + } + + if (!organization) { + return
Loading...
+ } + + return ( +
+
+ + setName(e.target.value)} required /> +
+
+ +