Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE][Feature] Kudos 유형 칭찬 대상 지정 기능 구현 #355

Merged
merged 3 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/api/@types/Section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface sectionData {
thumbnail: string;
comments: CommentData[];
actionItems?: ActionItemData;
kudosTarget?: KudosTargetData;
}

export interface ActionItemData {
Expand All @@ -27,6 +28,12 @@ export interface ActionItemData {
thumbnail: string;
}

export interface KudosTargetData {
userId: number;
username: string;
thumbnail: string;
}

export interface CommentData {
commentId: number;
userId: number;
Expand Down
18 changes: 18 additions & 0 deletions src/api/@types/TeamController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ export interface TemplateNameData {
sequence: number;
}

// put kudos 칭찬 대상
export interface PutKudosTargetRequest {
sectionId: number;
userId: number;
}

export interface PutKudosTargetResponse {
code: number;
message: number;
data: KudosTargetData[];
}

export interface KudosTargetData {
kudosId: number;
sectionId: number;
userId: number;
}

// put 담당자
export interface PutActionItemsRequest {
teamId: number;
Expand Down
15 changes: 15 additions & 0 deletions src/api/teamControllerApi/putKudosTarget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { PutKudosTargetRequest, PutKudosTargetResponse } from '@/api/@types/TeamController';
import axiosInstance from '@/api/axiosConfig';

export const putKudosTarget = async ({
sectionId,
...request
}: PutKudosTargetRequest): Promise<PutKudosTargetResponse> => {
try {
const response = await axiosInstance.put<PutKudosTargetResponse>(`sections/${sectionId}/kudos-target`, request);
console.log('kudos 지정 성공', response.data);
return response.data;
} catch (error) {
throw new Error('kudos 칭찬 지정 실패');
}
};
48 changes: 25 additions & 23 deletions src/components/writeRetro/ActionItems/ActionItemTask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const ActionItemTask: FC<ActionItemTaskProps> = ({ tId, rId, sId, section, fetch
}

if ((ActionItems && selectedUserImg === null) || selectedUserImg === '') {
return <CgProfile size="24px" color="#969696" />;
return <CgProfile size="24px" color="#ADB8CC" />;
}
};

Expand All @@ -103,28 +103,30 @@ const ActionItemTask: FC<ActionItemTaskProps> = ({ tId, rId, sId, section, fetch

return (
<>
<Popover isOpen={showPopup} onClose={() => setShowPopup(false)}>
<PopoverTrigger>
<S.ManagerButton onClick={togglePopup}>{renderImage()}</S.ManagerButton>
</PopoverTrigger>
{section.actionItems ? (
<S.ManagerText> {selectedUserName}</S.ManagerText>
) : (
<S.ManagerText>담당자</S.ManagerText>
)}
<PopoverContent border={'none'}>
<Members
users={users}
onSelectUserImg={handleSelectUserImg}
onSelectUserName={handleSelectUserName}
tId={teamId}
rId={retrospectiveId}
sId={sectionId}
imageURL={imageURL}
fetchSection={fetchSection}
/>
</PopoverContent>
</Popover>
<S.ActionItemsUserContainer>
<Popover isOpen={showPopup} onClose={() => setShowPopup(false)}>
{section.actionItems ? (
<S.ManagerText> {selectedUserName}</S.ManagerText>
) : (
<S.ManagerText>닉네임</S.ManagerText>
)}
<PopoverTrigger>
<S.ManagerButton onClick={togglePopup}>{renderImage()}</S.ManagerButton>
</PopoverTrigger>
<PopoverContent border={'none'}>
<Members
users={users}
onSelectUserImg={handleSelectUserImg}
onSelectUserName={handleSelectUserName}
tId={teamId}
rId={retrospectiveId}
sId={sectionId}
imageURL={imageURL}
fetchSection={fetchSection}
/>
</PopoverContent>
</Popover>
</S.ActionItemsUserContainer>
</>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/writeRetro/ActionItems/Members.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const Members: React.FC<UserListProps> = ({
{user.imageURL !== '' ? (
<img src={user.imageURL} style={{ width: '25px', height: '25px' }} />
) : (
<CgProfile size="25px" color="#969696" />
<CgProfile size="25px" color="#ADB8CC" />
)}
</S.ProfileImage>
<div style={{ alignItems: 'center' }}>
Expand Down
130 changes: 130 additions & 0 deletions src/components/writeRetro/KudosTarget/KudosTargetButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { FC, useState, useEffect } from 'react';
import { CgProfile } from 'react-icons/cg';
import { Popover, PopoverTrigger, PopoverContent } from '@chakra-ui/react';
import { sectionData, KudosTargetData } from '@/api/@types/Section';
import postImageToS3 from '@/api/imageApi/postImageToS3';
import { TeamControllerServices } from '@/api/services/TeamController';
import Members from '@/components/writeRetro/KudosTarget/Members';
import { useCustomToast } from '@/hooks/useCustomToast';
import * as S from '@/styles/writeRetroStyles/Layout.style';

interface KudosTargetTaskProps {
section: sectionData;
tId: number;
rId: number;
sId: number;
fetchSection: () => void;
}

const KudosTargetButton: FC<KudosTargetTaskProps> = ({ tId, rId, sId, section, fetchSection }) => {
const KudosTarget: KudosTargetData | undefined = section.kudosTarget;
const [showPopup, setShowPopup] = useState<boolean>(false);
const [selectedUserName, setSelectedUserName] = useState<string>(KudosTarget?.username || '');
const [selectedUserImg, setSelectedUserImg] = useState<string | null>(null);

const [users, setUsers] = useState<{ name: string; image: string; userId: number }[]>([]);
const [imageURL, setImageURL] = useState<{ url: string }[]>([]);

const teamId: number = tId;
const retrospectiveId: number = rId;
const sectionId: number = sId;
const toast = useCustomToast();

const fetchTeamMember = async () => {
try {
if (teamId) {
const data = await TeamControllerServices.TeamMemberGet({
teamId: teamId,
retrospectiveId: retrospectiveId,
});
const userData = data.data.map(member => ({
name: member.username,
image: member.profileImage,
userId: member.userId,
}));
setUsers(userData);
const image = data.data.map(member => ({
url: member.profileImage,
}));
setImageURL(image);
}
return;
} catch (e) {
toast.error('멤버 조회 실패');
}
};
useEffect(() => {
fetchTeamMember();
}, [section.kudosTarget]);

const fetchRetrospectiveImage = async () => {
if (section.kudosTarget && section.kudosTarget.thumbnail) {
try {
const data = await postImageToS3({ filename: section.kudosTarget.thumbnail, method: 'GET' });
setSelectedUserImg(data.data.preSignedUrl);
} catch (e) {
toast.error('담당자 이미지 가져오기 실패');
}
} else {
setSelectedUserImg(null);
}
};

const togglePopup = () => {
setShowPopup(!showPopup);
};

const handleSelectUserImg = (image: string) => {
setSelectedUserImg(image);
setShowPopup(false);
};

const handleSelectUserName = (name: string) => {
setSelectedUserName(name);
};

const renderImage = () => {
if (!KudosTarget) {
return 'M';
}

if (KudosTarget && selectedUserImg) {
return <img src={selectedUserImg} style={{ width: '30px' }} />;
}

if ((KudosTarget && selectedUserImg === null) || selectedUserImg === '') {
return <CgProfile size="24px" color="#ADB8CC" />;
}
};

useEffect(() => {
fetchRetrospectiveImage();
}, []);

return (
<S.ActionItemsUserContainer>
<Popover isOpen={showPopup} onClose={() => setShowPopup(false)}>
{section.kudosTarget ? (
<S.ManagerText> {selectedUserName}</S.ManagerText>
) : (
<S.ManagerText>닉네임</S.ManagerText>
)}
<PopoverTrigger>
<S.ManagerButton onClick={togglePopup}>{renderImage()}</S.ManagerButton>
</PopoverTrigger>
<PopoverContent border={'none'}>
<Members
users={users}
onSelectUserImg={handleSelectUserImg}
onSelectUserName={handleSelectUserName}
sId={sectionId}
imageURL={imageURL}
fetchSection={fetchSection}
/>
</PopoverContent>
</Popover>
</S.ActionItemsUserContainer>
);
};

export default KudosTargetButton;
102 changes: 102 additions & 0 deletions src/components/writeRetro/KudosTarget/Members.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { useEffect, useState } from 'react';
import { CgProfile } from 'react-icons/cg';
import postImageToS3 from '@/api/imageApi/postImageToS3';
import { putKudosTarget } from '@/api/teamControllerApi/putKudosTarget';
import { useCustomToast } from '@/hooks/useCustomToast';
import * as S from '@/styles/writeRetroStyles/Members.styles';

interface UserListProps {
users: { name: string; image: string; userId: number }[];
sId: number;
onSelectUserImg: (image: string) => void;
onSelectUserName: (name: string) => void;
imageURL: { url: string }[];
fetchSection: () => void;
}

export const Members: React.FC<UserListProps> = ({
users,
sId,
onSelectUserImg,
onSelectUserName,
imageURL,
fetchSection,
}) => {
const toast = useCustomToast();
const sectionId: number = sId;
const [rendering, setRendering] = useState<boolean>(false);

const putTargetMember = async (selectedUserId: number) => {
try {
await putKudosTarget({ sectionId: sectionId, userId: selectedUserId });
setRendering(prev => !prev);
} catch (e) {
toast.error('kudos 실패');
}
};

const [images, setImages] = useState<string[]>([]);

const fetchRetrospectiveImage = async () => {
try {
const newImages = [];
for (const urlData of imageURL) {
const url = urlData.url;
if (!url || url.trim() === '') {
newImages.push('');
continue;
}
const data = await postImageToS3({ filename: url.trim(), method: 'GET' });
newImages.push(data.data.preSignedUrl);
}
setImages(newImages);
} catch (e) {
console.error(e);
}
};

const handleUserClick = async (name: string, userId: number, userIndex: number) => {
onSelectUserName(name);
const selectedUserImage = usersWithImages[userIndex].imageURL;
const userImage = selectedUserImage !== '' ? selectedUserImage : '';
onSelectUserImg(userImage);
await putTargetMember(userId);
fetchSection();
};

useEffect(() => {
fetchRetrospectiveImage();
}, [imageURL, rendering]);

const usersWithImages = users.map((user, index) => ({
...user,
imageURL: user.image ? images[index] : '',
}));

return (
<>
<S.ListContainer>
<S.TitleContainer>
<S.Title>해당 업무의 담당자를 지정해주세요</S.Title>
</S.TitleContainer>
<ul>
{usersWithImages.map((user, index) => (
<S.ListItem key={index} onClick={() => handleUserClick(user.name, user.userId, index)}>
<S.ProfileImage>
{user.imageURL !== '' ? (
<img src={user.imageURL} style={{ width: '25px', height: '25px' }} />
) : (
<CgProfile size="25px" color="#ADB8CC" />
)}
</S.ProfileImage>
<div style={{ alignItems: 'center' }}>
<S.UserName>{user.name}</S.UserName>
</div>
</S.ListItem>
))}
</ul>
</S.ListContainer>
</>
);
};
export default Members;
Loading
Loading