-
Notifications
You must be signed in to change notification settings - Fork 1
알림 캐싱 오류 해결 & 반복 일정 추가 기능 구현 #214
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
Merged
Merged
Changes from 12 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
0cf7dbd
🐛 [#212] Fix: 새로고침해야만 새로운 알림이 업데이트 되는 오류 해결
MyungJiwoo 8ec38f6
✨ [#212] Feat: 체험 등록시 반복 일정 추가 기능 구현
MyungJiwoo 20f1a75
♻️ [#212] Refactor: 반복 일정 추가의 반복 유형은 매주로 고정
MyungJiwoo dad0ba5
♻️ [#212] Refactor: DatePicker에서 날짜를 선택하면 팝오버가 닫히도록 수정
MyungJiwoo 1473876
♻️ [#212] Refactor: 모바일일때는 반복 일정 추가 팝업에서 DateInput이 수직으로 배치
MyungJiwoo 7b53f7b
✨ [#212] Feat: 로그인하지 않은 사용자는 체험 등록/수정 페이지 접근 불가
MyungJiwoo c253ec7
🐛 [#212] Fix: 디자인 문서에서 사용하지 않는 변수로 발생하는 빌드 오류 임시 해결
MyungJiwoo 7043017
♻️ [#212] Refactor: 체험 설명 textarea 높이 수정
MyungJiwoo 111d31d
♻️ [#212] Refactor: 캘린더에서 선택된 날의 UI 변경
MyungJiwoo 64299e8
🐛 [#212] Fix: 반복 일정 추가시 시작 시간보다 종료 시간이 늦으면 에러 토스트 메시지 띄움
MyungJiwoo 8897373
♻️ [#212] Refactor: 반복 일정 로직을 별도 컴포넌트로 분리
MyungJiwoo b6b59da
🐛 [#212] Fix: 반복 일정 추가시 시작 날짜보다 종료 날짜가 늦으면 에러 토스트 메시지 띄움
MyungJiwoo 43aa6c7
♻️ [#212] Refactor: 토끼 리뷰 반영
MyungJiwoo ab379bb
🐛 [#212] Fix: 체험 수정 에러 메시지 토스트로 출력
MyungJiwoo 8022426
🐛 [#212] Fix: 빌드 오류 해결
MyungJiwoo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
255 changes: 255 additions & 0 deletions
255
apps/what-today/src/components/experiences/RecurringScheduleModal.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,255 @@ | ||
| import { Button, DatePicker, Popover, TimePicker, useToast } from '@what-today/design-system'; | ||
| import type { Dayjs } from 'dayjs'; | ||
| import { useState } from 'react'; | ||
|
|
||
| interface Time { | ||
| hour: string; | ||
| minute: string; | ||
| } | ||
|
|
||
| export interface Schedule { | ||
| date?: Dayjs | null; | ||
| startTime?: Time | null; | ||
| endTime?: Time | null; | ||
| } | ||
|
|
||
| interface RecurringScheduleModalProps { | ||
| isOpen: boolean; | ||
| onOpenChange: (open: boolean) => void; | ||
| onSchedulesGenerated: (schedules: Schedule[]) => void; | ||
| } | ||
|
|
||
| function timeToMinutes(time: { hour: string; minute: string } | null): number { | ||
| if (!time) return -1; | ||
| return parseInt(time.hour) * 60 + parseInt(time.minute); | ||
| } | ||
|
|
||
| export default function RecurringScheduleModal({ | ||
| isOpen, | ||
| onOpenChange, | ||
| onSchedulesGenerated, | ||
| }: RecurringScheduleModalProps) { | ||
| const { toast } = useToast(); | ||
| const [selectedDays, setSelectedDays] = useState<number[]>([]); | ||
| const [startTime, setStartTime] = useState<Time | null>(null); | ||
| const [endTime, setEndTime] = useState<Time | null>(null); | ||
| const [startDate, setStartDate] = useState<Dayjs | null>(null); | ||
| const [endDate, setEndDate] = useState<Dayjs | null>(null); | ||
|
|
||
| const resetForm = () => { | ||
| setSelectedDays([]); | ||
| setStartTime(null); | ||
| setEndTime(null); | ||
| setStartDate(null); | ||
| setEndDate(null); | ||
| }; | ||
|
|
||
| const handlePopoverChange = (open: boolean) => { | ||
| onOpenChange(open); | ||
|
|
||
| // 팝오버가 닫힐 때 폼 초기화 | ||
| if (!open) { | ||
| resetForm(); | ||
| } | ||
| }; | ||
|
|
||
| const weekdays = [ | ||
| { value: 1, label: '월' }, | ||
| { value: 2, label: '화' }, | ||
| { value: 3, label: '수' }, | ||
| { value: 4, label: '목' }, | ||
| { value: 5, label: '금' }, | ||
| { value: 6, label: '토' }, | ||
| { value: 0, label: '일' }, | ||
| ]; | ||
|
|
||
| const toggleDay = (dayValue: number) => { | ||
| setSelectedDays((prev) => (prev.includes(dayValue) ? prev.filter((d) => d !== dayValue) : [...prev, dayValue])); | ||
| }; | ||
|
|
||
| const handleStartDateChange = (date: Dayjs | null) => { | ||
| setStartDate(date); | ||
|
|
||
| // 시작 날짜가 설정되고 종료 날짜가 있을 때 검증 | ||
| if (date && endDate) { | ||
| if (date.isAfter(endDate, 'day')) { | ||
| toast({ | ||
| title: '날짜 설정 오류', | ||
| description: '시작 날짜는 종료 날짜보다 빨라야 합니다.', | ||
| type: 'error', | ||
| }); | ||
| setEndDate(null); // 종료 날짜 초기화 | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const handleEndDateChange = (date: Dayjs | null) => { | ||
| // 종료 날짜가 설정되고 시작 날짜가 있을 때 검증 | ||
| if (date && startDate) { | ||
| if (startDate.isAfter(date, 'day')) { | ||
| toast({ | ||
| title: '날짜 설정 오류', | ||
| description: '종료 날짜는 시작 날짜보다 늦어야 합니다.', | ||
| type: 'error', | ||
| }); | ||
| return; // 변경사항 적용하지 않음 | ||
| } | ||
| } | ||
|
|
||
| setEndDate(date); | ||
| }; | ||
|
|
||
| const handleStartTimeChange = (time: Time | null) => { | ||
| setStartTime(time); | ||
|
|
||
| // 시작 시간이 설정되고 종료 시간이 있을 때 검증 | ||
| if (time && endTime) { | ||
| const startMinutes = timeToMinutes(time); | ||
| const endMinutes = timeToMinutes(endTime); | ||
|
|
||
| if (startMinutes >= endMinutes) { | ||
| toast({ | ||
| title: '시간 설정 오류', | ||
| description: '시작 시간은 종료 시간보다 빨라야 합니다.', | ||
| type: 'error', | ||
| }); | ||
| setEndTime(null); // 종료 시간 초기화 | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const handleEndTimeChange = (time: Time | null) => { | ||
| // 종료 시간이 설정되고 시작 시간이 있을 때 검증 | ||
| if (time && startTime) { | ||
| const startMinutes = timeToMinutes(startTime); | ||
| const endMinutes = timeToMinutes(time); | ||
|
|
||
| if (startMinutes >= endMinutes) { | ||
| toast({ | ||
| title: '시간 설정 오류', | ||
| description: '종료 시간은 시작 시간보다 늦어야 합니다.', | ||
| type: 'error', | ||
| }); | ||
| return; // 변경사항 적용하지 않음 | ||
| } | ||
| } | ||
|
|
||
| setEndTime(time); | ||
| }; | ||
|
|
||
| const generateSchedules = () => { | ||
| if (!startTime || !endTime || !startDate || !endDate || selectedDays.length === 0) { | ||
| toast({ | ||
| title: '입력 오류', | ||
| description: '모든 필드를 입력해주세요.', | ||
| type: 'error', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const newSchedules: Schedule[] = []; | ||
| let current = startDate.clone(); | ||
| let loopCount = 0; | ||
|
|
||
| while (current.isBefore(endDate) || current.isSame(endDate, 'day')) { | ||
| loopCount++; | ||
|
|
||
| // 무한루프 방지 | ||
| if (loopCount > 1000) { | ||
| console.error('무한루프 감지! 중단합니다.'); | ||
| break; | ||
| } | ||
|
|
||
| const currentDayOfWeek = current.day(); | ||
|
|
||
| if (selectedDays.includes(currentDayOfWeek)) { | ||
| newSchedules.push({ | ||
| date: current.clone(), | ||
| startTime: { ...startTime }, | ||
| endTime: { ...endTime }, | ||
| }); | ||
| } | ||
|
|
||
| // 매주 반복: 하루씩 증가 | ||
| current = current.add(1, 'day'); | ||
| } | ||
|
|
||
| // 생성된 스케줄을 부모 컴포넌트로 전달 | ||
| onSchedulesGenerated(newSchedules); | ||
| resetForm(); | ||
| onOpenChange(false); | ||
|
|
||
| toast({ | ||
| title: '일정 생성 완료', | ||
| description: `${newSchedules.length}개의 일정이 추가되었습니다.`, | ||
| type: 'success', | ||
| }); | ||
| }; | ||
|
|
||
| return ( | ||
| <Popover.Root direction='fixed-center-center' open={isOpen} onOpenChange={handlePopoverChange}> | ||
| <Popover.Trigger asChild> | ||
| <Button | ||
| className='caption-text h-fit border-gray-100 px-10 py-4' | ||
| size='sm' | ||
| variant='outline' | ||
| onClick={() => onOpenChange(true)} | ||
| > | ||
| 반복 일정 추가 | ||
| </Button> | ||
| </Popover.Trigger> | ||
| <Popover.Content overlay preventInteraction className='rounded-2xl border-gray-50 bg-white p-24'> | ||
| <div className='flex w-300 flex-col gap-16 md:w-500 xl:w-700'> | ||
| <div> | ||
| <label className='mb-2 block text-sm font-medium'>반복 유형</label> | ||
| <div className='flex items-center gap-8 rounded-xl border border-gray-100 bg-white px-20 py-10'>매주</div> | ||
| </div> | ||
|
|
||
| <div className='flex flex-col gap-4 md:flex-row'> | ||
| <div className='flex-1'> | ||
| <label className='mb-2 block text-sm font-medium'>시작 날짜</label> | ||
| <DatePicker value={startDate} onChange={handleStartDateChange} /> | ||
| </div> | ||
| <div className='flex-1'> | ||
| <label className='mb-2 block text-sm font-medium'>종료 날짜</label> | ||
| <DatePicker value={endDate} onChange={handleEndDateChange} /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div> | ||
| <label className='mb-2 block text-sm font-medium'>요일 선택</label> | ||
| <div className='flex flex-wrap gap-4'> | ||
| {weekdays.map((day) => ( | ||
| <button | ||
| key={day.value} | ||
| className={`body-text flex size-32 cursor-pointer items-center justify-center rounded-lg border border-gray-100 transition-colors ${ | ||
| selectedDays.includes(day.value) ? 'bg-gray-50' : '' | ||
| }`} | ||
| type='button' | ||
| onClick={() => toggleDay(day.value)} | ||
| > | ||
| {day.label} | ||
| </button> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className='grid grid-cols-2 gap-4'> | ||
| <div> | ||
| <label className='mb-2 block text-sm font-medium'>시작 시간</label> | ||
| <TimePicker className='w-full' value={startTime} onChange={handleStartTimeChange} /> | ||
| </div> | ||
| <div> | ||
| <label className='mb-2 block text-sm font-medium'>종료 시간</label> | ||
| <TimePicker className='w-full' value={endTime} onChange={handleEndTimeChange} /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <Button className='w-full' size='sm' onClick={generateSchedules}> | ||
| 일정 생성 | ||
| </Button> | ||
| </div> | ||
| </Popover.Content> | ||
| </Popover.Root> | ||
| ); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.