Skip to content
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
3 changes: 3 additions & 0 deletions src/assets/icons/gray-lg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions src/assets/icons/status/doing.svg

This file was deleted.

3 changes: 0 additions & 3 deletions src/assets/icons/status/done.svg

This file was deleted.

3 changes: 0 additions & 3 deletions src/assets/icons/status/none.svg

This file was deleted.

3 changes: 0 additions & 3 deletions src/assets/icons/status/review.svg

This file was deleted.

3 changes: 0 additions & 3 deletions src/assets/icons/status/todo.svg

This file was deleted.

29 changes: 29 additions & 0 deletions src/components/DetailView/Comment/CommentCompletionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// CommentCompletionButton.tsx
// 상세페이지 댓글창) 댓글 입력 확인 버튼 컴포넌트

interface CommentCompletionButtonProps {
isCommentFilled: boolean; // 댓글이 입력되었는지를 따지는 플래그
onClick?: () => void;
}

const CommentCompletionButton = ({ isCommentFilled, onClick }: CommentCompletionButtonProps) => {
// 댓글이 입력되지 않으면: 버튼 비활성화
const isDisabled = !isCommentFilled;

// 버튼 비활성화 상태일 때 or 활성화 상태일 때의 스타일링 구분
const cursorClass = isDisabled ? '' : 'cursor-pointer';
const bgColor = isDisabled ? 'bg-gray-200' : 'bg-primary-blue';
const textColor = isDisabled ? 'text-gray-400' : 'text-gray-100';

return (
<button
disabled={isDisabled}
onClick={onClick}
className={`flex items-center justify-center px-[1.6rem] py-[0.8rem] font-small-b whitespace-nowrap rounded-md ${cursorClass} ${bgColor} ${textColor} transition-colors duration-300 ease-in-out`}
>
확인
</button>
);
};

export default CommentCompletionButton;
45 changes: 45 additions & 0 deletions src/components/DetailView/Comment/CommentInput.tsx

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

댓글 최대 글자수는 없는지 궁금합니다!!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

화면설계서 상에는 댓글 최대 글자 제한은 없었는데, 한번 여쭤보겠습니다~~!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

댓글 최대 글자수는 없는 것으로 정해졌습니다

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// CommentInput.tsx
// 상세페이지 댓글창) 댓글 입력창 컴포넌트

import { useState } from 'react';
import CommentCompletionButton from './CommentCompletionButton';

interface CommentIntputProps {
onAdd: (content: string) => void;
}

const CommentInput = ({ onAdd }: CommentIntputProps) => {
const [comment, setComment] = useState('');

// 입력 시
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setComment(e.target.value);
};

const handleSubmit = () => {
if (comment.trim().length === 0) return;
onAdd(comment.trim());
setComment('');
};

return (
<div
className="flex flex-end items-end gap-[0.8rem] w-full h-[13rem] p-[2.4rem] bg-gray-100 rounded-lg"
style={{
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.10), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
}}
>
{/* 댓글 입력창 영역 */}
<textarea
value={comment}
onChange={handleChange}
placeholder="댓글을 작성하세요."
className="w-full h-full font-body-r placeholder-gray-400 text-gray-600 focus:outline-none resize-none basic-scroll"
></textarea>
{/* 댓글 작성 완료 버튼 */}
<CommentCompletionButton isCommentFilled={comment.trim().length > 0} onClick={handleSubmit} />
</div>
);
};

export default CommentInput;
34 changes: 34 additions & 0 deletions src/components/DetailView/Comment/CommentItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// CommentItem.tsx
// 상세페이지 댓글창) 개별 댓글 컴포넌트

import IcDummyProfile from '../../../assets/icons/gray-lg.svg';

interface CommentItemProps {
author: string; // 댓글 작성자
content: string; // 댓글 내용
createAt: Date; // 댓글 작성 날짜
}

