Skip to content

Commit

Permalink
task create and assign make real-time
Browse files Browse the repository at this point in the history
  • Loading branch information
Md-Rubel-Ahmed-Rana committed Feb 15, 2024
1 parent 3d20bc8 commit ae05b3e
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 122 deletions.
11 changes: 11 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ io.on("connection", (socket) => {
socket.to(data.recipient.userId).emit("notification", data);
});

// tasks room
socket.on("task-room", (projectId: string) => {
console.log("task room", projectId);
socket.join(projectId);
});

socket.on("task", (data) => {
console.log("New task", data);
socket.to(data.project).emit("task", data);
});

socket.on("disconnect", () => {
console.log("User disconnected");
});
Expand Down
16 changes: 6 additions & 10 deletions backend/src/services/project.service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { INotification } from "@/interfaces/notification.interface";
import { IProject } from "@/interfaces/project.interface";
import { Project } from "@/models/project.model";
import ApiError from "@/shared/apiError";
Expand Down Expand Up @@ -34,7 +35,7 @@ class Service {
}

async assignedProjects(memberId: string): Promise<GetOnlyProjectDTO[]> {
const result = await Project.find({ "members.member": memberId });
const result = await Project.find({ members: memberId });
const mappedData = mapper.mapArray(
result,
ProjectEntity as ModelIdentifier,
Expand Down Expand Up @@ -77,7 +78,7 @@ class Service {
async addMember(
projectId: string,
memberId: string
): Promise<UpdateProjectDTO | null> {
): Promise<INotification | undefined> {
const project = await Project.findById(projectId);

if (!project) {
Expand All @@ -96,7 +97,7 @@ class Service {
);
}

const result = await Project.findByIdAndUpdate(
await Project.findByIdAndUpdate(
projectId,
{
$push: { members: memberId },
Expand All @@ -105,21 +106,16 @@ class Service {
);

if (project?.user) {
await NotificationService.sendNotification(
const result = await NotificationService.sendNotification(
project?.user,
memberId,
"project_invitation",
"Assigned to project",
`You've been added to a project (${project?.name})`,
`projects?team=${project?.team}&id=${project._id}&name=${project?.name}`
);
return result;
}
const mappedData = mapper.map(
result,
ProjectEntity as ModelIdentifier,
UpdateProjectDTO
);
return mappedData;
}

async removeMember(
Expand Down
45 changes: 31 additions & 14 deletions backend/src/services/task.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,46 @@ import { GetTaskDTO } from "@/dto/task/get";
import { CreateTaskDTO } from "@/dto/task/create";
import { UpdateTaskDTO } from "@/dto/task/update";
import { DeleteTaskDTO } from "@/dto/task/delete";
import { INotification } from "@/interfaces/notification.interface";

type ICreateTask = {
notification: INotification | undefined;
data: GetTaskDTO | undefined;
};

class Service {
async createTask(data: ITask): Promise<CreateTaskDTO> {
const result = await Task.create(data);
async createTask(taskData: ITask): Promise<ICreateTask | undefined> {
const newTask = await Task.create(taskData);
// send notification to add new task to project
if (data?.assignedTo && data?.assignedBy) {
await NotificationService.sendNotification(
data.assignedBy,
data.assignedTo,
if (taskData?.assignedTo && taskData?.assignedBy) {
const notification = await NotificationService.sendNotification(
taskData.assignedBy,
taskData.assignedTo,
"task_assignment",
"Assigned to task",
`You've been assigned to a task (${data?.name})`,
`You've been assigned to a task (${taskData?.name})`,
"projects"
);
}

const mappedData = mapper.map(
result,
TaskEntity as ModelIdentifier,
CreateTaskDTO
);
const result = await Task.findById(newTask?.id).populate([
{
path: "assignedTo",
model: "User",
},
{
path: "assignedBy",
model: "User",
},
]);

return mappedData;
const data = mapper.map(
result,
TaskEntity as ModelIdentifier,
GetTaskDTO
);

return { notification, data };
}
}

async getTasksByProjectId(projectId: string): Promise<GetTaskDTO[]> {
Expand Down
28 changes: 12 additions & 16 deletions backend/src/services/team.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,18 @@ class Service {

const result = await Team.findById(teamId).select({ name: 1, admin: 1 });

// remove this member from projects by member id //
await Project.updateMany(
{ team: teamId },
{ $pull: { members: { memberId: memberId } } }
);

// update leave request for team
await TeamLeaveRequest.findOneAndUpdate(
{ team: teamId },
{ $set: { status: "accepted" } }
).sort({ createdAt: -1 });

if (result && result?.admin) {
await NotificationService.sendNotification(
result?.admin,
Expand All @@ -191,22 +203,6 @@ class Service {
`dashboard?uId=${memberId}activeView=joined-teams`
);
}

// remove this member from projects
const projects = await Project.find({ "members.member": memberId });
const updatePromises = projects.map(async (project) => {
project.members = project.members.filter(
(member: any) => member?.member.toString() !== memberId
);
return project.save();
});
await Promise.all(updatePromises);

// update leave request for team
await TeamLeaveRequest.findOneAndUpdate(
{ team: teamId },
{ $set: { status: "accepted" } }
).sort({ createdAt: -1 });
}
}
export const TeamService = new Service();
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { Fragment, useState } from "react";
import React, { Fragment, useContext, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import Select from "react-select";
import Swal from "sweetalert2";
import { useAddMemberMutation } from "@/features/project";
import { useGetActiveMembersQuery } from "@/features/team";
import { IUser } from "@/interfaces/user.interface";
import customStyles from "@/utils/reactSelectCustomStyle";
import { projectMemberRoles } from "@/constants/projectMemberRoles";
import { SocketContext } from "@/context/SocketContext";

type Props = {
isOpen: boolean;
Expand All @@ -16,13 +16,13 @@ type Props = {
};

const AddMemberToProject = ({ isOpen, setIsOpen, projectId, team }: Props) => {
const { socket }: any = useContext(SocketContext);
const closeModal = () => {
setIsOpen(false);
};

const [addNewMember] = useAddMemberMutation();
const [newMember, setNewMember] = useState({ label: "", value: "" });
const [role, setRole] = useState("");
const { data: memberData } = useGetActiveMembersQuery(team?.id);
const members = memberData?.data?.map((member: IUser) => ({
value: member?.id,
Expand All @@ -35,12 +35,12 @@ const AddMemberToProject = ({ isOpen, setIsOpen, projectId, team }: Props) => {
const memberData = {
projectId,
memberId: newMember.value,
role,
};

const result: any = await addNewMember(memberData);

if (result?.data?.success) {
socket.emit("notification", result?.data?.data);
Swal.fire({
position: "center",
icon: "success",
Expand Down Expand Up @@ -115,32 +115,6 @@ const AddMemberToProject = ({ isOpen, setIsOpen, projectId, team }: Props) => {
}}
/>
</div>
<div className="relative w-full py-2">
<p className="text-stone-500 dark:text-white mb-2">
Assign a role
</p>
<Select
required
options={projectMemberRoles?.map((role) => ({
label: role,
value: role,
}))}
styles={customStyles}
onChange={(role: any) => setRole(role?.value)}
placeholder="Type a name to assign a member to project"
className="mt-1 w-full"
classNamePrefix="select2-selection"
noOptionsMessage={({ inputValue }: any) =>
!inputValue &&
`No active members in your team: ${team?.name}. Please invite members to join your team`
}
components={{
DropdownIndicator: () => null,
IndicatorSeparator: () => null,
}}
/>
</div>

<div className="mt-5 lg:flex justify-between">
<button
onClick={closeModal}
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/components/pages/projects/desktop/ProjectPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useContext, useEffect, useState } from "react";
import ProjectSidebar from "./ProjectSidebar";
import AddMemberToProject from "./AddMemberToProject";
import { useLoggedInUserQuery } from "@/features/user";
Expand All @@ -15,15 +15,17 @@ import RemoveMemberFromProject from "./RemoveMemberFromProject";
import { useRouter } from "next/router";
import Swal from "sweetalert2";
import ParentTask from "../../tasks/ParentTask";
import { SocketContext } from "@/context/SocketContext";

const Projects = () => {
const { socket }: any = useContext(SocketContext);
const { query } = useRouter();
const [activeProject, setActiveProject] = useState<any>("");
const [isOpen, setIsOpen] = useState(false);
const [isRemove, setIsRemove] = useState(false);
const { data: userData } = useLoggedInUserQuery({});
const user: IUser = userData?.data;
const { data: projectData } = useGetSingleProjectQuery(query?.id);
const { data: projectData }: any = useGetSingleProjectQuery(query?.id);
const project: IProject = projectData?.data;
const { data: projects } = useMyProjectsQuery(user?.id);
const { data: assignedProjects } = useAssignedProjectsQuery(user?.id);
Expand All @@ -32,6 +34,8 @@ const Projects = () => {
user?.id
);

console.log("Project for task", project);

const handleLeaveRequest = async () => {
Swal.fire({
title: "So sad",
Expand Down Expand Up @@ -64,6 +68,11 @@ const Projects = () => {
setActiveProject(query?.id);
}, [query?.id]);

// connect to socket team room
useEffect(() => {
socket.emit("task-room", project?.id);
}, [socket, project?.id]);

return (
<div className="flex h-screen">
<ProjectSidebar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const ProjectSidebar = ({ activeProject, setActiveProject }: any) => {
const { data: assignedProjects } = useAssignedProjectsQuery(user?.id);
const router = useRouter();

console.log(assignedProjects);

const handleEditProject = (project: IProject) => {
setIsEdit(true);
setEditableProject(project);
Expand Down
10 changes: 7 additions & 3 deletions frontend/src/components/pages/tasks/CreateTaskModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import { useGetSingleProjectQuery } from "@/features/project";
import { INewTask } from "@/interfaces/task.interface";
import { useCreateTaskMutation } from "@/features/task";
import customStyles from "@/utils/reactSelectCustomStyle";
import { SocketContext } from "@/context/SocketContext";

const CreateTaskModal = ({ isOpen, setIsOpen, project, status }: any) => {
const { socket }: any = useContext(SocketContext);
const closeModal = () => {
setIsOpen(false);
};
Expand All @@ -29,7 +31,9 @@ const CreateTaskModal = ({ isOpen, setIsOpen, project, status }: any) => {
data.project = project.id;
const result: any = await createTask(data);
if (result?.data?.success) {
window.location.reload();
// window.location.reload();
socket.emit("task", result?.data?.data?.data);
socket.emit("notification", result?.data?.data?.notification);
Swal.fire({
position: "center",
icon: "success",
Expand Down Expand Up @@ -90,8 +94,8 @@ const CreateTaskModal = ({ isOpen, setIsOpen, project, status }: any) => {
options={
members &&
members?.map((member: any) => ({
value: member?.member?.id,
label: member?.member?.name,
value: member?.id,
label: member?.name,
}))
}
styles={customStyles}
Expand Down
Loading

0 comments on commit ae05b3e

Please sign in to comment.