Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0af70c1
style: 알림패널 "알림" 폰트 수정 (#308)
KIMGEONHWI May 6, 2025
04e773e
fix: 설정 컴포넌트 문구 수정 (#308)
KIMGEONHWI May 6, 2025
090af21
fix: 설정 컴포넌트 탭 카테고리 폰트 수정 (#308)
KIMGEONHWI May 6, 2025
eb7efab
fix: 오늘 할 일 추가 박스 문구 수정 (#308)
KIMGEONHWI May 6, 2025
0a33fbe
fix: 친구삭제 버튼 클릭 영역 넓히기 (#308)
KIMGEONHWI May 6, 2025
aab9beb
refactor: 할 일 카드 완료된 일로 변경시, 타이머 정지 (#308)
KIMGEONHWI May 6, 2025
fa5179c
refactor: 타이머페이지 완료된 할 일 카드인 경우 타이머 실행 막기 (#308)
KIMGEONHWI May 6, 2025
f50457d
refactor: 타이머페이지에서 완료된 할 일 카드인 경우 line-through 적용 (#308)
KIMGEONHWI May 6, 2025
0bf10fa
fix: 타이머페이지 할 일 카드 duration 버튼 hover 막기 (#308)
KIMGEONHWI May 6, 2025
fa6ff6f
refactor: 할 일 카드를 완료시킬 경우 클릭된 할 일 카드가 완료시킨 할 일 카드가 되는데, 클릭된 할 일 카드는 남…
KIMGEONHWI May 6, 2025
b80be0e
fix: 타이머페이지 Boxtodo의 경우 name 클릭해도 name 수정 안되도록 수정 (#308)
KIMGEONHWI May 6, 2025
905747d
style: 타이머페이지 친구리스트 캐러셀 스타일 조정 (#308)
KIMGEONHWI May 7, 2025
394645c
feat: 허용서비스페이지 알림패널 적용 (#308)
KIMGEONHWI May 7, 2025
7e23e4f
refactor: Boxtodo 완료 버튼 이벤트 전파 막기 및 선택된 할 일의 상태가 변경되면 타이머 정지 및 남은 할 일…
KIMGEONHWI May 7, 2025
ad93bd3
fix: 할 일 추가 중인 상태에서 클릭 막기 (#308)
KIMGEONHWI May 8, 2025
d093c00
fix: 타이머페이지 완료된 할 일 카드 클릭 가능하도록 수정 (#308)
KIMGEONHWI May 8, 2025
1734d1a
codereview: 타이머페이지 캐러셀 absolute 적용해서 스타일링 (#308)
KIMGEONHWI May 9, 2025
d21eda3
codereview: 상아 코리 반영 (#308)
KIMGEONHWI May 9, 2025
f2d8c67
codereview: 선택된 할 일의 상태 변경시 타이머 정지 타이밍 이슈 해결 (#308)
KIMGEONHWI May 9, 2025
c8d454c
codereview: 대원 코리 반영 (#308)
KIMGEONHWI May 9, 2025
19551d4
Merge branch 'develop' into refactor/#308/home-friend-qa
10tacion May 10, 2025
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
34 changes: 32 additions & 2 deletions src/pages/AllowedServicePage/AllowedServicePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { useQueryClient } from '@tanstack/react-query';
import AutoFixedGrid from '@/shared/components/AutoFixedGrid/AutoFixedGrid';
import ModalContentsFriends from '@/shared/components/ModalContentsFriends/ModalContentsFriends';
import ModalWrapper, { ModalWrapperRef } from '@/shared/components/ModalWrapper/ModalWrapper';
import NotificationPanel from '@/shared/components/NotificationPanel/NotificationPanel';
import Spacer from '@/shared/components/Spacer/Spacer';
import TextField from '@/shared/components/TextField/TextField';

import useClickOutside from '@/shared/hooks/useClickOutside';

import { isUrlValid } from '@/shared/utils/validation';

import { ColorPaletteType } from '@/shared/types/allowedService';
Expand Down Expand Up @@ -43,11 +46,16 @@ const AllowedServicePage = () => {
const [isEditingTitle, setIsEditingTitle] = useState(false);
const [urlInput, setUrlInput] = useState('');
const [selectedColor, setSelectedColor] = useState<ColorPaletteType>('#868C93');
const [isNotificationVisible, setIsNotificationVisible] = useState(false);

const queryClient = useQueryClient();

const friendsModalRef = useRef<ModalWrapperRef>(null);
const requireTitleModalRef = useRef<ModalWrapperRef>(null);

const bellIconRef = useRef<HTMLButtonElement>(null);
const notificationPanelRef = useRef<HTMLDivElement>(null);


const handleChangeTitleInput = (e: ChangeEvent<HTMLInputElement>) => {
setTitleInput(e.target.value);
Expand Down Expand Up @@ -211,6 +219,24 @@ const AllowedServicePage = () => {
}
};

const toggleNotification = () => {
setIsNotificationVisible((prev) => !prev);
};

useClickOutside(
notificationPanelRef,
(event) => {
if (!isNotificationVisible) return;

if (bellIconRef.current && event && bellIconRef.current.contains(event.target as Node)) {
return;
}

setIsNotificationVisible(false);
},
isNotificationVisible,
);

// NOTE: 첫 렌더링 시 api를 통해 받은 첫번째 allowed service group id를 activeGroupId로 설정
// 리스트 삭제 후, 현재 active 그룹이 리스트에 없는 경우 첫 번째 그룹으로 설정
useEffect(() => {
Expand Down Expand Up @@ -252,7 +278,7 @@ const AllowedServicePage = () => {
<button onClick={handleOpenFriendsModal}>
<FriendSettingIcon className="rounded-[1.6rem] hover:bg-gray-bg-04 active:bg-gray-bg-05" />
</button>
<button>
<button ref={bellIconRef} onClick={toggleNotification}>
<BellIcon className="rounded-[1.6rem] hover:bg-gray-bg-04 active:bg-gray-bg-05" />
</button>
</div>
Expand Down Expand Up @@ -356,9 +382,13 @@ const AllowedServicePage = () => {
<ModalWrapper ref={friendsModalRef} backdrop>
{({ isModalOpen }) => <ModalContentsFriends isModalOpen={isModalOpen} />}
</ModalWrapper>

<ModalWrapper ref={requireTitleModalRef} backdrop>
{() => <ModalContentsAlert.RequireTitle onClick={handleCloseRequireTitleModal} />}
</ModalWrapper>
</ModalWrapper>

{isNotificationVisible && <NotificationPanel ref={notificationPanelRef} />}

</AutoFixedGrid>
);
};
Expand Down
9 changes: 6 additions & 3 deletions src/pages/HomePage/BoxCategory/BoxCategory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,10 @@ const BoxCategory = ({
onKeyDown={handleKeyDown}
/>
) : (
<h2 className="truncate text-white subhead-semibold-18" onClick={handleStartEditing}>
<h2
className={`truncate text-white subhead-semibold-18 ${addingTodayTodoStatus ? 'pointer-events-none' : ''}`}
onClick={handleStartEditing}
>
{title}
</h2>
)}
Expand All @@ -257,12 +260,12 @@ const BoxCategory = ({
<button
onMouseEnter={handleMouseEnter}
onClick={startAddingTodo}
className="rounded-full hover:bg-gray-bg-04 active:bg-gray-bg-05"
className={`rounded-full ${addingTodayTodoStatus ? 'pointer-events-none' : 'hover:bg-gray-bg-04 active:bg-gray-bg-05'}`}
>
<PlusIcon />
</button>
<Dropdown>
<Dropdown.Trigger>
<Dropdown.Trigger className={addingTodayTodoStatus ? 'pointer-events-none' : ''}>
<MeatballDefaultIcon className="rounded-full hover:bg-gray-bg-04 active:bg-gray-bg-05" />
</Dropdown.Trigger>
<Dropdown.Content className="right-0 top-[3.2rem]">
Expand Down
1 change: 1 addition & 0 deletions src/pages/HomePage/BoxTodayTodo/BoxTodayTodo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const BoxTodayTodo = ({
cancelComplete={cancelComplete}
addingComplete={addingComplete}
onCreateTodayTodos={onCreateTodayTodos}
addingTodayTodoStatus={addingTodayTodoStatus}
/>
) : (
<StatusDefaultBoxTodayTodo hasTodos={hasTodos} onEnableAddStatus={enableAddingTodayTodo} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface StatusAddBoxTodayTodoProps {
cancelComplete: () => void;
addingComplete: boolean;
onCreateTodayTodos: () => void;
addingTodayTodoStatus: boolean;
}

const StatusAddBoxTodayTodo = ({
Expand All @@ -35,6 +36,7 @@ const StatusAddBoxTodayTodo = ({
cancelComplete,
addingComplete,
onCreateTodayTodos,
addingTodayTodoStatus,
}: StatusAddBoxTodayTodoProps) => {
const { data: allowedServiceList } = useGetPopoverAllowedServiceList();
const registerServiceModalRef = useRef<ModalWrapperRef>(null);
Expand Down Expand Up @@ -82,6 +84,7 @@ const StatusAddBoxTodayTodo = ({
selectedNumber={selectedNumber}
updateTodayTodos={deleteTodayTodos}
addingComplete={addingComplete}
addingTodayTodoStatus={addingTodayTodoStatus}
/>
</li>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const StatusDefaultBoxTodayTodo = ({ hasTodos, onEnableAddStatus }: StatusDefaul
<div className="mb-[24%]">
<p className="text-center text-gray-03 body-med-16">아직 오늘 할 일이 없어요</p>
<div className="mb-[2.2rem] mt-[0.8rem] flex flex-col gap-[0.5rem]">
<p className="text-center text-gray-05 subhead-semibold-18">할 일을 추가하려면</p>
<p className="text-center text-gray-05 subhead-semibold-18">+ 아이콘을 선택해주세요.</p>
<p className="text-center text-gray-05 subhead-semibold-18">할 일을 추가해</p>
<p className="text-center text-gray-05 subhead-semibold-18">타이머를 시작해 보세요.</p>
</div>

<div className="mx-auto">
Expand Down
18 changes: 14 additions & 4 deletions src/pages/HomePage/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,13 @@ const HomePage = () => {
<PopoverAddCategoryIcon />
</button>
)}
<button className="flex-shrink-0" onClick={handleAddCategory}>
<LargePlusIcon className="rounded-full bg-gray-bg-03 hover:bg-gray-bg-05" />
<button
className={`flex-shrink-0 ${addingTodayTodoStatus ? 'pointer-events-none' : ''}`}
onClick={handleAddCategory}
>
<LargePlusIcon
className={`rounded-full bg-gray-bg-03 ${addingTodayTodoStatus ? '' : 'hover:bg-gray-bg-05'}`}
/>
</button>
</div>
)}
Expand All @@ -425,8 +430,13 @@ const HomePage = () => {

{dailyCategoryTask.length > 2 && (
<div className="ml-[1.4rem] flex flex-col">
<button className="flex-shrink-0" onClick={handleAddCategory}>
<LargePlusIcon className="rounded-full bg-gray-bg-03 hover:bg-gray-bg-05" />
<button
className={`flex-shrink-0 ${addingTodayTodoStatus ? 'pointer-events-none' : ''}`}
onClick={addingTodayTodoStatus ? undefined : handleAddCategory}
>
<LargePlusIcon
className={`rounded-full bg-gray-bg-03 ${addingTodayTodoStatus ? '' : 'hover:bg-gray-bg-05'}`}
/>
</button>
</div>
)}
Expand Down
6 changes: 4 additions & 2 deletions src/pages/TimerPage/Carousel/CarouselFriend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const CarouselFriend = memo(function CarouselFriend({
return (
<div className="relative flex h-[15rem] w-[9.8rem] flex-shrink-0 flex-col items-center justify-center px-[0.8rem] py-[0.5rem]">
{/* 프로필 이미지 영역 */}
<span className="relative mb-[2.9rem] h-[7.4rem] w-[7.4rem]">
<span className="relative mb-[3rem] h-[7.4rem] w-[7.4rem]">
<img
src={image}
alt={`${name}의 프로필`}
Expand All @@ -61,7 +61,9 @@ const CarouselFriend = memo(function CarouselFriend({
{/* 타이머 표시 영역 */}
<div className="absolute top-[8.4rem] flex items-center gap-[0.4rem]">
<ClockIcon />
<span className={`detail-reg-14 ${isPlaying ? 'text-mint-02' : 'text-gray-03'}`}>{formattedTime}</span>
<span className={`pt-[0.1rem] detail-reg-14 ${isPlaying ? 'text-mint-02' : 'text-gray-03'}`}>
{formattedTime}
</span>
</div>

{/* 이름 및 카테고리 표시 */}
Expand Down
29 changes: 25 additions & 4 deletions src/pages/TimerPage/MainTimer/MainTimer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';

import { splitTasksByCompletion } from '@/shared/utils/timer';

import { getFormattedTimeInfo } from '@/pages/TimerPage/utils/timeFormat';
import { useGetTimerTodos } from '@/shared/apisV2/timer/timer.queries';

import { useTimerContext } from '../contexts/TimerContext';
import TimerDisplay from './TimerDisplay/TimerDisplay';
Expand All @@ -13,18 +16,34 @@ import TimerHeader from './TimerHeader/TimerHeader';
*/
const MainTimer = () => {
// 타이머 컨텍스트에서 필요한 상태와 액션만 가져오기
const { timer, elapsedTime, totalElapsedTimeOfToday, isPlaying, selectedTask, actions } = useTimerContext();
const { todayFormattedDate, timer, elapsedTime, totalElapsedTimeOfToday, isPlaying, selectedTask, actions } =
useTimerContext();

// 할일 데이터 조회
const { data: todosData } = useGetTimerTodos({ targetDate: todayFormattedDate });

// 완료된 할 일 목록 가져오기
const { completedTodos } = useMemo(() => {
const todos = todosData?.data?.task ?? [];
return splitTasksByCompletion(todos);
}, [todosData]);

// 작업이 선택되어 있는지 여부
const hasSelectedTask = selectedTask.id !== null;

// 선택된 작업이 완료되었는지 확인
const isSelectedTaskCompleted = useCallback(() => {
if (!selectedTask.id) return false;
return completedTodos.some((todo) => todo.id === selectedTask.id);
}, [completedTodos, selectedTask.id]);

// 재생/정지 토글 핸들러 - 작업이 선택되지 않은 경우 처리 방지
const handlePlayPauseToggle = useCallback(() => {
if (!hasSelectedTask) return;
if (!hasSelectedTask || isSelectedTaskCompleted()) return;

// 현재 상태의 반대로 토글
actions.togglePlay(!isPlaying);
}, [isPlaying, hasSelectedTask, actions]);
}, [isPlaying, hasSelectedTask, isSelectedTaskCompleted, actions]);

// 표시할 시간 정보 계산 - 유틸 함수로 분리하여 관심사 분리
const timeInfo = getFormattedTimeInfo(timer, elapsedTime, totalElapsedTimeOfToday);
Expand All @@ -36,6 +55,7 @@ const MainTimer = () => {
selectedTaskName={selectedTask.name}
selectedTaskCategoryName={selectedTask.categoryName}
hasSelectedTask={hasSelectedTask}
isCompleted={isSelectedTaskCompleted()}
/>

{/* 타이머 디스플레이 - 시간 표시 및 제어 버튼 */}
Expand All @@ -45,6 +65,7 @@ const MainTimer = () => {
timer={timer}
isPlaying={isPlaying}
onToggle={handlePlayPauseToggle}
disabled={!hasSelectedTask || isSelectedTaskCompleted()}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { MouseEvent } from 'react';

import PauseIcon from '@/shared/assets/svgs/defaultpause.svg?react';
import PlayIcon from '@/shared/assets/svgs/defaultplay.svg?react';
import HoverPauseIcon from '@/shared/assets/svgs/hoverpause.svg?react';
Expand All @@ -6,14 +8,15 @@ import HoverPlayIcon from '@/shared/assets/svgs/hoverplay.svg?react';
interface ButtonTimerPlayProps {
onClick: () => void;
isPlaying: boolean;
disabled?: boolean;
}

const ButtonTimerPlay = ({ onClick, isPlaying }: ButtonTimerPlayProps) => {
const ButtonTimerPlay = ({ onClick, isPlaying, disabled = false }: ButtonTimerPlayProps) => {
const IconComponent = isPlaying ? PauseIcon : PlayIcon;
const HoverIconComponent = isPlaying ? HoverPauseIcon : HoverPlayIcon;

return (
<button onClick={onClick} className="group relative">
<button onClick={onClick} className={`group relative ${disabled ? 'cursor-not-allowed' : ''}`} disabled={disabled}>
<IconComponent className="block group-hover:hidden" />
<HoverIconComponent className="hidden group-hover:block" />
</button>
Expand Down
12 changes: 10 additions & 2 deletions src/pages/TimerPage/MainTimer/TimerDisplay/TimerDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,22 @@ interface TimerDisplayProps {
timer: number;
isPlaying: boolean;
onToggle: () => void;
disabled?: boolean;
}

/**
* 타이머 디스플레이 컴포넌트
*
* 타이머의 시간 표시, 상태 텍스트 및 재생/정지 버튼을 포함하는 UI
*/
const TimerDisplay = ({ statusText, formattedTimeText, timer, isPlaying, onToggle }: TimerDisplayProps) => {
const TimerDisplay = ({
statusText,
formattedTimeText,
timer,
isPlaying,
onToggle,
disabled = false,
}: TimerDisplayProps) => {
return (
<div className="relative flex items-center justify-center">
<ProgressCircle isPlaying={isPlaying} timer={timer} />
Expand All @@ -32,7 +40,7 @@ const TimerDisplay = ({ statusText, formattedTimeText, timer, isPlaying, onToggl
<span className="text-mint-01 title-semibold-48">{formattedTimeText}</span>
</div>

<ButtonTimerPlay onClick={onToggle} isPlaying={isPlaying} />
<ButtonTimerPlay onClick={onToggle} isPlaying={isPlaying} disabled={disabled} />
</div>
</div>
);
Expand Down
8 changes: 7 additions & 1 deletion src/pages/TimerPage/MainTimer/TimerHeader/TimerHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@ interface TimerHeaderProps {
selectedTaskName: string;
selectedTaskCategoryName: string;
hasSelectedTask: boolean;
isCompleted?: boolean;
}

/**
* 타이머 헤더 컴포넌트
*
* 선택된 할일의 이름과 카테고리를 표시하거나, 할일이 선택되지 않은 경우 안내 메시지를 표시.
*/
const TimerHeader = ({ selectedTaskName, selectedTaskCategoryName, hasSelectedTask }: TimerHeaderProps) => {
const TimerHeader = ({
selectedTaskName,
selectedTaskCategoryName,
hasSelectedTask,
isCompleted = false,
}: TimerHeaderProps) => {
if (!hasSelectedTask) {
return (
<header className="flex flex-col items-center gap-[0.4rem]">
Expand Down
18 changes: 16 additions & 2 deletions src/pages/TimerPage/SideMenuTimer/SideMenuTimer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,24 @@ const SideMenuTimer = ({ ongoingTodos = [], completedTodos = [] }: SideMenuTimer
setCompletedTodoToggle(true);
}

// 선택된 할 일의 상태가 변경되면 타이머 정지 및 남은 할 일 중 첫번 째 할 일 선택
if (selectedTask.id === taskId && actions.stopCurrentTimer) {
actions.stopCurrentTimer(selectedTask.id);
const remainingTodos = ongoingTodos.filter((todo) => todo.id !== taskId);
if (remainingTodos.length > 0) {
const next = remainingTodos[0];
actions.selectTask(next.id, next.elapsedTime, next.name, next.categoryName);
}
}

queryClient.invalidateQueries({
queryKey: timerKeys.todos({ targetDate: todayFormattedDate }),
});
},
},
);
},
[toggleTaskStatus, todayFormattedDate, queryClient],
[toggleTaskStatus, todayFormattedDate, queryClient, selectedTask.id, actions, ongoingTodos],
);

// 할일 항목 렌더링 함수 - 최적화됨
Expand All @@ -102,9 +112,13 @@ const SideMenuTimer = ({ ongoingTodos = [], completedTodos = [] }: SideMenuTimer
{...todo}
isSelected={todo.id === selectedTask.id}
onClick={() => handleTodoClick(todo)}
onToggleComplete={() => handleToggleTodoComplete(todo.id, isOngoing)}
onToggleComplete={(e) => {
e.stopPropagation();
handleToggleTodoComplete(todo.id, isOngoing);
}}
timerIncreasedTime={getTimerIncreasedTime(todo.id, todo.elapsedTime, selectedTask.id, timer)}
undeletable={true}
disableHoverCalendar={true}
/>
),
[handleTodoClick, handleToggleTodoComplete, selectedTask.id, timer],
Expand Down
Loading