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
115 changes: 84 additions & 31 deletions src/features/Calendar/components/CustomCalendar/CalendarModals.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import moment from 'moment'
import { useMemo } from 'react'
import { useMemo, useState } from 'react'

import { useDetailEventQuery } from '@/shared/hooks/query/useCalendarQueries'
import type { CalendarEvent } from '@/shared/types/calendar/types'
import type { ItemEditorDraft } from '@/shared/types/modal/itemEditor'
import ScheduleEditorModal from '@/shared/ui/Modals/ScheduleEditor'
import TodoEditorModal from '@/shared/ui/Modals/TodoEditor'
import { buildDefaultItemEditorDraft } from '@/shared/utils'

type CalendarModalsProps = {
modalDate: string
Expand All @@ -27,6 +29,74 @@ type CalendarModalsProps = {
}
}

type DraftBackedModalProps = {
modalDate: string
modalEventId: CalendarEvent['id']
modalEvent: CalendarEvent | null
detailEvent: CalendarEvent | null
isModalEditing: boolean
modalMode: 'modal' | 'inline'
onCloseModal: () => void
eventActions: CalendarModalsProps['eventActions']
}

const DraftBackedModal = ({
modalDate,
modalEventId,
modalEvent,
detailEvent,
isModalEditing,
modalMode,
onCloseModal,
eventActions,
}: DraftBackedModalProps) => {
const activeEvent = detailEvent ?? modalEvent
const isTodoModal = activeEvent?.type === 'todo'
const [draftValues, setDraftValues] = useState<ItemEditorDraft | null>(() =>
isModalEditing
? null
: buildDefaultItemEditorDraft(modalDate, isTodoModal ? 'todo' : 'schedule', activeEvent),
)

if (isTodoModal) {
return (
<TodoEditorModal
date={modalDate}
onClose={onCloseModal}
mode={modalMode}
eventId={modalEventId}
event={activeEvent}
showTypeTabs={!isModalEditing}
draftValues={draftValues}
onDraftChange={setDraftValues}
onEventColorChange={eventActions.onEventColorChange}
onEventTitleConfirm={eventActions.onEventTitleConfirm}
onEventTypeChange={eventActions.onEventTypeChange}
onEventTimingChange={eventActions.onEventTimingChange}
isEditing={isModalEditing}
/>
)
}

return (
<ScheduleEditorModal
date={modalDate}
onClose={onCloseModal}
mode={modalMode}
eventId={modalEventId}
event={activeEvent}
isEditing={isModalEditing}
showTypeTabs={!isModalEditing}
draftValues={draftValues}
onDraftChange={setDraftValues}
onEventColorChange={eventActions.onEventColorChange}
onEventTitleConfirm={eventActions.onEventTitleConfirm}
onEventTypeChange={eventActions.onEventTypeChange}
onEventTimingChange={eventActions.onEventTimingChange}
/>
)
}

