get groups
This commit is contained in:
@@ -11,6 +11,7 @@ import Articles from "../views/home/articles/Articles";
|
|||||||
import Groups from "../views/home/groups/Groups";
|
import Groups from "../views/home/groups/Groups";
|
||||||
import Contests from "../views/home/contests/Contests";
|
import Contests from "../views/home/contests/Contests";
|
||||||
import { PrimaryButton } from "../components/button/PrimaryButton";
|
import { PrimaryButton } from "../components/button/PrimaryButton";
|
||||||
|
import Group from "../views/home/groups/Group";
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const name = useAppSelector((state) => state.auth.username);
|
const name = useAppSelector((state) => state.auth.username);
|
||||||
@@ -34,6 +35,7 @@ const Home = () => {
|
|||||||
<Route path="register" element={<Register />} />
|
<Route path="register" element={<Register />} />
|
||||||
<Route path="missions/*" element={<Missions/>} />
|
<Route path="missions/*" element={<Missions/>} />
|
||||||
<Route path="articles/*" element={<Articles/>} />
|
<Route path="articles/*" element={<Articles/>} />
|
||||||
|
<Route path="group/:groupId" element={<Group/>} />
|
||||||
<Route path="groups/*" element={<Groups/>} />
|
<Route path="groups/*" element={<Groups/>} />
|
||||||
<Route path="contests/*" element={<Contests/>} />
|
<Route path="contests/*" element={<Contests/>} />
|
||||||
<Route path="*" element={<>
|
<Route path="*" element={<>
|
||||||
|
|||||||
258
src/redux/slices/groups.ts
Normal file
258
src/redux/slices/groups.ts
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
import axios from "../../axios";
|
||||||
|
|
||||||
|
// ─── Типы ────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface GroupMember {
|
||||||
|
userId: number;
|
||||||
|
username: string;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Group {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
members: GroupMember[];
|
||||||
|
contests: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GroupsState {
|
||||||
|
groups: Group[];
|
||||||
|
currentGroup: Group | null;
|
||||||
|
status: "idle" | "loading" | "successful" | "failed";
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: GroupsState = {
|
||||||
|
groups: [],
|
||||||
|
currentGroup: null,
|
||||||
|
status: "idle",
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Async Thunks ─────────────────────────────────────
|
||||||
|
|
||||||
|
// POST /groups
|
||||||
|
export const createGroup = createAsyncThunk(
|
||||||
|
"groups/createGroup",
|
||||||
|
async (
|
||||||
|
{ name, description }: { name: string; description: string },
|
||||||
|
{ rejectWithValue }
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post("/groups", { name, description });
|
||||||
|
return response.data as Group;
|
||||||
|
} catch (err: any) {
|
||||||
|
return rejectWithValue(err.response?.data?.message || "Ошибка при создании группы");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// PUT /groups/{groupId}
|
||||||
|
export const updateGroup = createAsyncThunk(
|
||||||
|
"groups/updateGroup",
|
||||||
|
async (
|
||||||
|
{ groupId, name, description }: { groupId: number; name: string; description: string },
|
||||||
|
{ rejectWithValue }
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.put(`/groups/${groupId}`, { name, description });
|
||||||
|
return response.data as Group;
|
||||||
|
} catch (err: any) {
|
||||||
|
return rejectWithValue(err.response?.data?.message || "Ошибка при обновлении группы");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// DELETE /groups/{groupId}
|
||||||
|
export const deleteGroup = createAsyncThunk(
|
||||||
|
"groups/deleteGroup",
|
||||||
|
async (groupId: number, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
await axios.delete(`/groups/${groupId}`);
|
||||||
|
return groupId;
|
||||||
|
} catch (err: any) {
|
||||||
|
return rejectWithValue(err.response?.data?.message || "Ошибка при удалении группы");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// GET /groups/my
|
||||||
|
export const fetchMyGroups = createAsyncThunk(
|
||||||
|
"groups/fetchMyGroups",
|
||||||
|
async (_, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get("/groups/my");
|
||||||
|
return response.data.groups as Group[];
|
||||||
|
} catch (err: any) {
|
||||||
|
return rejectWithValue(err.response?.data?.message || "Ошибка при получении групп");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// GET /groups/{groupId}
|
||||||
|
export const fetchGroupById = createAsyncThunk(
|
||||||
|
"groups/fetchGroupById",
|
||||||
|
async (groupId: number, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/groups/${groupId}`);
|
||||||
|
return response.data as Group;
|
||||||
|
} catch (err: any) {
|
||||||
|
return rejectWithValue(err.response?.data?.message || "Ошибка при получении группы");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// POST /groups/members
|
||||||
|
export const addGroupMember = createAsyncThunk(
|
||||||
|
"groups/addGroupMember",
|
||||||
|
async ({ userId, role }: { userId: number; role: string }, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
await axios.post("/groups/members", { userId, role });
|
||||||
|
return { userId, role };
|
||||||
|
} catch (err: any) {
|
||||||
|
return rejectWithValue(err.response?.data?.message || "Ошибка при добавлении участника");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// DELETE /groups/{groupId}/members/{memberId}
|
||||||
|
export const removeGroupMember = createAsyncThunk(
|
||||||
|
"groups/removeGroupMember",
|
||||||
|
async (
|
||||||
|
{ groupId, memberId }: { groupId: number; memberId: number },
|
||||||
|
{ rejectWithValue }
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
await axios.delete(`/groups/${groupId}/members/${memberId}`);
|
||||||
|
return { groupId, memberId };
|
||||||
|
} catch (err: any) {
|
||||||
|
return rejectWithValue(err.response?.data?.message || "Ошибка при удалении участника");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ─── Slice ────────────────────────────────────────────
|
||||||
|
|
||||||
|
const groupsSlice = createSlice({
|
||||||
|
name: "groups",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
clearCurrentGroup: (state) => {
|
||||||
|
state.currentGroup = null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
// ─── CREATE GROUP ───
|
||||||
|
builder.addCase(createGroup.pending, (state) => {
|
||||||
|
state.status = "loading";
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(createGroup.fulfilled, (state, action: PayloadAction<Group>) => {
|
||||||
|
state.status = "successful";
|
||||||
|
state.groups.push(action.payload);
|
||||||
|
});
|
||||||
|
builder.addCase(createGroup.rejected, (state, action: PayloadAction<any>) => {
|
||||||
|
state.status = "failed";
|
||||||
|
state.error = action.payload;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── UPDATE GROUP ───
|
||||||
|
builder.addCase(updateGroup.pending, (state) => {
|
||||||
|
state.status = "loading";
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(updateGroup.fulfilled, (state, action: PayloadAction<Group>) => {
|
||||||
|
state.status = "successful";
|
||||||
|
const index = state.groups.findIndex((g) => g.id === action.payload.id);
|
||||||
|
if (index !== -1) state.groups[index] = action.payload;
|
||||||
|
if (state.currentGroup?.id === action.payload.id)
|
||||||
|
state.currentGroup = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(updateGroup.rejected, (state, action: PayloadAction<any>) => {
|
||||||
|
state.status = "failed";
|
||||||
|
state.error = action.payload;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── DELETE GROUP ───
|
||||||
|
builder.addCase(deleteGroup.pending, (state) => {
|
||||||
|
state.status = "loading";
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteGroup.fulfilled, (state, action: PayloadAction<number>) => {
|
||||||
|
state.status = "successful";
|
||||||
|
state.groups = state.groups.filter((g) => g.id !== action.payload);
|
||||||
|
if (state.currentGroup?.id === action.payload) state.currentGroup = null;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteGroup.rejected, (state, action: PayloadAction<any>) => {
|
||||||
|
state.status = "failed";
|
||||||
|
state.error = action.payload;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── FETCH MY GROUPS ───
|
||||||
|
builder.addCase(fetchMyGroups.pending, (state) => {
|
||||||
|
state.status = "loading";
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchMyGroups.fulfilled, (state, action: PayloadAction<Group[]>) => {
|
||||||
|
state.status = "successful";
|
||||||
|
state.groups = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchMyGroups.rejected, (state, action: PayloadAction<any>) => {
|
||||||
|
state.status = "failed";
|
||||||
|
state.error = action.payload;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── FETCH GROUP BY ID ───
|
||||||
|
builder.addCase(fetchGroupById.pending, (state) => {
|
||||||
|
state.status = "loading";
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchGroupById.fulfilled, (state, action: PayloadAction<Group>) => {
|
||||||
|
state.status = "successful";
|
||||||
|
state.currentGroup = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchGroupById.rejected, (state, action: PayloadAction<any>) => {
|
||||||
|
state.status = "failed";
|
||||||
|
state.error = action.payload;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── ADD MEMBER ───
|
||||||
|
builder.addCase(addGroupMember.pending, (state) => {
|
||||||
|
state.status = "loading";
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(addGroupMember.fulfilled, (state) => {
|
||||||
|
state.status = "successful";
|
||||||
|
});
|
||||||
|
builder.addCase(addGroupMember.rejected, (state, action: PayloadAction<any>) => {
|
||||||
|
state.status = "failed";
|
||||||
|
state.error = action.payload;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── REMOVE MEMBER ───
|
||||||
|
builder.addCase(removeGroupMember.pending, (state) => {
|
||||||
|
state.status = "loading";
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(
|
||||||
|
removeGroupMember.fulfilled,
|
||||||
|
(state, action: PayloadAction<{ groupId: number; memberId: number }>) => {
|
||||||
|
state.status = "successful";
|
||||||
|
if (state.currentGroup && state.currentGroup.id === action.payload.groupId) {
|
||||||
|
state.currentGroup.members = state.currentGroup.members.filter(
|
||||||
|
(m) => m.userId !== action.payload.memberId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
builder.addCase(removeGroupMember.rejected, (state, action: PayloadAction<any>) => {
|
||||||
|
state.status = "failed";
|
||||||
|
state.error = action.payload;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { clearCurrentGroup } = groupsSlice.actions;
|
||||||
|
export const groupsReducer = groupsSlice.reducer;
|
||||||
@@ -4,6 +4,7 @@ import { storeReducer } from "./slices/store";
|
|||||||
import { missionsReducer } from "./slices/missions";
|
import { missionsReducer } from "./slices/missions";
|
||||||
import { submitReducer } from "./slices/submit";
|
import { submitReducer } from "./slices/submit";
|
||||||
import { contestsReducer } from "./slices/contests";
|
import { contestsReducer } from "./slices/contests";
|
||||||
|
import { groupsReducer } from "./slices/groups";
|
||||||
|
|
||||||
|
|
||||||
// использование
|
// использование
|
||||||
@@ -23,6 +24,7 @@ export const store = configureStore({
|
|||||||
missions: missionsReducer,
|
missions: missionsReducer,
|
||||||
submin: submitReducer,
|
submin: submitReducer,
|
||||||
contests: contestsReducer,
|
contests: contestsReducer,
|
||||||
|
groups: groupsReducer,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
26
src/views/home/groups/Group.tsx
Normal file
26
src/views/home/groups/Group.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { cn } from "../../../lib/cn";
|
||||||
|
import { useParams, Navigate } from "react-router-dom";
|
||||||
|
|
||||||
|
interface GroupsBlockProps {}
|
||||||
|
|
||||||
|
const Group: FC<GroupsBlockProps> = () => {
|
||||||
|
const { groupId } = useParams<{ groupId: string }>();
|
||||||
|
const groupIdNumber = Number(groupId);
|
||||||
|
|
||||||
|
if (!groupId || isNaN(groupIdNumber) || !groupIdNumber) {
|
||||||
|
return <Navigate to="/home/groups" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"border-b-[1px] border-b-liquid-lighter rounded-[10px]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{groupIdNumber}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Group;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { cn } from "../../../lib/cn";
|
import { cn } from "../../../lib/cn";
|
||||||
import { Book, UserAdd, Edit, EyeClosed, EyeOpen } from "../../../assets/icons/groups";
|
import { Book, UserAdd, Edit, EyeClosed, EyeOpen } from "../../../assets/icons/groups";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export interface GroupItemProps {
|
export interface GroupItemProps {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -26,9 +27,13 @@ const IconComponent: React.FC<IconComponentProps> = ({
|
|||||||
const GroupItem: React.FC<GroupItemProps> = ({
|
const GroupItem: React.FC<GroupItemProps> = ({
|
||||||
id, name, visible, role
|
id, name, visible, role
|
||||||
}) => {
|
}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("w-full h-[120px] box-border relative rounded-[10px] p-[10px] text-liquid-white bg-liquid-lighter",
|
<div className={cn("w-full h-[120px] box-border relative rounded-[10px] p-[10px] text-liquid-white bg-liquid-lighter cursor-pointer",
|
||||||
)}>
|
)}
|
||||||
|
onClick={() => navigate(`/group/${id}`)}
|
||||||
|
>
|
||||||
<div className="grid grid-cols-[100px,1fr] gap-[20px]">
|
<div className="grid grid-cols-[100px,1fr] gap-[20px]">
|
||||||
<img src={Book} className="bg-liquid-brightmain rounded-[10px]"/>
|
<img src={Book} className="bg-liquid-brightmain rounded-[10px]"/>
|
||||||
<div className="grid grid-flow-row grid-rows-[1fr,24px]">
|
<div className="grid grid-flow-row grid-rows-[1fr,24px]">
|
||||||
|
|||||||
@@ -1,51 +1,58 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { SecondaryButton } from "../../../components/button/SecondaryButton";
|
import { SecondaryButton } from "../../../components/button/SecondaryButton";
|
||||||
import { cn } from "../../../lib/cn";
|
import { cn } from "../../../lib/cn";
|
||||||
import { useAppDispatch } from "../../../redux/hooks";
|
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
|
||||||
import GroupsBlock from "./GroupsBlock";
|
import GroupsBlock from "./GroupsBlock";
|
||||||
import { setMenuActivePage } from "../../../redux/slices/store";
|
import { setMenuActivePage } from "../../../redux/slices/store";
|
||||||
|
import { fetchMyGroups } from "../../../redux/slices/groups";
|
||||||
|
|
||||||
export interface Group {
|
|
||||||
id: number;
|
|
||||||
role: "menager" | "member" | "owner" | "viewer";
|
|
||||||
visible: boolean;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const Groups = () => {
|
const Groups = () => {
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const groups: Group[] = [
|
// Берём группы из стора
|
||||||
{ id: 1, role: "owner", name: "Main Administration", visible: true },
|
const groups = useAppSelector((store) => store.groups.groups);
|
||||||
{ id: 2, role: "menager", name: "Project Managers", visible: true },
|
|
||||||
{ id: 3, role: "member", name: "Developers", visible: true },
|
// Берём текущего пользователя
|
||||||
{ id: 4, role: "viewer", name: "QA Viewers", visible: true },
|
const currentUserName = useAppSelector((store) => store.auth.username);
|
||||||
{ id: 5, role: "member", name: "Design Team", visible: true },
|
|
||||||
{ id: 6, role: "owner", name: "Executive Board", visible: true },
|
|
||||||
{ id: 7, role: "menager", name: "HR Managers", visible: true },
|
|
||||||
{ id: 8, role: "viewer", name: "Marketing Reviewers", visible: false },
|
|
||||||
{ id: 9, role: "member", name: "Content Creators", visible: false },
|
|
||||||
{ id: 10, role: "menager", name: "Support Managers", visible: true },
|
|
||||||
{ id: 11, role: "viewer", name: "External Auditors", visible: false },
|
|
||||||
{ id: 12, role: "member", name: "Frontend Developers", visible: true },
|
|
||||||
{ id: 13, role: "member", name: "Backend Developers", visible: true },
|
|
||||||
{ id: 14, role: "viewer", name: "Guest Access", visible: false },
|
|
||||||
{ id: 15, role: "menager", name: "Operations", visible: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(setMenuActivePage("groups"))
|
dispatch(setMenuActivePage("groups"));
|
||||||
}, []);
|
dispatch(fetchMyGroups())
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
// Разделяем группы
|
||||||
|
const { managedGroups, currentGroups, hiddenGroups } = useMemo(() => {
|
||||||
|
if (!groups || !currentUserName) {
|
||||||
|
return { managedGroups: [], currentGroups: [], hiddenGroups: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const managed: typeof groups = [];
|
||||||
|
const current: typeof groups = [];
|
||||||
|
const hidden: typeof groups = []; // пока пустые, без логики
|
||||||
|
|
||||||
|
groups.forEach((group) => {
|
||||||
|
const me = group.members.find((m) => m.username === currentUserName);
|
||||||
|
if (!me) return;
|
||||||
|
|
||||||
|
if (me.role === "Administrator") {
|
||||||
|
managed.push(group);
|
||||||
|
} else {
|
||||||
|
current.push(group);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { managedGroups: managed, currentGroups: current, hiddenGroups: hidden };
|
||||||
|
}, [groups, currentUserName]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-[calc(100%+250px)] box-border p-[20px] pt-[20p]">
|
<div className="h-full w-[calc(100%+250px)] box-border p-[20px] pt-[20p]">
|
||||||
<div className="h-full box-border">
|
<div className="h-full box-border">
|
||||||
|
|
||||||
<div className="relative flex items-center mb-[20px]">
|
<div className="relative flex items-center mb-[20px]">
|
||||||
<div className={cn("h-[50px] text-[40px] font-bold text-liquid-white flex items-center")}>
|
<div
|
||||||
|
className={cn(
|
||||||
|
"h-[50px] text-[40px] font-bold text-liquid-white flex items-center"
|
||||||
|
)}
|
||||||
|
>
|
||||||
Группы
|
Группы
|
||||||
</div>
|
</div>
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
@@ -55,14 +62,23 @@ const Groups = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-liquid-lighter h-[50px] mb-[20px]">
|
<div className="bg-liquid-lighter h-[50px] mb-[20px]"></div>
|
||||||
|
|
||||||
</div>
|
<GroupsBlock
|
||||||
|
className="mb-[20px]"
|
||||||
|
title="Управляемые"
|
||||||
<GroupsBlock className="mb-[20px]" title="Управляемые" groups={groups.filter((v) => v.visible && (v.role == "owner" || v.role == "menager"))} />
|
groups={managedGroups}
|
||||||
<GroupsBlock className="mb-[20px]" title="Текущие" groups={groups.filter((v) => v.visible && (v.role == "member" || v.role == "viewer"))} />
|
/>
|
||||||
<GroupsBlock className="mb-[20px]" title="Скрытые" groups={groups.filter((v) => v.visible == false)} />
|
<GroupsBlock
|
||||||
|
className="mb-[20px]"
|
||||||
|
title="Текущие"
|
||||||
|
groups={currentGroups}
|
||||||
|
/>
|
||||||
|
<GroupsBlock
|
||||||
|
className="mb-[20px]"
|
||||||
|
title="Скрытые"
|
||||||
|
groups={hiddenGroups} // пока пусто
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,14 +2,7 @@ import { useState, FC } from "react";
|
|||||||
import GroupItem from "./GroupItem";
|
import GroupItem from "./GroupItem";
|
||||||
import { cn } from "../../../lib/cn";
|
import { cn } from "../../../lib/cn";
|
||||||
import { ChevroneDown } from "../../../assets/icons/groups";
|
import { ChevroneDown } from "../../../assets/icons/groups";
|
||||||
|
import { Group } from "../../../redux/slices/groups";
|
||||||
|
|
||||||
export interface Group {
|
|
||||||
id: number;
|
|
||||||
role: "menager" | "member" | "owner" | "viewer";
|
|
||||||
visible: boolean;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GroupsBlockProps {
|
interface GroupsBlockProps {
|
||||||
groups: Group[];
|
groups: Group[];
|
||||||
@@ -47,7 +40,7 @@ const GroupsBlock: FC<GroupsBlockProps> = ({ groups, title, className }) => {
|
|||||||
|
|
||||||
<div className="grid grid-cols-3 gap-[20px] pt-[20px] pb-[20px] box-border">
|
<div className="grid grid-cols-3 gap-[20px] pt-[20px] pb-[20px] box-border">
|
||||||
{
|
{
|
||||||
groups.map((v, i) => <GroupItem key={i} {...v} />)
|
groups.map((v, i) => <GroupItem key={i} id={v.id} visible={true} role={"owner"} name={v.name}/>)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user