dont work
This commit is contained in:
@@ -48,9 +48,8 @@ export const Modal: React.FC<ModalProps> = ({
|
|||||||
transition={{ duration: 0.15 }}
|
transition={{ duration: 0.15 }}
|
||||||
className={cn(
|
className={cn(
|
||||||
" fixed top-0 left-0 h-svh w-svw backdrop-filter transition-all z-50",
|
" fixed top-0 left-0 h-svh w-svw backdrop-filter transition-all z-50",
|
||||||
// " pointer-events-none",
|
backdrop == "blur" && open && "backdrop-blur-sm",
|
||||||
backdrop == "blur" && open ? "backdrop-blur-sm" : "",
|
backdrop == "opaque" && open && "bg-[#00000055] pointer-events-none",
|
||||||
backdrop == "opaque" && open ? "bg-[#00000055]" : ""
|
|
||||||
)}
|
)}
|
||||||
></motion.div>
|
></motion.div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import axios from "../../axios";
|
|||||||
|
|
||||||
// ─── Типы ────────────────────────────────────────────
|
// ─── Типы ────────────────────────────────────────────
|
||||||
|
|
||||||
|
type Status = "idle" | "loading" | "successful" | "failed";
|
||||||
|
|
||||||
export interface GroupMember {
|
export interface GroupMember {
|
||||||
userId: number;
|
userId: number;
|
||||||
username: string;
|
username: string;
|
||||||
@@ -20,17 +22,34 @@ export interface Group {
|
|||||||
interface GroupsState {
|
interface GroupsState {
|
||||||
groups: Group[];
|
groups: Group[];
|
||||||
currentGroup: Group | null;
|
currentGroup: Group | null;
|
||||||
status: "idle" | "loading" | "successful" | "failed";
|
statuses: {
|
||||||
|
create: Status;
|
||||||
|
update: Status;
|
||||||
|
delete: Status;
|
||||||
|
fetchMy: Status;
|
||||||
|
fetchById: Status;
|
||||||
|
addMember: Status;
|
||||||
|
removeMember: Status;
|
||||||
|
};
|
||||||
error: string | null;
|
error: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: GroupsState = {
|
const initialState: GroupsState = {
|
||||||
groups: [],
|
groups: [],
|
||||||
currentGroup: null,
|
currentGroup: null,
|
||||||
status: "idle",
|
statuses: {
|
||||||
|
create: "idle",
|
||||||
|
update: "idle",
|
||||||
|
delete: "idle",
|
||||||
|
fetchMy: "idle",
|
||||||
|
fetchById: "idle",
|
||||||
|
addMember: "idle",
|
||||||
|
removeMember: "idle",
|
||||||
|
},
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ─── Async Thunks ─────────────────────────────────────
|
// ─── Async Thunks ─────────────────────────────────────
|
||||||
|
|
||||||
// POST /groups
|
// POST /groups
|
||||||
@@ -146,111 +165,117 @@ const groupsSlice = createSlice({
|
|||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
// ─── CREATE GROUP ───
|
// ─── CREATE GROUP ───
|
||||||
builder.addCase(createGroup.pending, (state) => {
|
builder.addCase(createGroup.pending, (state) => {
|
||||||
state.status = "loading";
|
state.statuses.create = "loading";
|
||||||
state.error = null;
|
state.error = null;
|
||||||
});
|
});
|
||||||
builder.addCase(createGroup.fulfilled, (state, action: PayloadAction<Group>) => {
|
builder.addCase(createGroup.fulfilled, (state, action: PayloadAction<Group>) => {
|
||||||
state.status = "successful";
|
state.statuses.create = "successful";
|
||||||
state.groups.push(action.payload);
|
state.groups.push(action.payload);
|
||||||
});
|
});
|
||||||
builder.addCase(createGroup.rejected, (state, action: PayloadAction<any>) => {
|
builder.addCase(createGroup.rejected, (state, action: PayloadAction<any>) => {
|
||||||
state.status = "failed";
|
state.statuses.create = "failed";
|
||||||
state.error = action.payload;
|
state.error = action.payload;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// ─── UPDATE GROUP ───
|
// ─── UPDATE GROUP ───
|
||||||
builder.addCase(updateGroup.pending, (state) => {
|
builder.addCase(updateGroup.pending, (state) => {
|
||||||
state.status = "loading";
|
state.statuses.update = "loading";
|
||||||
state.error = null;
|
state.error = null;
|
||||||
});
|
});
|
||||||
builder.addCase(updateGroup.fulfilled, (state, action: PayloadAction<Group>) => {
|
builder.addCase(updateGroup.fulfilled, (state, action: PayloadAction<Group>) => {
|
||||||
state.status = "successful";
|
state.statuses.update = "successful";
|
||||||
const index = state.groups.findIndex((g) => g.id === action.payload.id);
|
const index = state.groups.findIndex((g) => g.id === action.payload.id);
|
||||||
if (index !== -1) state.groups[index] = action.payload;
|
if (index !== -1) state.groups[index] = action.payload;
|
||||||
if (state.currentGroup?.id === action.payload.id)
|
if (state.currentGroup?.id === action.payload.id) {
|
||||||
state.currentGroup = action.payload;
|
state.currentGroup = action.payload;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
builder.addCase(updateGroup.rejected, (state, action: PayloadAction<any>) => {
|
builder.addCase(updateGroup.rejected, (state, action: PayloadAction<any>) => {
|
||||||
state.status = "failed";
|
state.statuses.update = "failed";
|
||||||
state.error = action.payload;
|
state.error = action.payload;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ─── DELETE GROUP ───
|
// ─── DELETE GROUP ───
|
||||||
builder.addCase(deleteGroup.pending, (state) => {
|
builder.addCase(deleteGroup.pending, (state) => {
|
||||||
state.status = "loading";
|
state.statuses.delete = "loading";
|
||||||
state.error = null;
|
state.error = null;
|
||||||
});
|
});
|
||||||
builder.addCase(deleteGroup.fulfilled, (state, action: PayloadAction<number>) => {
|
builder.addCase(deleteGroup.fulfilled, (state, action: PayloadAction<number>) => {
|
||||||
state.status = "successful";
|
state.statuses.delete = "successful";
|
||||||
state.groups = state.groups.filter((g) => g.id !== action.payload);
|
state.groups = state.groups.filter((g) => g.id !== action.payload);
|
||||||
if (state.currentGroup?.id === action.payload) state.currentGroup = null;
|
if (state.currentGroup?.id === action.payload) state.currentGroup = null;
|
||||||
});
|
});
|
||||||
builder.addCase(deleteGroup.rejected, (state, action: PayloadAction<any>) => {
|
builder.addCase(deleteGroup.rejected, (state, action: PayloadAction<any>) => {
|
||||||
state.status = "failed";
|
state.statuses.delete = "failed";
|
||||||
state.error = action.payload;
|
state.error = action.payload;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// ─── FETCH MY GROUPS ───
|
// ─── FETCH MY GROUPS ───
|
||||||
builder.addCase(fetchMyGroups.pending, (state) => {
|
builder.addCase(fetchMyGroups.pending, (state) => {
|
||||||
state.status = "loading";
|
state.statuses.fetchMy = "loading";
|
||||||
state.error = null;
|
state.error = null;
|
||||||
});
|
});
|
||||||
builder.addCase(fetchMyGroups.fulfilled, (state, action: PayloadAction<Group[]>) => {
|
builder.addCase(fetchMyGroups.fulfilled, (state, action: PayloadAction<Group[]>) => {
|
||||||
state.status = "successful";
|
state.statuses.fetchMy = "successful";
|
||||||
state.groups = action.payload;
|
state.groups = action.payload;
|
||||||
});
|
});
|
||||||
builder.addCase(fetchMyGroups.rejected, (state, action: PayloadAction<any>) => {
|
builder.addCase(fetchMyGroups.rejected, (state, action: PayloadAction<any>) => {
|
||||||
state.status = "failed";
|
state.statuses.fetchMy = "failed";
|
||||||
state.error = action.payload;
|
state.error = action.payload;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// ─── FETCH GROUP BY ID ───
|
// ─── FETCH GROUP BY ID ───
|
||||||
builder.addCase(fetchGroupById.pending, (state) => {
|
builder.addCase(fetchGroupById.pending, (state) => {
|
||||||
state.status = "loading";
|
state.statuses.fetchById = "loading";
|
||||||
state.error = null;
|
state.error = null;
|
||||||
});
|
});
|
||||||
builder.addCase(fetchGroupById.fulfilled, (state, action: PayloadAction<Group>) => {
|
builder.addCase(fetchGroupById.fulfilled, (state, action: PayloadAction<Group>) => {
|
||||||
state.status = "successful";
|
state.statuses.fetchById = "successful";
|
||||||
state.currentGroup = action.payload;
|
state.currentGroup = action.payload;
|
||||||
});
|
});
|
||||||
builder.addCase(fetchGroupById.rejected, (state, action: PayloadAction<any>) => {
|
builder.addCase(fetchGroupById.rejected, (state, action: PayloadAction<any>) => {
|
||||||
state.status = "failed";
|
state.statuses.fetchById = "failed";
|
||||||
state.error = action.payload;
|
state.error = action.payload;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// ─── ADD MEMBER ───
|
// ─── ADD MEMBER ───
|
||||||
builder.addCase(addGroupMember.pending, (state) => {
|
builder.addCase(addGroupMember.pending, (state) => {
|
||||||
state.status = "loading";
|
state.statuses.addMember = "loading";
|
||||||
state.error = null;
|
state.error = null;
|
||||||
});
|
});
|
||||||
builder.addCase(addGroupMember.fulfilled, (state) => {
|
builder.addCase(addGroupMember.fulfilled, (state) => {
|
||||||
state.status = "successful";
|
state.statuses.addMember = "successful";
|
||||||
});
|
});
|
||||||
builder.addCase(addGroupMember.rejected, (state, action: PayloadAction<any>) => {
|
builder.addCase(addGroupMember.rejected, (state, action: PayloadAction<any>) => {
|
||||||
state.status = "failed";
|
state.statuses.addMember = "failed";
|
||||||
state.error = action.payload;
|
state.error = action.payload;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// ─── REMOVE MEMBER ───
|
// ─── REMOVE MEMBER ───
|
||||||
builder.addCase(removeGroupMember.pending, (state) => {
|
builder.addCase(removeGroupMember.pending, (state) => {
|
||||||
state.status = "loading";
|
state.statuses.removeMember = "loading";
|
||||||
state.error = null;
|
state.error = null;
|
||||||
});
|
});
|
||||||
builder.addCase(
|
builder.addCase(removeGroupMember.fulfilled, (state, action: PayloadAction<{ groupId: number; memberId: number }>) => {
|
||||||
removeGroupMember.fulfilled,
|
state.statuses.removeMember = "successful";
|
||||||
(state, action: PayloadAction<{ groupId: number; memberId: number }>) => {
|
if (state.currentGroup && state.currentGroup.id === action.payload.groupId) {
|
||||||
state.status = "successful";
|
state.currentGroup.members = state.currentGroup.members.filter(
|
||||||
if (state.currentGroup && state.currentGroup.id === action.payload.groupId) {
|
(m) => m.userId !== action.payload.memberId
|
||||||
state.currentGroup.members = state.currentGroup.members.filter(
|
);
|
||||||
(m) => m.userId !== action.payload.memberId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
builder.addCase(removeGroupMember.rejected, (state, action: PayloadAction<any>) => {
|
builder.addCase(removeGroupMember.rejected, (state, action: PayloadAction<any>) => {
|
||||||
state.status = "failed";
|
state.statuses.removeMember = "failed";
|
||||||
state.error = action.payload;
|
state.error = action.payload;
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,37 @@
|
|||||||
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";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { GroupUpdate } from "./Groups";
|
||||||
|
|
||||||
export interface GroupItemProps {
|
export interface GroupItemProps {
|
||||||
id: number;
|
id: number;
|
||||||
role: "menager" | "member" | "owner" | "viewer";
|
role: "menager" | "member" | "owner" | "viewer";
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
description: string;
|
||||||
|
setUpdateActive: (value: any) => void;
|
||||||
|
setUpdateGroup: (value: GroupUpdate) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface IconComponentProps {
|
interface IconComponentProps {
|
||||||
src: string;
|
src: string;
|
||||||
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const IconComponent: React.FC<IconComponentProps> = ({
|
const IconComponent: React.FC<IconComponentProps> = ({
|
||||||
src
|
src, onClick = () => void
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
return <img
|
return <img
|
||||||
src={src}
|
src={src}
|
||||||
|
onClick={() => onClick()}
|
||||||
className="hover:bg-liquid-light rounded-[5px] cursor-pointer transition-all duration-300"
|
className="hover:bg-liquid-light rounded-[5px] cursor-pointer transition-all duration-300"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
const GroupItem: React.FC<GroupItemProps> = ({
|
const GroupItem: React.FC<GroupItemProps> = ({
|
||||||
id, name, visible, role
|
id, name, visible, role, description, setUpdateGroup, setUpdateActive
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -45,7 +51,10 @@ const GroupItem: React.FC<GroupItemProps> = ({
|
|||||||
(role == "menager" || role == "owner") && <IconComponent src={UserAdd}/>
|
(role == "menager" || role == "owner") && <IconComponent src={UserAdd}/>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(role == "menager" || role == "owner") && <IconComponent src={Edit}/>
|
(role == "menager" || role == "owner") && <IconComponent src={Edit} onClick={() => {
|
||||||
|
setUpdateGroup({id, });
|
||||||
|
setUpdateActive(true);
|
||||||
|
}} />
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
visible == false && <IconComponent src={EyeOpen} />
|
visible == false && <IconComponent src={EyeOpen} />
|
||||||
|
|||||||
@@ -5,12 +5,23 @@ 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";
|
import { fetchMyGroups } from "../../../redux/slices/groups";
|
||||||
import { Modal } from "../../../components/modal/Modal";
|
import ModalCreate from "./ModalCreate";
|
||||||
|
import ModalUpdate from "./ModalUpdate";
|
||||||
|
|
||||||
|
export interface GroupUpdate {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
const Groups = () => {
|
const Groups = () => {
|
||||||
const [modalActive, setModalActive] = useState<boolean>(false);
|
const [modalActive, setModalActive] = useState<boolean>(false);
|
||||||
|
const [modelUpdateActive, setModalUpdateActive] = useState<boolean>(false);
|
||||||
|
const [updateGroup, setUpdateGroup] = useState<GroupUpdate>({id: 0, name: "", description: ""});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
|
||||||
// Берём группы из стора
|
// Берём группы из стора
|
||||||
const groups = useAppSelector((store) => store.groups.groups);
|
const groups = useAppSelector((store) => store.groups.groups);
|
||||||
|
|
||||||
@@ -84,9 +95,8 @@ const Groups = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<Modal className="bg-liquid-lighter p-[20px] rounded-[20px]" onOpenChange={setModalActive} open={modalActive} backdrop="blur" >
|
<ModalCreate setActive={setModalActive} active={modalActive} />
|
||||||
<div>modal</div>
|
<ModalUpdate setActive={setModalUpdateActive} active={modelUpdateActive} groupId={updateGroup.id} groupName={updateGroup.name}/>
|
||||||
</Modal>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
43
src/views/home/groups/ModalCreate.tsx
Normal file
43
src/views/home/groups/ModalCreate.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { FC, useEffect, useState } from "react";
|
||||||
|
import { Modal } from "../../../components/modal/Modal";
|
||||||
|
import { PrimaryButton } from "../../../components/button/PrimaryButton";
|
||||||
|
import { SecondaryButton } from "../../../components/button/SecondaryButton";
|
||||||
|
import { Input } from "../../../components/input/Input";
|
||||||
|
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
|
||||||
|
import { createGroup } from "../../../redux/slices/groups";
|
||||||
|
|
||||||
|
interface ModalCreateProps {
|
||||||
|
active: boolean;
|
||||||
|
setActive: (value: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
|
||||||
|
const [name, setName] = useState<string>("");
|
||||||
|
const [description, setDescription] = useState<string>("");
|
||||||
|
const status = useAppSelector((state) => state.groups.statuses.create);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status == "successful"){
|
||||||
|
setActive(false);
|
||||||
|
}
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal className="bg-liquid-background border-liquid-lighter border-[2px] p-[25px] rounded-[20px] text-liquid-white" onOpenChange={setActive} open={active} backdrop="blur" >
|
||||||
|
<div className="w-[500px]">
|
||||||
|
<div className="font-bold text-[30px]">Создать группу</div>
|
||||||
|
<Input name="name" autocomplete="name" className="mt-[10px]" type="text" label="Название" onChange={(v) => { setName(v)}} placeholder="login" />
|
||||||
|
<Input name="description" autocomplete="description" className="mt-[10px]" type="text" label="Описание" onChange={(v) => { setDescription(v)}} placeholder="login" />
|
||||||
|
|
||||||
|
<div className="flex flex-row w-full items-center justify-end mt-[20px] gap-[20px]">
|
||||||
|
<PrimaryButton onClick={() => {dispatch(createGroup({name, description}))}} text="Создать" disabled={status=="loading"}/>
|
||||||
|
<SecondaryButton onClick={() => {setActive(false);}} text="Отмена" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalCreate;
|
||||||
|
|
||||||
45
src/views/home/groups/ModalUpdate.tsx
Normal file
45
src/views/home/groups/ModalUpdate.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { FC, useEffect, useState } from "react";
|
||||||
|
import { Modal } from "../../../components/modal/Modal";
|
||||||
|
import { PrimaryButton } from "../../../components/button/PrimaryButton";
|
||||||
|
import { SecondaryButton } from "../../../components/button/SecondaryButton";
|
||||||
|
import { Input } from "../../../components/input/Input";
|
||||||
|
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
|
||||||
|
import { createGroup } from "../../../redux/slices/groups";
|
||||||
|
|
||||||
|
interface ModalUpdateProps {
|
||||||
|
active: boolean;
|
||||||
|
setActive: (value: boolean) => void;
|
||||||
|
groupId: number;
|
||||||
|
groupName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalUpdate: FC<ModalUpdateProps> = ({ active, setActive, groupName, groupId }) => {
|
||||||
|
const [name, setName] = useState<string>("");
|
||||||
|
const [description, setDescription] = useState<string>("");
|
||||||
|
const status = useAppSelector((state) => state.groups.statuses.create);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status == "successful"){
|
||||||
|
setActive(false);
|
||||||
|
}
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal className="bg-liquid-background border-liquid-lighter border-[2px] p-[25px] rounded-[20px] text-liquid-white" onOpenChange={setActive} open={active} backdrop="blur" >
|
||||||
|
<div className="w-[500px]">
|
||||||
|
<div className="font-bold text-[30px]">Изменить группу {groupName} #{groupId}</div>
|
||||||
|
<Input name="name" autocomplete="name" className="mt-[10px]" type="text" label="Новое название" defaultState={groupName} onChange={(v) => { setName(v)}} placeholder="login" />
|
||||||
|
<Input name="description" autocomplete="description" className="mt-[10px]" type="text" label="Описание" onChange={(v) => { setDescription(v)}} placeholder="login" />
|
||||||
|
|
||||||
|
<div className="flex flex-row w-full items-center justify-end mt-[20px] gap-[20px]">
|
||||||
|
<PrimaryButton onClick={() => {dispatch(createGroup({name, description}))}} text="Обновить" disabled={status=="loading"}/>
|
||||||
|
<SecondaryButton onClick={() => {setActive(false);}} text="Отмена" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalUpdate;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ import { DropDownList } from "../../../components/drop-down-list/DropDownList";
|
|||||||
|
|
||||||
const languageMap: Record<string, string> = {
|
const languageMap: Record<string, string> = {
|
||||||
c: "cpp",
|
c: "cpp",
|
||||||
cpp: "cpp",
|
"C++": "cpp",
|
||||||
java: "java",
|
java: "java",
|
||||||
python: "python",
|
python: "python",
|
||||||
pascal: "pascal",
|
pascal: "pascal",
|
||||||
|
|||||||
Reference in New Issue
Block a user