Skip to content

Commit

Permalink
Merge branch 'feature/#175-schedulingFeed' of https://github.com/98OO…
Browse files Browse the repository at this point in the history
…/colla-frontend into feature/#175-schedulingFeed
  • Loading branch information
ledraco committed Dec 5, 2024
2 parents d98927f + f4c336c commit 9d0f55f
Show file tree
Hide file tree
Showing 21 changed files with 594 additions and 119 deletions.
17 changes: 17 additions & 0 deletions src/apis/Feed/Collect/getCollectSubTask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { axiosInstance } from '@apis/axiosInstance';
import { END_POINTS } from '@constants/api';
import type { SubTaskResponse } from '@type/feed';

const getCollectSubTask = async (
teamspaceId: number,
feedId: number,
userId: number
): Promise<SubTaskResponse> => {
const response = await axiosInstance.get(
`${END_POINTS.GET_COLLECT_SUB_TASK(teamspaceId, feedId, userId)}`
);

return response.data.content;
};

export default getCollectSubTask;
28 changes: 28 additions & 0 deletions src/apis/Feed/Collect/patchCollectSubTask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { axiosInstance } from '@apis/axiosInstance';
import { END_POINTS } from '@constants/api';

export interface CollectSubTaskParams {
teamspaceId: number | undefined;
feedId: number;
title: string | null;
content: string | null;
}

const patchCollectSubTask = async ({
teamspaceId,
feedId,
title,
content,
}: CollectSubTaskParams) => {
const response = await axiosInstance.patch(
`${END_POINTS.PATCH_COLLECT_SUB_TASK(teamspaceId!, feedId)}`,
{
title,
content,
}
);

return response.data;
};

export default patchCollectSubTask;
25 changes: 25 additions & 0 deletions src/apis/post/postCollectFeed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { axiosInstance } from '@apis/axiosInstance';
import { END_POINTS } from '@constants/api';
import type { CollectFeedForm } from '@type/feed';

const postCollectFeed = async ({
teamspaceId,
title,
images,
attachments,
details,
}: CollectFeedForm) => {
const response = await axiosInstance.post(
`${END_POINTS.POST_COLLECT_FEED(teamspaceId)}`,
{
title,
images,
attachments,
details,
}
);

return response.data;
};

