Skip to content

Commit

Permalink
Feat: #265 프로젝트 수정 및 삭제 권한 설정 및 컴포넌트 분리
Browse files Browse the repository at this point in the history
  • Loading branch information
ice-bear98 committed Nov 28, 2024
1 parent 4876100 commit e8da37e
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 110 deletions.
12 changes: 6 additions & 6 deletions src/components/modal/team/UpdateModalTeam.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default function UpdateModalTeam({ teamId, onClose: handleClose }: Update
const [keyword, setKeyword] = useState('');
const { toastInfo } = useToast();

const { coworkers, isLoading: isTeamCoworkersLoading } = useReadTeamCoworkers(teamId);
const { teamCoworkers, isLoading: isTeamCoworkersLoading } = useReadTeamCoworkers(teamId);
const { teamList, isLoading: isTeamListLoading } = useReadTeams();
const { teamInfo } = useReadTeamInfo(Number(teamId));
const teamNameList = useMemo(() => getTeamNameList(teamList, teamInfo?.teamName), [teamList, teamInfo?.teamName]);
Expand All @@ -66,10 +66,10 @@ export default function UpdateModalTeam({ teamId, onClose: handleClose }: Update
} = methods;

useEffect(() => {
if (teamInfo?.teamName && teamInfo?.content && coworkers) {
reset({ teamName: teamInfo.teamName, content: teamInfo.content, coworkers });
if (teamInfo?.teamName && teamInfo?.content && teamCoworkers) {
reset({ teamName: teamInfo.teamName, content: teamInfo.content });
}
}, [teamInfo, coworkers, reset]);
}, [teamInfo, teamCoworkers, reset]);

const handleKeywordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setKeyword(e.target.value.trim());
Expand All @@ -81,7 +81,7 @@ export default function UpdateModalTeam({ teamId, onClose: handleClose }: Update
};

