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
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const BookSearchStates = ({ isLoading, error, isEmpty, onClose }: BookSearchStat
if (isEmpty) {
return (
<EmptyContainer>
<EmptyText>현재 등록된 책이 아닙니다.</EmptyText>
<EmptyText>현재 등록된 책이 없습니다.</EmptyText>
<EmptyText>원하시는 책을 신청해주세요.</EmptyText>
<ApplyButton onClick={handleApplyBook}>책 신청하기</ApplyButton>
</EmptyContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ interface ActivityPeriodSectionProps {
endDate: { year: number; month: number; day: number };
onStartDateChange: (date: { year: number; month: number; day: number }) => void;
onEndDateChange: (date: { year: number; month: number; day: number }) => void;
onValidationChange?: (isValid: boolean) => void;
}

const ActivityPeriodSection = ({
startDate,
endDate,
onStartDateChange,
onEndDateChange,
onValidationChange,
}: ActivityPeriodSectionProps) => {
// 현재 년도와 다음 년도
const currentYear = new Date().getFullYear();
Expand Down Expand Up @@ -63,6 +65,16 @@ const ActivityPeriodSection = ({
return endDateObj < startDateObj;
}, [startDate, endDate]);

// 날짜 유효성 검사 결과
const isDateValid = !isOverMaxDays && !isEndDateBeforeStart;

// 유효성 변경 시 부모에게 알림
useEffect(() => {
if (onValidationChange) {
onValidationChange(isDateValid);
}
}, [isDateValid, onValidationChange]);

// 오늘 날짜로 초기값 설정
const getInitialDate = () => {
const today = new Date();
Expand Down
5 changes: 3 additions & 2 deletions src/components/createpost/PostContentSection.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const TextAreaBox = styled.div`
export const TextArea = styled.textarea<{ readOnly?: boolean }>`
width: 100%;
min-height: 100px;
background-color: ${props => props.readOnly ? '#f5f5f5' : semanticColors.background.primary};
background-color: ${props => (props.readOnly ? '#f5f5f5' : semanticColors.background.primary)};
color: ${semanticColors.text.secondary};
font-size: ${typography.fontSize.sm};
font-weight: ${typography.fontWeight.regular};
Expand All @@ -19,7 +19,8 @@ export const TextArea = styled.textarea<{ readOnly?: boolean }>`
outline: none;
border: none;
overflow: hidden;
cursor: ${props => props.readOnly ? 'not-allowed' : 'text'};
cursor: ${props => (props.readOnly ? 'not-allowed' : 'text')};
padding: 0;

&::placeholder {
color: ${semanticColors.text.ghost};
Expand Down
10 changes: 10 additions & 0 deletions src/components/group/GroupBookSection.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@ export const BookTitle = styled.h3`
font-weight: ${typography.fontWeight.semibold};
margin: 0;
margin-right: 12px;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;

export const BookAuthor = styled.span`
color: ${semanticColors.text.secondary};
font-size: ${typography.fontSize.xs};
font-weight: ${typography.fontWeight.regular};
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;

export const ChevronIcon = styled.img`
Expand All @@ -37,4 +45,6 @@ export const RightSection = styled.div`
display: flex;
align-items: center;
margin-right: -8px;
flex-shrink: 0;
gap: 8px;
`;
1 change: 1 addition & 0 deletions src/components/group/GroupCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const CoverWrapper = styled.div`

const Cover = styled.img<{ cardType: 'main' | 'search' | 'modal'; isRecommend?: boolean }>`
object-fit: cover;
object-position: top;
flex-shrink: 0;
width: ${({ cardType, isRecommend }) => (cardType === 'search' || isRecommend ? '60px' : '80px')};
height: ${({ cardType, isRecommend }) =>
Expand Down
25 changes: 15 additions & 10 deletions src/components/memory/RecordItem/PollRecord.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,17 @@ const PollRecord = ({ content, pollOptions, postId, shouldBlur = false, onVoteUp
}
};

// 아무도 투표하지 않았는지 확인 (모든 옵션이 0%인지 확인)
const hasVotes = currentOptions.some(option => option.percentage > 0);

return (
<PollSection ref={pollRef}>
<PollQuestion>{content}</PollQuestion>
<PollOptions>
{currentOptions.map((option, index) => (
<PollOptionStyled
key={option.id}
isHighest={option.isHighest}
isHighest={hasVotes && option.isHighest}
onClick={shouldBlur ? undefined : () => handleOptionClick(option)}
style={{
cursor: shouldBlur ? 'default' : (isVoting ? 'not-allowed' : 'pointer'),
Expand All @@ -158,22 +161,24 @@ const PollRecord = ({ content, pollOptions, postId, shouldBlur = false, onVoteUp
>
<PollBar>
<PollBarFill
percentage={option.percentage}
isHighest={option.isHighest}
animate={animate}
percentage={hasVotes ? option.percentage : 0}
isHighest={hasVotes && option.isHighest}
animate={hasVotes && animate}
delay={index * 200} // 각 옵션마다 200ms 지연
/>
</PollBar>
<PollContent>
<PollNumber isHighest={option.isHighest}>
{option.id}
<PollNumber isHighest={hasVotes && option.isHighest}>
{index + 1}
</PollNumber>
<PollText isHighest={option.isHighest}>
<PollText isHighest={hasVotes && option.isHighest}>
{option.text}
</PollText>
<PollPercentage isHighest={option.isHighest}>
{option.percentage}%
</PollPercentage>
{hasVotes && (
<PollPercentage isHighest={hasVotes && option.isHighest}>
{option.percentage}%
</PollPercentage>
)}
</PollContent>
</PollOptionStyled>
))}
Expand Down
5 changes: 4 additions & 1 deletion src/components/memory/RecordItem/RecordItem.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ export const UserSection = styled.div`
margin-bottom: 8px;
`;

export const UserAvatar = styled.div`
export const UserAvatar = styled.div<{ src?: string }>`
width: 36px;
height: 36px;
border-radius: 50%;
background-color: ${colors.grey[400]};
background-image: ${({ src }) => src ? `url(${src})` : 'none'};
background-size: cover;
background-position: center;
margin-right: 8px;
`;

Expand Down
110 changes: 28 additions & 82 deletions src/components/memory/RecordItem/RecordItem.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useRef, useCallback } from 'react';
import { useState, useCallback } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import type { Record } from '../../../types/memory';
import TextRecord from './TextRecord';
Expand Down Expand Up @@ -39,6 +39,7 @@ const RecordItem = ({ record, shouldBlur = false }: RecordItemProps) => {
const {
id,
user,
profileImageUrl,
content,
likeCount,
commentCount,
Expand All @@ -57,11 +58,6 @@ const RecordItem = ({ record, shouldBlur = false }: RecordItemProps) => {
// 전역 댓글 바텀시트
const { openCommentBottomSheet } = useCommentBottomSheetStore();

// 길게 누르기 상태 관리
const [isPressed, setIsPressed] = useState(false);
const longPressTimer = useRef<NodeJS.Timeout | null>(null);
const pressStartPos = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
const hasTriggeredLongPress = useRef(false);

// API에서 받은 isWriter 속성으로 내 기록인지 판단
const isMyRecord = isWriter ?? false;
Expand Down Expand Up @@ -269,94 +265,44 @@ const RecordItem = ({ record, shouldBlur = false }: RecordItemProps) => {
openCommentBottomSheet(parseInt(id), type === 'poll' ? 'VOTE' : 'RECORD');
}, [openCommentBottomSheet, id, type]);

// 길게 누르기 이벤트 핸들러
const handleTouchStart = useCallback(
(e: React.TouchEvent) => {
setIsPressed(true);
hasTriggeredLongPress.current = false;
pressStartPos.current = {
x: e.touches[0].clientX,
y: e.touches[0].clientY,
};

longPressTimer.current = setTimeout(() => {
hasTriggeredLongPress.current = true;
setIsPressed(false);

if (isMyRecord) {
openMoreMenu({
onEdit: handleEdit,
onDelete: handleDeleteConfirm,
onPin: handlePinConfirm,
onClose: closePopup,
type: 'post',
isWriter: true,
});
} else {
openMoreMenu({
onReport: handleReport,
onClose: closePopup,
});
}
}, 800);
},
[
isMyRecord,
openMoreMenu,
handleReport,
handleEdit,
handleDeleteConfirm,
handlePinConfirm,
closePopup,
],
);

const handleTouchMove = useCallback((e: React.TouchEvent) => {
if (!longPressTimer.current) return;

const currentX = e.touches[0].clientX;
const currentY = e.touches[0].clientY;
const deltaX = Math.abs(currentX - pressStartPos.current.x);
const deltaY = Math.abs(currentY - pressStartPos.current.y);

if (deltaX > 10 || deltaY > 10) {
clearTimeout(longPressTimer.current);
longPressTimer.current = null;
setIsPressed(false);
}
}, []);

const handleTouchEnd = useCallback(() => {
if (longPressTimer.current) {
clearTimeout(longPressTimer.current);
longPressTimer.current = null;
}
setIsPressed(false);
}, []);

const handleClick = useCallback(() => {
if (hasTriggeredLongPress.current) {
hasTriggeredLongPress.current = false;
return;
// 클릭으로 더보기 메뉴 표시
if (isMyRecord) {
openMoreMenu({
onEdit: handleEdit,
onDelete: handleDeleteConfirm,
onPin: handlePinConfirm,
onClose: closePopup,
type: 'post',
isWriter: true,
});
} else {
openMoreMenu({
onReport: handleReport,
onClose: closePopup,
});
}
// 모든 기록(내 기록 포함)은 이제 길게 누르기로만 더보기 메뉴 표시
}, []);
}, [
isMyRecord,
openMoreMenu,
handleReport,
handleEdit,
handleDeleteConfirm,
handlePinConfirm,
closePopup,
]);

return (
<Container
onClick={handleClick}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
style={{
filter: shouldBlur ? 'blur(4px)' : 'none',
transform: isPressed ? 'scale(0.98)' : 'scale(1)',
transition: 'transform 0.1s ease',
touchAction: 'manipulation',
cursor: 'pointer',
}}
>
<UserSection>
<UserAvatar />
<UserAvatar src={profileImageUrl} />
<UserInfo>
<UserName>{user}</UserName>
<PageInfo>{renderPageInfo()}</PageInfo>
Expand Down
8 changes: 7 additions & 1 deletion src/components/recordwrite/PageRangeSection.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const InputWrapper = styled.div`
font-size: ${typography.fontSize.sm};
font-weight: ${typography.fontWeight.regular};
font-family: ${typography.fontFamily.primary};
width: fit-content;
`;

export const PageInput = styled.input<{ inputLength?: number }>`
Expand All @@ -49,11 +50,12 @@ export const PageInput = styled.input<{ inputLength?: number }>`
font-size: ${typography.fontSize.sm};
font-weight: ${typography.fontWeight.regular};
font-family: ${typography.fontFamily.primary};
width: ${props => (props.inputLength ? `${Math.max(30, props.inputLength * 8 + 10)}px` : '30px')};
width: ${props => (props.inputLength ? `${Math.max(27, props.inputLength * 8)}px` : '27px')};
padding: 0;
margin: 0;
caret-color: ${colors.white};
transition: width 0.2s ease;
flex-shrink: 0;

&::placeholder {
color: ${semanticColors.text.ghost};
Expand All @@ -77,6 +79,10 @@ export const PageSuffix = styled.span`
font-family: ${typography.fontFamily.primary};
margin: 0;
padding: 0;
white-space: nowrap;
display: inline-block;
flex-shrink: 0;
margin-left: 0px;
`;

export const OverallRangeText = styled.div`
Expand Down
9 changes: 8 additions & 1 deletion src/components/today-words/MessageList/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState, forwardRef, useImperativeHandle, useEffect } from 'react';
import moreIcon from '../../../assets/common/more.svg';
import type { Message } from '../../../types/today';
import MessageActionBottomSheet from './MessageActionBottomSheet';
import { usePopupActions } from '../../../hooks/usePopupActions';
import {
MessageList as StyledMessageList,
DateGroup,
Expand Down Expand Up @@ -39,6 +40,7 @@ const MessageList = forwardRef<MessageListRef, MessageListProps>(
) => {
const [selectedMessageId, setSelectedMessageId] = useState<string | null>(null);
const [messages, setMessages] = useState(initialMessages);
const { openSnackbar } = usePopupActions();

useEffect(() => {
setMessages(initialMessages);
Expand Down Expand Up @@ -102,7 +104,12 @@ const MessageList = forwardRef<MessageListRef, MessageListProps>(
if (selectedMessageId) {
// TODO: 실제 삭제 API 연동 필요
console.log(`메시지 ID ${selectedMessageId} 삭제 요청 (API 개발 대기중)`);
alert('삭제 기능은 현재 개발 중입니다.');
openSnackbar({
message: '삭제 기능은 현재 개발 중입니다.',
variant: 'top',
isError: true,
onClose: () => {},
});
}
setSelectedMessageId(null);
};
Expand Down
Loading