export default postCollectFeed;
4 changes: 2 additions & 2 deletions src/components/Feed/Detail/Collect/CollectDetail.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export const FeedContainer = styled.div`
gap: ${theme.units.spacing.space24};
height: calc(100% - 64px);
overflow: auto;
padding: ${theme.units.spacing.space12} ${theme.units.spacing.space32};
padding-bottom: 0;
padding: 0 ${theme.units.spacing.space32};
padding-bottom: ${theme.units.spacing.space24};
overflow-x: hidden;
&::-webkit-scrollbar {
Expand Down
163 changes: 86 additions & 77 deletions src/components/Feed/Detail/Collect/CollectDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Text from '@components/common/Text/Text';
import SubTask from '@components/Feed/CollectFeed/SubTask/SubTask';
import CommentInput from '@components/Feed/CommentInput/CommentInput';
import Comment from '@components/Feed/Comments/Comment';
import SubTaskDetail from '@components/Feed/Detail/SubTask/SubTaskDetail';
import FeedAuthor from '@components/Feed/FeedAuthors/FeedAuthor';
import ProgressChip from '@components/Feed/ProgressChip/ProgressChip';
import useUserStatusQuery from '@hooks/queries/useUserStatusQuery';
Expand All @@ -34,93 +35,101 @@ const CollectDetail = ({ feedData }: FeedProps) => {
onClick={() => setSelectSubTask(null)}
/>
{selectSubTask && (
<Flex align='center' gap='8'>
<Flex align='center' gap='10'>
<Icon name='ChevronRight' />
<Text size='md' weight='semiBold' color='iSecondary'>
{selectSubTask.username}
</Text>
</Flex>
)}
</Flex>
<FeedAuthor
profile={author.profileImageUrl}
initial={author.username.charAt(0)}
title={author.username}
createdAt={getFormattedDate(createdAt, 'detail')}
tag={author?.tag?.name || ''}
/>
<Flex direction='column' gap='12'>
<Heading size='xs'>{title}</Heading>
<Divider size='sm' />
<Flex direction='column' gap='16' marginBottom='20'>
<Flex align='center' gap='14'>
<Icon name='Clock' />
<Flex gap='8'>
<ProgressChip type='PENDING' status={!details.isClosed} />
<ProgressChip type='COMPLETED' status={details.isClosed} />
{selectSubTask ? (
<SubTaskDetail subTaskAuthor={selectSubTask} feedId={feedId} />
) : (
<>
<FeedAuthor
profile={author.profileImageUrl}
initial={author.username.charAt(0)}
title={author.username}
createdAt={getFormattedDate(createdAt, 'detail')}
tag={author?.tag?.name || ''}
/>
<Flex direction='column' gap='12'>
<Heading size='xs'>{title}</Heading>
<Divider size='sm' />
<Flex direction='column' gap='16' marginBottom='20'>
<Flex align='center' gap='14'>
<Icon name='Clock' />
<Flex gap='8'>
<ProgressChip type='PENDING' status={!details.isClosed} />
<ProgressChip type='COMPLETED' status={details.isClosed} />
</Flex>
</Flex>
<Flex align='center' gap='14'>
<Icon name='Calendar' />
<Text size='md' weight='regular'>
{getFormattedDate(createdAt, 'detail')}
</Text>
</Flex>
<Flex align='center' gap='14'>
<Icon name='Calendar' />
{details.dueAt && (
<Text size='md' weight='regular'>
{`${getFormattedDate(details.dueAt, 'detail')} 까지`}
</Text>
)}
</Flex>
<S.DetailWrapper>
<div
dangerouslySetInnerHTML={{ __html: details.content || '' }}
/>
</S.DetailWrapper>
</Flex>
<Flex direction='column' gap='12'>
<Flex align='center' gap='6'>
<Text size='lg' weight='semiBold'>
하위 업무
</Text>
<Text size='lg' weight='semiBold' color='tertiary'>
{details.responses.length.toString()}
</Text>
</Flex>
<Flex direction='column'>
{details.responses.map((task) => (
<SubTask
subTaskData={task}
onClick={() => setSelectSubTask(task.author)}
/>
))}
</Flex>
</Flex>
</Flex>
<Flex align='center' gap='14'>
<Icon name='Calendar' />
<Text size='md' weight='regular'>
{getFormattedDate(createdAt, 'detail')}
</Text>
</Flex>
<Flex align='center' gap='14'>
<Icon name='Calendar' />
{details.dueAt && (
<Text size='md' weight='regular'>
{`${getFormattedDate(details.dueAt, 'detail')} 까지`}
</Text>
)}
</Flex>
<S.DetailWrapper>
<div dangerouslySetInnerHTML={{ __html: details.content || '' }} />
</S.DetailWrapper>
</Flex>
<Flex direction='column' gap='12'>
<Flex align='center' gap='6'>
<Text size='lg' weight='semiBold'>
하위업무
</Text>
<Text size='lg' weight='semiBold' color='tertiary'>
{details.responses.length.toString()}
</Text>
</Flex>
<Flex direction='column'>
{details.responses.map((task) => (
<SubTask
subTaskData={task}
onClick={() => setSelectSubTask(task.author)}
/>
))}
</Flex>
</Flex>
</Flex>
{comments.length !== 0 && (
<S.SectionContainer>
<Flex direction='column' align='flex-start'>
<Text
size='md'
weight='medium'
color='tertiary'>{`댓글 ${comments.length}개`}</Text>
</Flex>
<Divider size='sm' />
{comments.map((comment) => {
return (
<Flex direction='column' gap='8'>
<Comment comment={comment} />
<Divider size='sm' />
{comments.length !== 0 && (
<S.SectionContainer>
<Flex direction='column' align='flex-start'>
<Text
size='md'
weight='medium'
color='tertiary'>{`댓글 ${comments.length}개`}</Text>
</Flex>
);
})}
</S.SectionContainer>
)}
{userStatus && (
<CommentInput
teamspaceId={userStatus.profile.lastSeenTeamspaceId}
feedId={feedId}
/>
<Divider size='sm' />
{comments.map((comment) => {
return (
<Flex direction='column' gap='8'>
<Comment comment={comment} />
<Divider size='sm' />
</Flex>
);
})}
</S.SectionContainer>
)}
{userStatus && (
<CommentInput
teamspaceId={userStatus.profile.lastSeenTeamspaceId}
feedId={feedId}
/>
)}
</>
)}
</S.FeedContainer>
);
Expand Down
24 changes: 24 additions & 0 deletions src/components/Feed/Detail/SubTask/SubTaskDetail.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { styled } from 'styled-components';
import { editorStyles } from '@styles/editorStyles';
import theme from '@styles/theme';

export const SubTaskPostContainer = styled.form`
display: flex;
flex-direction: column;
width: 680px;
gap: ${theme.units.spacing.space32};
`;

export const PostInput = styled.input`
font-size: ${theme.typography.fontSize.header.sm};
font-weight: ${theme.typography.fontWeight.semiBold};
border: none;
outline: none;
`;

export const DetailWrapper = styled.div`
${editorStyles}
margin-bottom: ${theme.units.spacing.space48};
width: 732px;
min-height: 150px;
`;
103 changes: 103 additions & 0 deletions src/components/Feed/Detail/SubTask/SubTaskDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { useEffect, useState } from 'react';
import { Button } from '@components/common/Button/Button';
import Divider from '@components/common/Divider/Divider';
import Flex from '@components/common/Flex/Flex';
import Heading from '@components/common/Heading/Heading';
import FeedAuthor from '@components/Feed/FeedAuthors/FeedAuthor';
import Editor from '@components/Post/Editor/Editor';
import usePostEditor from '@hooks/post/usePostEditor';
import useCollectSubTaskMutation from '@hooks/queries/Feed/Collect/useCollectSubTaskMutation';
import useCollectSubTaskQuery from '@hooks/queries/Feed/Collect/useCollectSubTaskQuery';
import useUserStatusQuery from '@hooks/queries/useUserStatusQuery';
import { getFormattedDate } from '@utils/getFormattedDate';
import type { Author } from '@type/feed';
import * as S from './SubTaskDetail.styled';

interface SubTaskPostProps {
subTaskAuthor: Author;
feedId: number;
}

const SubTaskDetail = ({ subTaskAuthor, feedId }: SubTaskPostProps) => {
const { id } = subTaskAuthor;
const { editorRef, appendImageFile } = usePostEditor();
const { userStatus } = useUserStatusQuery();
const { subTask } = useCollectSubTaskQuery(
userStatus?.profile.lastSeenTeamspaceId,
feedId,
id
);
const [title, setTitle] = useState<string | null>(null);
const { mutateCollectSubTask } = useCollectSubTaskMutation(
userStatus?.profile.lastSeenTeamspaceId,
feedId
);

useEffect(() => {
if (subTask) setTitle(subTask.title);
}, [subTask]);

const handleTitleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setTitle(event.target.value);
};

const handleSubmitSubTask = async () => {
if (editorRef.current && editorRef.current.getHTML() !== undefined) {
const content = editorRef.current.getHTML();

await mutateCollectSubTask({
teamspaceId: userStatus?.profile.lastSeenTeamspaceId,
feedId,
title,
content: content === '<p></p>' ? null : content,
});
}
};

return (
<Flex>
{id === userStatus?.profile.userId ? (
<S.SubTaskPostContainer>
<S.PostInput
placeholder='제목을 입력해주세요'
value={title || ''}
onChange={handleTitleChange}
maxLength={50}
/>
<Editor editorRef={editorRef} appendImageFile={appendImageFile} />
<Flex justify='flex-end'>
<Button
label='수정'
variant='primary'
size='md'
onClick={handleSubmitSubTask}
/>
</Flex>
</S.SubTaskPostContainer>
) : (
subTask && (
<Flex direction='column' gap='24'>
<FeedAuthor
profile={subTask.author.profileImageUrl}
initial={subTask.author.username.charAt(0)}
title={subTask.author.username}
createdAt={getFormattedDate(subTask.updatedAt, 'detail')}
tag={subTask.author.tag?.name || ''}
/>
<Flex direction='column' gap='12'>
{subTask.title && <Heading size='xs'>{subTask.title}</Heading>}
<Divider size='sm' />
<S.DetailWrapper>
<div
dangerouslySetInnerHTML={{ __html: subTask.content || '' }}
/>
</S.DetailWrapper>
</Flex>
</Flex>
)
)}
</Flex>
);
};

export default SubTaskDetail;
Loading

0 comments on commit 9d0f55f

Please sign in to comment.