const CalendarModals = ({
modalDate,
modalEventId,
Expand Down Expand Up @@ -60,39 +130,22 @@ const CalendarModals = ({
id: result.id ?? safeDetailEventId ?? 0,
}
}, [data, safeDetailEventId])
const resetKey = `${String(modalEventId ?? 'closed')}::${occurrenceDate}::${isModalEditing ? 'edit' : 'create'}`

return (
<>
{/* ItemEditorModal ๋‚ด๋ถ€ ํฌํ„ธ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•ด, ๋ฆฌ์‚ฌ์ด์ฆˆ ์‹œ ๊ฐ™์€ ํผ ์ƒํƒœ๋กœ ๋ฃจํŠธ๋งŒ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. */}
{shouldRenderModal && isTodoModal && (
<TodoEditorModal
key={`todo-${String(modalEventId)}-${occurrenceDate}-${isModalEditing ? 'edit' : 'create'}`}
date={modalDate}
onClose={onCloseModal}
mode={modalMode}
eventId={modalEventId}
event={modalEvent}
showTypeTabs={!isModalEditing}
onEventColorChange={eventActions.onEventColorChange}
onEventTitleConfirm={eventActions.onEventTitleConfirm}
onEventTypeChange={eventActions.onEventTypeChange}
onEventTimingChange={eventActions.onEventTimingChange}
isEditing={isModalEditing}
/>
)}
{shouldRenderModal && !isTodoModal && (
<ScheduleEditorModal
key={`schedule-${String(modalEventId)}-${occurrenceDate}-${isModalEditing ? 'edit' : 'create'}`}
date={modalDate}
onClose={onCloseModal}
mode={modalMode}
eventId={modalEventId}
event={detailEvent ?? modalEvent}
isEditing={isModalEditing}
showTypeTabs={!isModalEditing}
onEventColorChange={eventActions.onEventColorChange}
onEventTitleConfirm={eventActions.onEventTitleConfirm}
onEventTypeChange={eventActions.onEventTypeChange}
onEventTimingChange={eventActions.onEventTimingChange}
{shouldRenderModal && modalEventId != null && (
<DraftBackedModal
key={resetKey}
modalDate={modalDate}
modalEventId={modalEventId}
modalEvent={modalEvent}
detailEvent={detailEvent}
isModalEditing={isModalEditing}
modalMode={modalMode}
onCloseModal={onCloseModal}
eventActions={eventActions}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const WeekEventContainer = styled.div<{
export const EventTitle = styled.div`
font-weight: 400;
font-size: 12px;
width: 45px;
width: 38px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
Expand Down
8 changes: 6 additions & 2 deletions src/shared/hooks/form/useScheduleFormFields.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { yupResolver } from '@hookform/resolvers/yup'
import { useEffect, useMemo } from 'react'
import { useEffect, useMemo, useRef } from 'react'
import { type Control, type Resolver, useForm, type UseFormReturn, useWatch } from 'react-hook-form'

import { addScheduleSchema } from '@/shared/schemas/schedule'
Expand Down Expand Up @@ -113,6 +113,7 @@ export const useScheduleFormFields = ({
() => buildScheduleDefaultValues({ date, initialEvent, draftValues }),
[date, draftValues, initialEvent],
)
const previousResetKeyRef = useRef(`${date}::${String(initialEvent?.id ?? 'new')}`)
const formMethods = useForm<ScheduleEditorFormValues>({
resolver,
defaultValues: initialValues,
Expand Down Expand Up @@ -148,8 +149,11 @@ export const useScheduleFormFields = ({

useEffect(() => {
if (isEditing) return
const nextResetKey = `${date}::${String(initialEvent?.id ?? 'new')}`
if (previousResetKeyRef.current === nextResetKey) return
previousResetKeyRef.current = nextResetKey
reset(initialValues)
}, [date, initialValues, isEditing, reset])
}, [date, initialEvent?.id, initialValues, isEditing, reset])

useEffect(() => {
if (isEditing || !onDraftChange) return
Expand Down
8 changes: 6 additions & 2 deletions src/shared/hooks/form/useTodoFormFields.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { yupResolver } from '@hookform/resolvers/yup'
import { useEffect, useMemo } from 'react'
import { useEffect, useMemo, useRef } from 'react'
import { type Control, type Resolver, useForm, type UseFormReturn, useWatch } from 'react-hook-form'

import { addTodoSchema } from '@/shared/schemas/todo'
Expand Down Expand Up @@ -73,6 +73,7 @@ export const useTodoFormFields = ({
() => buildTodoDefaultValues({ date, initialEvent, draftValues }),
[date, draftValues, initialEvent],
)
const previousResetKeyRef = useRef(`${date}::${String(initialEvent?.id ?? 'new')}`)
const formMethods = useForm<TodoEditorFormValues>({
resolver,
defaultValues: initialValues,
Expand All @@ -98,8 +99,11 @@ export const useTodoFormFields = ({

useEffect(() => {
if (isEditing) return
const nextResetKey = `${date}::${String(initialEvent?.id ?? 'new')}`
if (previousResetKeyRef.current === nextResetKey) return
previousResetKeyRef.current = nextResetKey
reset(initialValues)
}, [date, initialValues, isEditing, reset])
}, [date, initialEvent?.id, initialValues, isEditing, reset])

useEffect(() => {
if (isEditing || !onDraftChange) return
Expand Down
132 changes: 96 additions & 36 deletions src/shared/ui/Modals/ItemEditorModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,26 @@ import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } fro
import { createPortal } from 'react-dom'

import type { CalendarEvent } from '@/shared/types/calendar/types'
import type { RepeatConfigSchema } from '@/shared/types/event/event'
import type { ItemEditorDraft } from '@/shared/types/modal/itemEditor'
import { defaultRepeatConfig } from '@/shared/types/recurrence/repeat'
import ScheduleEditorForm from '@/shared/ui/Modals/ScheduleEditor/ScheduleEditorForm'
import TodoEditorForm from '@/shared/ui/Modals/TodoEditor/TodoEditorForm'
import { buildDefaultItemEditorDraft } from '@/shared/utils'

import EditorModalLayout from './EditorModalLayout'
import * as S from './ItemEditorModal.style'

type ItemType = 'todo' | 'schedule'

const pad2 = (value: number) => String(value).padStart(2, '0')

const formatTimeFromDate = (value: Date) => `${pad2(value.getHours())}:${pad2(value.getMinutes())}`

const getDefaultDraft = (
date: string,
initialType: ItemType,
initialEvent?: CalendarEvent | null,
): ItemEditorDraft => {
const baseStart = initialEvent?.start ? new Date(initialEvent.start) : new Date(date)
const baseEnd =
initialEvent?.end && new Date(initialEvent.end).getTime() !== baseStart.getTime()
? new Date(initialEvent.end)
: new Date(baseStart.getTime() + 60 * 60 * 1000)

return {
title:
initialType === 'schedule' && initialEvent?.title === '์ƒˆ ์ผ์ •'
? ''
: (initialEvent?.title ?? ''),
description: initialEvent?.content ?? '',
startDate: baseStart,
endDate: baseEnd,
startTime: formatTimeFromDate(baseStart),
endTime: initialType === 'todo' ? formatTimeFromDate(baseStart) : formatTimeFromDate(baseEnd),
isAllday: initialEvent?.isAllDay ?? false,
eventColor: initialEvent?.color ?? (initialType === 'todo' ? 'GRAY' : 'BLUE'),
repeatConfig: defaultRepeatConfig as RepeatConfigSchema,
location: initialEvent?.location ?? '',
address: initialEvent?.address ?? null,
const buildDateTime = (fallbackDate: string, dateValue?: Date | null, timeValue?: string) => {
const nextDate = dateValue ? new Date(dateValue) : new Date(fallbackDate)
if (!timeValue) {
nextDate.setHours(0, 0, 0, 0)
return nextDate
}

const [hour, minute] = timeValue.split(':').map((value) => Number.parseInt(value, 10))
nextDate.setHours(Number.isNaN(hour) ? 0 : hour, Number.isNaN(minute) ? 0 : minute, 0, 0)
return nextDate
}

type ItemEditorModalProps = {
Expand All @@ -65,6 +43,8 @@ type ItemEditorModalProps = {
allDay: boolean,
occurrenceDate?: CalendarEvent['occurrenceDate'],
) => void
draftValues?: ItemEditorDraft | null
onDraftChange?: (draft: ItemEditorDraft | null) => void
}

const ItemEditorModal = ({
Expand All @@ -80,18 +60,37 @@ const ItemEditorModal = ({
onEventTitleConfirm,
onEventTypeChange,
onEventTimingChange,
draftValues: externalDraftValues,
onDraftChange: onExternalDraftChange,
}: ItemEditorModalProps) => {
const [activeType, setActiveType] = useState<ItemType>(initialType)
const [draftValues, setDraftValues] = useState<ItemEditorDraft | null>(() =>
isEditing ? null : getDefaultDraft(date, initialType, initialEvent),
const [internalDraftValues, setInternalDraftValues] = useState<ItemEditorDraft | null>(() =>
isEditing ? null : buildDefaultItemEditorDraft(date, initialType, initialEvent),
)
const draftValues = externalDraftValues ?? internalDraftValues
const draftValuesRef = useRef(draftValues)
const setDraftValues = useCallback(
(draft: ItemEditorDraft | null) => {
if (onExternalDraftChange) {
onExternalDraftChange(draft)
return
}
setInternalDraftValues(draft)
},
[onExternalDraftChange],
)
const [footerChildren, setFooterChildren] = useState<ReactNode | null>(null)
const [deleteHandler, setDeleteHandler] = useState<() => void>(() => () => undefined)
const [closeGuard, setCloseGuard] = useState<null | (() => boolean)>(null)
const [modalWrapperElement, setModalWrapperElement] = useState<HTMLDivElement | null>(null)
const modalWrapperRef = useRef<HTMLDivElement | null>(null)
const previousActiveTypeRef = useRef(activeType)
const noopDeleteHandler = useCallback(() => undefined, [])

useEffect(() => {
draftValuesRef.current = draftValues
}, [draftValues])

const registerDeleteHandler = useCallback(
(handler?: (() => void) | null) => {
setDeleteHandler(() => handler ?? noopDeleteHandler)
Expand Down Expand Up @@ -120,14 +119,75 @@ const ItemEditorModal = ({
}, [initialType])

useEffect(() => {
setDraftValues(isEditing ? null : getDefaultDraft(date, initialType, initialEvent))
}, [date, initialEvent, initialType, isEditing])
if (externalDraftValues !== undefined) return
setInternalDraftValues(
isEditing ? null : buildDefaultItemEditorDraft(date, initialType, initialEvent),
)
}, [date, externalDraftValues, initialEvent, initialType, isEditing])

useEffect(() => {
if (eventId == null || eventId === 0) return
onEventTypeChange?.(eventId, activeType)
}, [activeType, eventId, onEventTypeChange])

useEffect(() => {
if (!showTypeTabs) return
if (previousActiveTypeRef.current === activeType) return
previousActiveTypeRef.current = activeType
if (eventId == null || eventId === 0) return
if (!onEventTimingChange) return

const latestDraftValues = draftValuesRef.current
const startDate =
latestDraftValues?.startDate ?? (initialEvent?.start ? new Date(initialEvent.start) : null)
const endDate =
latestDraftValues?.endDate ?? (initialEvent?.end ? new Date(initialEvent.end) : startDate)
const isAllDay = latestDraftValues?.isAllday ?? initialEvent?.isAllDay ?? false
const occurrenceDate = initialEvent?.occurrenceDate

if (activeType === 'todo') {
if (isAllDay) {
const start = new Date(startDate ?? new Date(date))
start.setHours(0, 0, 0, 0)
const end = new Date(start)
end.setHours(23, 59, 59, 999)
onEventTimingChange(eventId, start, end, true, occurrenceDate)
return
}

const point = buildDateTime(
date,
startDate,
latestDraftValues?.endTime ?? latestDraftValues?.startTime,
)
onEventTimingChange(eventId, point, point, false, occurrenceDate)
return
}

if (isAllDay) {
const start = new Date(startDate ?? new Date(date))
start.setHours(0, 0, 0, 0)
const end = new Date(endDate ?? start)
end.setHours(23, 59, 59, 999)
onEventTimingChange(eventId, start, end, true, occurrenceDate)
return
}

const start = buildDateTime(date, startDate, latestDraftValues?.startTime)
const end = buildDateTime(date, endDate ?? startDate, latestDraftValues?.endTime)
onEventTimingChange(eventId, start, end, false, occurrenceDate)
}, [
activeType,
date,
eventId,
initialEvent?.end,
initialEvent?.isAllDay,
initialEvent?.occurrenceDate,
initialEvent?.start,
onEventTimingChange,
showTypeTabs,
])

const handleSubmit = useCallback(() => {
const submitFormId = activeType === 'todo' ? 'add-todo-form' : 'add-schedule-form'
const scopedTarget = modalWrapperRef.current?.querySelector(
Expand Down
Loading
Loading