const CommentItem = ({ author, content, createAt }: CommentItemProps) => {
return (
<div className="flex items-start gap-[1.6rem] w-full">
{/* 댓글 작성자 프로필: 현재는 더미이미지 삽입. 추후 데이터 받아와 연동 예정 */}
<img src={IcDummyProfile} alt="작성자 프로필" />

<div className="flex flex-col gap-[0.8rem]">
{/* 작성자 정보 */}
<div className="flex gap-[0.8rem]">
{/* 작성자 이름 */}
<span className="font-body-b text-gray-600">{author}</span>
{/* 작성 시간 */}
<span className="font-small-r text-gray-500">{createAt.toLocaleString()}</span>
</div>

{/* 댓글 내용 */}
<div className="font-body-r text-gray-600">{content}</div>
</div>
</div>
);
};

export default CommentItem;
57 changes: 57 additions & 0 deletions src/components/DetailView/Comment/CommentSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// CommentSection.tsx
// 상세페이지 댓글창) 댓글 전체 영역 컴포넌트

import { useState } from 'react';
import CommentInput from './CommentInput';
import CommentItem from './CommentItem';

interface Comment {
author: string; // 댓글 작성자
content: string; // 댓글 내용
createAt: Date; // 댓글 작성 날짜
}

const CommentSection = () => {
const [comments, setComments] = useState<Comment[]>([]); // comments라는 상태를 배열로 관리

const handleAddComment = (content: string) => {
const newComment: Comment = {
author: '전채운', // 추후 로그인 유저로 대체
content,
createAt: new Date(),
};
setComments((prev) => [...prev, newComment]);
};

return (
<div className="flex flex-col gap-[3.2rem] w-full h-[41rem]">
{/* 댓글 목록 헤더 */}
<div className="flex gap-[0.8rem]">
<div className="font-title-sub-r text-gray-600">댓글</div>
{/* 댓글 개수를 숫자로 렌더링 */}
<div className="font-title-sub-r text-gray-500">{comments.length}</div>
</div>

<div className="flex flex-col gap-[1.6rem] w-full h-[35rem]">
{/* 댓글 목록 */}
<div className="flex flex-col gap-[2.4rem] w-full h-full overflow-y-auto basic-scroll">
{comments.length === 0 ? (
<div className="flex items-center justify-center w-full h-full">
{/* 댓글 개수가 0개일 때: 댓글 목록의 초기 문구를 띄우도록 */}
<div className="font-body-r text-gray-600">댓글을 작성하세요.</div>
</div>
) : (
comments.map(({ author, content, createAt }) => (
<CommentItem author={author} content={content} createAt={createAt} />
))
)}
</div>

{/* 댓글 입력창 */}
<CommentInput onAdd={handleAddComment} />
</div>
</div>
);
};