const handleCoworkersClick = (userId: User['userId'], roleName: TeamRoleName) => {
const isIncludedUser = coworkers.find((coworker) => coworker.userId === userId);
const isIncludedUser = teamCoworkers.find((coworker) => coworker.userId === userId);
if (isIncludedUser) return toastInfo('이미 포함된 팀원입니다');

addTeamCoworkerMutate({ userId, roleName });
Expand Down Expand Up @@ -142,7 +142,7 @@ export default function UpdateModalTeam({ teamId, onClose: handleClose }: Update
onUserClick={(user) => handleCoworkersClick(user.userId, TEAM_DEFAULT_ROLE)}
/>
<div className="flex flex-wrap">
{coworkers.map(({ userId, nickname, roleName }) => (
{teamCoworkers.map(({ userId, nickname, roleName }) => (
<UserRoleSelectBox
key={userId}
userId={userId}
Expand Down
8 changes: 8 additions & 0 deletions src/components/project/EmptyProjectItemList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function EmptyProjectItemList() {
return (
<div className="flex h-full items-center justify-center text-center">
진행중인 프로젝트가 없습니다! <br />
새로운 프로젝트를 생성해보세요 😄
</div>
);
}
89 changes: 89 additions & 0 deletions src/components/project/ProjectItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Link } from 'react-router-dom';
import { FaRegTrashAlt } from 'react-icons/fa';
import { IoIosSettings } from 'react-icons/io';
import useModal from '@hooks/useModal';
import { useDeleteProject, useDeleteProjectUser, useReadProjectCoworkers } from '@hooks/query/useProjectQuery';
import useStore from '@stores/useStore';
import UpdateModalProject from '@components/modal/project/UpdateModalProject';

import type { Team } from '@/types/TeamType';
import type { Project } from '@/types/ProjectType';
import useToast from '@/hooks/useToast';

type ProjectItemProps = {
teamId: Team['teamId'];
project: Project;
};

export default function ProjectItem({ teamId, project }: ProjectItemProps) {
const { toastWarn } = useToast();
const { showModal: showUpdateModal, openModal: openUpdateModal, closeModal: closeUpdateModal } = useModal();
const {
userInfo: { userId },
} = useStore();

const { projectCoworkers } = useReadProjectCoworkers(project.projectId);
const { mutate: deleteProjectMutate } = useDeleteProject(teamId);

const userProjectRole = projectCoworkers.find((coworker) => coworker.userId === userId)?.roleName;

const handleOpenUpdateModal = () => {
if (userProjectRole !== 'ADMIN') return toastWarn('프로젝트 수정 권한이 없습니다.');
openUpdateModal();
};

const handleDeleteClick = (projectId: Project['projectId']) => {
if (userProjectRole !== 'ADMIN') return toastWarn('프로젝트 삭제 권한이 없습니다.');
deleteProjectMutate(projectId);
};

return (
<>
<li key={project.projectId} className="min-w-300 space-y-2 text-sm">
<Link to={`/teams/${teamId}/projects/${project.projectId}`} className="flex h-50 items-center border p-8">
<div className="flex max-h-full grow">
<div className="max-h-full w-60 shrink-0">
<small className="flex flex-col text-xs font-bold text-category">project</small>
<p className="truncate">{project.projectName}</p>
</div>

<div className="flex max-h-full max-w-350 flex-col px-4">
<small className="text-xs font-bold text-category">desc</small>
<p className="truncate">{project.content}</p>
</div>
</div>

<div className="mr-6 flex shrink-0 space-x-10">
<button
type="button"
className="flex items-center text-main hover:brightness-50"
aria-label="Settings"
onClick={(e) => {
e.preventDefault();
handleOpenUpdateModal();
}}
>
<IoIosSettings size={20} className="mr-2" />
setting
</button>

<button
type="button"
className="hover:brightness-200"
aria-label="Delete"
onClick={(e) => {
e.preventDefault();
handleDeleteClick(project.projectId);
}}
>
<FaRegTrashAlt size={20} />
</button>
</div>
</Link>
</li>
{showUpdateModal && project.projectId && (
<UpdateModalProject projectId={project.projectId} onClose={closeUpdateModal} />
)}
</>
);
}
18 changes: 18 additions & 0 deletions src/components/project/ProjectItemList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import ProjectItem from '@components/project/ProjectItem';
import type { Team } from '@/types/TeamType';
import type { Project } from '@/types/ProjectType';

type ProjectItemListProps = {
teamId: Team['teamId'];
projectList: Project[];
};

export default function ProjectItemList({ teamId, projectList }: ProjectItemListProps) {
return (
<ul>
{projectList.map((project) => (
<ProjectItem key={project.projectId} teamId={teamId} project={project} />
))}
</ul>
);
}
4 changes: 2 additions & 2 deletions src/hooks/query/useTeamQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ export function useUpdateTeamCoworkerRole(teamId: Team['teamId']) {
// 팀원 목록 조회
export function useReadTeamCoworkers(teamId: Team['teamId']) {
const {
data: coworkers = [] as TeamCoworker[],
data: teamCoworkers = [] as TeamCoworker[],
isLoading,
isError,
} = useQuery({
Expand All @@ -263,5 +263,5 @@ export function useReadTeamCoworkers(teamId: Team['teamId']) {
},
});

return { coworkers, isLoading, isError };
return { teamCoworkers, isLoading, isError };
}
18 changes: 10 additions & 8 deletions src/layouts/page/TeamLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ import { useReadTeams } from '@hooks/query/useTeamQuery';
import Spinner from '@components/common/Spinner';

export default function TeamLayout() {
const { showModal: showTeamModal, openModal: openTeamModal, closeModal: closeTeamModal } = useModal();
const location = useLocation();
const { teamId } = useParams();
const { joinedTeamList: teamData, isLoading: isTeamLoading } = useReadTeams();
const selectedTeam = useMemo(() => teamData.find((team) => team.teamId.toString() === teamId), [teamId, teamData]);
const { showModal: showTeamModal, openModal: openTeamModal, closeModal: closeTeamModal } = useModal();
const { joinedTeamList, isLoading: isTeamLoading } = useReadTeams();

const selectedTeam = useMemo(
() => joinedTeamList.find((team) => team.teamId.toString() === teamId),
[teamId, joinedTeamList],
);
const hasProjectRoute = location.pathname.split('/').includes('projects');

if (isTeamLoading) {
return <Spinner />;
}
if (isTeamLoading) return <Spinner />;

if (!selectedTeam && teamId) return <Navigate to="/error" replace />;

Expand All @@ -27,10 +29,10 @@ export default function TeamLayout() {
<>
<section className="flex h-full gap-10 p-15">
<ListSidebar title="팀 목록" showButton text="팀 생성" onClick={openTeamModal}>
<ListTeam data={teamData} targetId={teamId} />
<ListTeam data={joinedTeamList} targetId={teamId} />
</ListSidebar>
<section className="flex grow flex-col border border-list bg-contents-box">
{teamData.length === 0 ? (
{joinedTeamList.length === 0 ? (
<div className="flex h-full items-center justify-center text-center">
소속된 팀이 없습니다! <br />
팀을 생성하여 다른 사람들과 함께 프로젝트를 관리해보세요 😄
Expand Down
112 changes: 18 additions & 94 deletions src/pages/team/TeamPage.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,40 @@
import { IoIosSettings } from 'react-icons/io';
import { FaRegTrashAlt } from 'react-icons/fa';
import { useParams, Link } from 'react-router-dom';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import CreateModalProject from '@components/modal/project/CreateModalProject';
import { useStore } from '@stores/useStore';
import useModal from '@hooks/useModal';
import UpdateModalProject from '@components/modal/project/UpdateModalProject';
import { useReadProjects, useDeleteProject, useReadProjectCoworkers } from '@hooks/query/useProjectQuery';
import Spinner from '@components/common/Spinner';
import { useReadTeamCoworkers, useReadTeams } from '@hooks/query/useTeamQuery';

import useToast from '@hooks/useToast';
import { useStore } from '@stores/useStore';
import type { Project } from '@/types/ProjectType';
import { useReadProjects } from '@hooks/query/useProjectQuery';
import { useReadTeamCoworkers, useReadTeams } from '@hooks/query/useTeamQuery';
import Spinner from '@components/common/Spinner';
import ProjectItemList from '@components/project/ProjectItemList';
import EmptyProjectItemList from '@components/project/EmptyProjectItemList';

export default function TeamPage() {
const { showModal: showProjectModal, openModal: openProjectModal, closeModal: closeProjectModal } = useModal();
const { showModal: showUpdateModal, openModal: openUpdateModal, closeModal: closeUpdateModal } = useModal();
const { teamId } = useParams();
const { userInfo } = useStore();
const { userId } = userInfo;
const { coworkers: teamCoworkers } = useReadTeamCoworkers(Number(teamId));
const { projectCoworkers } = useReadProjectCoworkers(Number(teamId));
const {
userInfo: { userId },
} = useStore();
const { toastWarn } = useToast();

const { projectList: teamProjects, isProjectLoading } = useReadProjects(Number(teamId));
const { projectList, isProjectLoading } = useReadProjects(Number(teamId));
const { joinedTeamList, isLoading: isTeamLoading } = useReadTeams();
const [selectedProjectId, setSelectedProjectId] = useState<Project['projectId'] | null>(null);
const { toastWarn, toastError } = useToast();

const { mutate: deleteProjectMutate } = useDeleteProject(Number(teamId));
const { teamCoworkers } = useReadTeamCoworkers(Number(teamId));

const userTeamRole = teamCoworkers.find((coworker) => coworker.userId === userId)?.roleName || null;
const userProjectRole = projectCoworkers.find((coworker) => coworker.userId === userId)?.roleName;
const team = joinedTeamList.find((team) => team.teamId.toString() === teamId);
const userTeamRole = teamCoworkers.find((coworker) => coworker.userId === userId)?.roleName || null;
const teamName = team ? team.teamName : '';

const handleOpenUpdateModal = (projectId: Project['projectId']) => {
if (userProjectRole !== 'ADMIN') {
return toastError('프로젝트 수정 권한이 없습니다.');
}

setSelectedProjectId(projectId);
openUpdateModal();
};

const handleCreateProjectClick = () => {
if (!teamId) return toastWarn('팀을 선택한 후 프로젝트 생성을 진행해주세요.');

if (userTeamRole !== 'HEAD' && userTeamRole !== 'LEADER') {
toastError('프로젝트 생성 권한이 없습니다.');
return;
return toastWarn('프로젝트 생성 권한이 없습니다.');
}

openProjectModal();
};

const handleDeleteClick = (e: React.MouseEvent, projectId: Project['projectId']) => {
e.preventDefault();
deleteProjectMutate(projectId);
};

if (isProjectLoading || isTeamLoading) {
return <Spinner />;
}
Expand All @@ -81,65 +57,13 @@ export default function TeamPage() {
</header>

<section className="h-full overflow-y-auto">
{/* ToDo: 컴포넌트 분리필요 */}
{teamProjects.length > 0 ? (
<ul>
{teamProjects.map((project) => (
<li key={project.projectId} className="min-w-300 space-y-2 text-sm">
<Link
to={`/teams/${teamId}/projects/${project.projectId}`}
className="flex h-50 items-center border p-8"
>
<div className="flex max-h-full grow">
<div className="max-h-full w-60 shrink-0">
<small className="flex flex-col text-xs font-bold text-category">project</small>
<p className="truncate">{project.projectName}</p>
</div>

<div className="flex max-h-full max-w-350 flex-col px-4">
<small className="text-xs font-bold text-category">desc</small>
<p className="truncate">{project.content}</p>
</div>
</div>

<div className="mr-6 flex shrink-0 space-x-10">
<button
className="flex items-center text-main hover:brightness-50"
aria-label="Settings"
type="button"
onClick={(e) => {
e.preventDefault();
handleOpenUpdateModal(project.projectId);
}}
>
<IoIosSettings size={20} className="mr-2" />
setting
</button>

<button
className="hover:brightness-200"
type="button"
aria-label="Delete"
onClick={(e) => handleDeleteClick(e, project.projectId)}
>
<FaRegTrashAlt size={20} />
</button>
</div>
</Link>
</li>
))}
</ul>
{projectList.length > 0 ? (
<ProjectItemList teamId={Number(teamId)} projectList={projectList} />
) : (
<div className="flex h-full items-center justify-center text-center">
진행중인 프로젝트가 없습니다! <br />
새로운 프로젝트를 생성해보세요 😄
</div>
<EmptyProjectItemList />
)}
</section>
{showProjectModal && <CreateModalProject onClose={closeProjectModal} />}
{showUpdateModal && selectedProjectId && (
<UpdateModalProject projectId={selectedProjectId} onClose={closeUpdateModal} />
)}
</section>
);
}

0 comments on commit e8da37e

Please sign in to comment.