export default CommentSection;
6 changes: 3 additions & 3 deletions src/components/DetailView/CompletionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ const CompletionButton = ({ isTitleFilled, isCompleted, onToggle }: CompletionBu
// 작성 완료하지 않았을 때: 배경&텍스트 색상 비활성화 상태
// 작성 완료 시: 활성화 상태
const bgColor = isDisabled ? 'bg-gray-200' : 'bg-primary-blue';
const textColor = isDisabled ? 'text-gray-400' : 'text-white';
const textColor = isDisabled ? 'text-gray-400' : 'text-gray-100';

return (
<div className="flex items-end justify-end mb-[1.6rem]">
<div className="flex items-end justify-end">
<button
onClick={isDisabled ? undefined : onToggle}
disabled={isDisabled}
className={`flex items-center justify-center gap-[0.8rem] h-[3.6rem] py-[0.8rem] pl-[1.2rem] pr-[1.6rem] bg-gray-200 rounded-md ${cursorClass} ${bgColor} transition-colors duration-300 ease-in-out`}
className={`flex items-center justify-center gap-[0.8rem] h-[3.6rem] py-[0.8rem] pl-[1.2rem] pr-[1.6rem] rounded-md ${cursorClass} ${bgColor} transition-colors duration-300 ease-in-out`}
>
{/* 버튼 아이콘 */}
<img
Expand Down
2 changes: 1 addition & 1 deletion src/components/DetailView/DetailTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const DetailTitle = ({ defaultTitle, title, setTitle, isEditable }: DetailTitleP
disabled={!isEditable}
// 각 페이지별 placeholder를 서로 다르게 할 수 있도록 defaultTitle로 처리
placeholder={defaultTitle}
className="w-full font-bigtitle-b placeholder-gray-400 text-gray-600 focus:outline-none resize-none overflow-hidden break-keep ${!isEditable ? '' : 'cursor-not-allowed'}"
className="w-full font-bigtitle-b placeholder-gray-400 text-gray-600 focus:outline-none resize-none overflow-hidden"
></textarea>
);
};
Expand Down
5 changes: 2 additions & 3 deletions src/components/DetailView/PropertyItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
* 상세페이지 내 속성 항목 컴포넌트
*
* @todo
* - onClick에 이벤트 전파 관련 처리 코드 반영
* - 관련된 드롭다운 컴포넌트 수정: 일부 구성 등
* - 화면 가로 길이 줄여도 줄어들지 않게 처리
*
* @description
* - 속성 드롭다운은 작성 완료 여부에 영향을 받지 않도록 구현.
* - 속성 항목 수정은 상세페이지 내의 작성 완료 버튼 클릭 여부에 영향을 받지 않도록 구현.
* (즉, 속성은 언제나 수정 가능함)
* - 기한 드롭다운은 이 컴포넌트로 관리하지 않음 (별도로 필요한 상세페이지에서 구현)
*/

Expand Down
22 changes: 15 additions & 7 deletions src/pages/goal/GoalDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import pr1 from '../../assets/icons/pr-1-sm.svg';
import pr2 from '../../assets/icons/pr-2-sm.svg';
import pr3 from '../../assets/icons/pr-3-sm.svg';
import pr4 from '../../assets/icons/pr-4-sm.svg';
import IcDummyProfile from '../../assets/icons/user-circle-sm.svg';
import IcProfile from '../../assets/icons/user-circle-sm.svg';
// import IcDate from '../../assets/icons/date-lg.svg';
// import IcIssue from '../../assets/icons/issue.svg';

import { getStatusColor } from '../../utils/listItemUtils';
import { statusLabelToCode } from '../../types/detailitem';
import CommentSection from '../../components/DetailView/Comment/CommentSection';

const GoalDetail = () => {
const [title, setTitle] = useState('');
Expand All @@ -36,10 +37,10 @@ const GoalDetail = () => {

// '담당자' 속성 아이콘 매핑 (나중에 API로부터 받아온 데이터로 대체 예정)
const userIconMap = {
담당자: IcDummyProfile,
없음: IcDummyProfile,
전채운: IcDummyProfile,
전시현: IcDummyProfile,
담당자: IcProfile,
없음: IcProfile,
전채운: IcProfile,
전시현: IcProfile,
};

// const dateIconMap = IcDate; // '기한' 속성 아이콘 매핑
Expand All @@ -50,13 +51,13 @@ const GoalDetail = () => {
};

return (
<div className="flex flex-1 flex-col gap-[5.7rem] w-full p-[3.2rem]">
<div className="flex flex-1 flex-col gap-[5.7rem] w-full px-[3.2rem] pt-[3.2rem] pb-[5.3rem]">
{/* 상세페이지 헤더 */}
<DetailHeader defaultTitle="목표를 생성하세요" title={title} />

{/* 상세페이지 메인 */}
<div className="flex px-[3.2rem] gap-[8.8rem] w-full h-full">
{/* 상세페이지 좌측 영역 - 제목 & 상세 설명 & 댓글 영역 */}
{/* 상세페이지 좌측 영역 - 제목 & 상세설명 & 댓글 */}
<div className="flex flex-col gap-[3.2rem] w-[calc(100%-33rem)]">
{/* 상세페이지 제목 */}
<DetailTitle
Expand All @@ -68,6 +69,9 @@ const GoalDetail = () => {

{/* 상세 설명 작성 컴포넌트 */}
<DetailTextEditor isEditable={!isCompleted} />

{/* 댓글 영역 */}
{isCompleted && <CommentSection />}
</div>

{/* 상세페이지 우측 영역 - 속성 탭 & 하단의 작성 완료 버튼 */}
Expand Down Expand Up @@ -110,6 +114,10 @@ const GoalDetail = () => {
{/* <PropertyItem defaultValue="기한" iconMap={{ 기한: dateIconMap }} /> */}

{/* (5) 이슈 */}
{/**
* @todo
* - 이슈 많아질 경우 '외 n개'로 축약되게 하기
*/}
{/*
<PropertyItem
defaultValue="이슈"
Expand Down