From 4a3896a313d0e72f387d674c811fbdcc6749c01b Mon Sep 17 00:00:00 2001 From: geonhee-min Date: Mon, 15 Dec 2025 17:34:52 +0900 Subject: [PATCH] =?UTF-8?q?issue=20#60=20-=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 8 +- package.json | 2 +- src/components/ui/sonner.tsx | 2 +- src/const/schedule/SchedulePopoverMode.ts | 1 + src/data/response/index.ts | 3 +- .../schedule/ScheduleDetailResponse.ts | 24 ++++ src/layouts/Layout.tsx | 1 + src/network/AccountNetwork.ts | 20 +-- src/network/BaseNetwork.ts | 43 +++--- src/network/ScheduleNetwork.ts | 13 +- src/ui/component/calendar/CustomCalendar.tsx | 127 ++++++++++++++---- src/ui/component/calendar/CustomCalendarCN.ts | 2 +- src/ui/component/schedule/SchedulePopover.tsx | 12 +- .../schedule/content/ContentProps.ts | 6 +- .../content/ScheduleCreateContent.tsx | 50 ++++--- .../content/ScheduleDetailContent.tsx | 112 ++++++++++++++- .../schedule/tile/ScheduleListTile.tsx | 7 +- 17 files changed, 334 insertions(+), 99 deletions(-) create mode 100644 src/const/schedule/SchedulePopoverMode.ts create mode 100644 src/data/response/schedule/ScheduleDetailResponse.ts diff --git a/package-lock.json b/package-lock.json index d5d1087..0c91d77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "scheduler", "version": "0.0.0", "dependencies": { - "@baekyangdan/core-utils": "^1.0.4", + "@baekyangdan/core-utils": "^1.0.9", "@diceui/mention": "^0.8.0", "@hookform/resolvers": "^5.2.2", "@radix-ui/react-accordion": "^1.2.12", @@ -373,9 +373,9 @@ } }, "node_modules/@baekyangdan/core-utils": { - "version": "1.0.4", - "resolved": "https://gitea.bkdhome.p-e.kr/api/packages/baekyangdan/npm/%40baekyangdan%2Fcore-utils/-/1.0.4/core-utils-1.0.4.tgz", - "integrity": "sha512-0++RXd6eg3IkS5xygRzv19p174wPtSh/tQYSGjKtlKP5GZnhRO8NYNDT1IciornLmFfVHAnuC/cnmwUqcFPX4A==", + "version": "1.0.9", + "resolved": "https://gitea.bkdhome.p-e.kr/api/packages/baekyangdan/npm/%40baekyangdan%2Fcore-utils/-/1.0.9/core-utils-1.0.9.tgz", + "integrity": "sha512-zeXQPXJlwpO2/PzmQJQrXP9A6/maZmWWISmEuW82R42fkgXwaxavT/xlI1MsYLg1tqibwNBLSgnjXd0H9vR0KQ==", "license": "ISC", "dependencies": { "date-fns": "^4.1.0", diff --git a/package.json b/package.json index 4426002..a0b62bb 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "preview": "vite preview" }, "dependencies": { - "@baekyangdan/core-utils": "^1.0.4", + "@baekyangdan/core-utils": "^1.0.9", "@diceui/mention": "^0.8.0", "@hookform/resolvers": "^5.2.2", "@radix-ui/react-accordion": "^1.2.12", diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx index 9f46e06..4cc0870 100644 --- a/src/components/ui/sonner.tsx +++ b/src/components/ui/sonner.tsx @@ -14,7 +14,7 @@ const Toaster = ({ ...props }: ToasterProps) => { return ( , info: , diff --git a/src/const/schedule/SchedulePopoverMode.ts b/src/const/schedule/SchedulePopoverMode.ts new file mode 100644 index 0000000..286aefe --- /dev/null +++ b/src/const/schedule/SchedulePopoverMode.ts @@ -0,0 +1 @@ +export type SchedulePopoverMode = 'list' | 'create' | 'detail' | 'update'; \ No newline at end of file diff --git a/src/data/response/index.ts b/src/data/response/index.ts index 18c87a4..a54a508 100644 --- a/src/data/response/index.ts +++ b/src/data/response/index.ts @@ -9,4 +9,5 @@ export * from './account/ResetPasswordResponse'; export * from './schedule/CreateScheduleResponse'; export * from './schedule/UpdateScheduleResponse'; -export * from './schedule/ScheduleListResponse'; \ No newline at end of file +export * from './schedule/ScheduleListResponse'; +export * from './schedule/ScheduleDetailResponse'; \ No newline at end of file diff --git a/src/data/response/schedule/ScheduleDetailResponse.ts b/src/data/response/schedule/ScheduleDetailResponse.ts new file mode 100644 index 0000000..cef19fd --- /dev/null +++ b/src/data/response/schedule/ScheduleDetailResponse.ts @@ -0,0 +1,24 @@ +import type { ScheduleStatus } from "@/const/schedule/ScheduleStatus"; +import { BaseResponse } from "../BaseResponse"; +import type { ScheduleType } from "@/const/schedule/ScheduleType"; + +export class ScheduleDetailData { + id!: string; + name!: string; + startDate!: string; + endDate!: string; + status!: ScheduleStatus; + content?: string | null; + type!: ScheduleType; + createdAt!: string; + owner!: string; + style!: string; + startTime!: string; + endTime!: string; + dayList?: string | null; + participantList?: string[]; +} + +export class ScheduleDetailResponse extends BaseResponse { + data?: ScheduleDetailData; +} \ No newline at end of file diff --git a/src/layouts/Layout.tsx b/src/layouts/Layout.tsx index be7a761..4adcc7f 100644 --- a/src/layouts/Layout.tsx +++ b/src/layouts/Layout.tsx @@ -31,6 +31,7 @@ export default function Layout() { <> , error: , diff --git a/src/network/AccountNetwork.ts b/src/network/AccountNetwork.ts index d5d37a6..631d8fe 100644 --- a/src/network/AccountNetwork.ts +++ b/src/network/AccountNetwork.ts @@ -19,15 +19,17 @@ import { ResetPasswordResponse } from "@/data/response"; import { BaseNetwork } from "./BaseNetwork"; +import { HttpApiUrl } from "@baekyangdan/core-utils"; +const AccountApi = HttpApiUrl.Account; export class AccountNetwork extends BaseNetwork { - private baseUrl = "/account"; + private baseUrl = AccountApi.base; async checkDuplication(data: CheckDuplicationRequest) { const { type, value } = data; return await this.get( - `${this.baseUrl}/check-duplication?type=${type}&value=${value}` + `${this.baseUrl}${AccountApi.checkDuplication}?type=${type}&value=${value}` , { authPass: true } @@ -36,7 +38,7 @@ export class AccountNetwork extends BaseNetwork { async sendVerificationCode(data: SendVerificationCodeRequest) { return await this.post( - this.baseUrl + "/send-email-verification-code" + `${this.baseUrl}${AccountApi.sendEmailVerificationCode}` , data , { authPass: true @@ -46,7 +48,7 @@ export class AccountNetwork extends BaseNetwork { async verifyCode(data: VerifyCodeRequest) { return await this.post( - this.baseUrl + "/verify-email-verification-code" + `${this.baseUrl}${AccountApi.verifyEmailVerificationCode}` , data , { authPass: true @@ -56,7 +58,7 @@ export class AccountNetwork extends BaseNetwork { async signup(data: SignupRequest) { return await this.post( - this.baseUrl + "/signup" + `${this.baseUrl}${AccountApi.signup}` , data , { authPass: true @@ -66,7 +68,7 @@ export class AccountNetwork extends BaseNetwork { async login(data: LoginRequest) { return await this.post( - this.baseUrl + "/login" + `${this.baseUrl}${AccountApi.login}` , data , { authPass: true @@ -76,21 +78,21 @@ export class AccountNetwork extends BaseNetwork { async sendResetPasswordCode(data: SendResetPasswordCodeRequest) { return await this.post( - this.baseUrl + '/send-reset-password-code', + `${this.baseUrl}${AccountApi.sendResetPasswordCode}`, data ); } async verifyResetPasswordCode(data: VerifyResetPasswordCodeRequest) { return await this.post( - this.baseUrl + '/verify-reset-password-code', + `${this.baseUrl}${AccountApi.verifyResetPasswordCode}`, data ); } async resetPassword(data: ResetPasswordRequest) { return await this.post( - this.baseUrl + '/reset-password', + `${this.baseUrl}${AccountApi.resetPassword}`, data ); } diff --git a/src/network/BaseNetwork.ts b/src/network/BaseNetwork.ts index 1a812df..580d766 100644 --- a/src/network/BaseNetwork.ts +++ b/src/network/BaseNetwork.ts @@ -9,7 +9,7 @@ import type { import { useAuthStore } from '@/store/authStore'; import { RefreshAccessTokenResponse } from '@/data/response/account/RefreshAccessTokenResponse'; import type { AuthData } from '@/data/AuthData'; -import { UnauthorizedCode, UnauthorizedMessage } from '@baekyangdan/core-utils'; +import { HttpApiUrl, UnauthorizedCode, UnauthorizedMessage } from '@baekyangdan/core-utils'; export class BaseNetwork { protected instance: AxiosInstance; @@ -92,14 +92,16 @@ export class BaseNetwork { } private async handleRefreshToken(originalRequest: AxiosRequestConfig) { - - const authData = useAuthStore.getState().authData; - - if (!authData) { - useAuthStore.getState().logout(); - return Promise.reject(UnauthorizedMessage.INVALID_TOKEN); + const autoLogin = localStorage.getItem('autoLogin') === 'true'; + if (autoLogin) { + const authData = useAuthStore.getState().authData; + + if (!authData) { + useAuthStore.getState().logout(); + return Promise.reject(UnauthorizedMessage.INVALID_TOKEN); + } } - + if (this.isRefreshing) { return new Promise((resolve) => { this.refreshQueue.push((newToken: string) => { @@ -148,22 +150,25 @@ export class BaseNetwork { } public async refreshToken() { - const storedAuth = localStorage.getItem('auth-storage'); + const autoLogin = localStorage.getItem('autoLogin') === 'true'; + if (autoLogin) { + const storedAuth = localStorage.getItem('auth-storage'); - if (!storedAuth) { - localStorage.setItem('autoLogin', 'false'); - throw new Error; - } - - const authData: AuthData = JSON.parse(storedAuth).state; + if (!storedAuth) { + localStorage.setItem('autoLogin', 'false'); + throw new Error; + } + + const authData: AuthData = JSON.parse(storedAuth).state; - if (!authData) { - localStorage.setItem('autoLogin', 'false'); - throw new Error; + if (!authData) { + localStorage.setItem('autoLogin', 'false'); + throw new Error; + } } const result = await this.get( - '/account/refresh-access-token', + `${HttpApiUrl.Account.base}${HttpApiUrl.Account.refreshAccessToken}`, { withCredentials: true } diff --git a/src/network/ScheduleNetwork.ts b/src/network/ScheduleNetwork.ts index 989287e..453a5fe 100644 --- a/src/network/ScheduleNetwork.ts +++ b/src/network/ScheduleNetwork.ts @@ -7,11 +7,14 @@ import { } from '@/data/request'; import { CreateScheduleResponse, + ScheduleDetailResponse, ScheduleListResponse } from "@/data/response"; +import { HttpApiUrl } from "@baekyangdan/core-utils"; +const ScheduleApi = HttpApiUrl.Schedule; export class ScheduleNetwork extends BaseNetwork { - private baseUrl = "/schedule"; + private baseUrl = ScheduleApi.base; async getList(data: ScheduleListRequest) { return await this.post( @@ -21,28 +24,28 @@ export class ScheduleNetwork extends BaseNetwork { } async getDetail(id: string) { - return await this.get( + return await this.get( `${this.baseUrl}/${id}` ); } async create(data: CreateScheduleRequest) { return await this.post( - `${this.baseUrl}/create`, + `${this.baseUrl}${ScheduleApi.create}`, data ); } async update(data: UpdateScheduleRequest) { return await this.post( - `${this.baseUrl}/update`, + `${this.baseUrl}${ScheduleApi.update}`, data ); } async del(data: DeleteScheduleRequest) { return await this.post( - `${this.baseUrl}/delete`, + `${this.baseUrl}${ScheduleApi.delete}`, data ); } diff --git a/src/ui/component/calendar/CustomCalendar.tsx b/src/ui/component/calendar/CustomCalendar.tsx index f4b7c36..4c244d8 100644 --- a/src/ui/component/calendar/CustomCalendar.tsx +++ b/src/ui/component/calendar/CustomCalendar.tsx @@ -9,6 +9,7 @@ import { ScheduleListData } from "@/data/response"; import { CustomCalendarCN } from "./CustomCalendarCN"; import { toast } from "sonner"; import { Converter } from "@/util/Converter"; +import type { SchedulePopoverMode } from "@/const/schedule/SchedulePopoverMode"; interface CustomCalendarProps { data?: any; @@ -27,13 +28,16 @@ const DATE_FORMAT_ARIA = 'EEEE, MMMM do, yyyy'; const DATE_FORMAT_KEY = 'yyyyMMdd'; export const CustomCalendar = ({ data }: CustomCalendarProps) => { + const [isLoading, setIsLoading] = useState(false); + const [refetchTrigger, setRefetchTrigger] = useState(0); const [weekCount, setWeekCount] = useState(5); const [selectedDate, setSelectedDate] = useState(undefined); const [popoverOpen, setPopoverOpen] = useState(false); const [popoverSide, setPopoverSide] = useState<'right' | 'left'>('right'); const [popoverAlign, setPopoverAlign] = useState<'start' | 'end'>('end'); + const [popoverMode, setPopoverMode] = useState('list'); + const [popoverDetailId, setPopoverDetailId] = useState(''); const [month, setMonth] = useState(new Date()); - const [currentDataMonth, setCurrentDataMonth] = useState(month); const [windowSize, setWindowSize] = useState({ width: window.innerWidth, height: window.innerHeight }); const [maxVisibleEvents, setMaxVisibleEvents] = useState(3); const [overflowTrackIndex, setOverflowTrackIndex] = useState(maxVisibleEvents + 1); @@ -41,7 +45,8 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { const [barPositions, setBarPositions] = useState>([]); const scheduleNetwork = new ScheduleNetwork(); const containerRef = useRef(null); - + const cellInfoMapRef = useRef>(new Map()); + const updateWeekCount = () => { if (containerRef === null) return; if (!containerRef.current) return; @@ -73,11 +78,12 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { } }, []); - + // 화면 상의 달이 바뀌면 req 하는 로직 useLayoutEffect(() => { updateWeekCount(); const reqList = async () => { + setIsLoading(true); const requestedMonth = month; const monthStart = startOfMonth(month); @@ -96,12 +102,13 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { if (result.data.success) { if (result.data.data) { if (isSameMonth(requestedMonth, month)) { + // setCurrentDataMonth(month); setScheduleList(result.data.data!); - setCurrentDataMonth(month); } - setScheduleList(result.data.data); + // setScheduleList(result.data.data); } } + setIsLoading(false); } requestAnimationFrame(() => { @@ -118,19 +125,25 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { ) updateWeekCount(); }); - }, [month]); + }, [month, refetchTrigger]); + const refetchList = () => { + setRefetchTrigger(prev => prev + 1); + } + + // 이벤트 bar 그리는 로직 useLayoutEffect(() => { - if (!isSameMonth(month, currentDataMonth)) { - setBarPositions([]); - return; - } + // if (!isSameMonth(month, currentDataMonth)) { + // setBarPositions([]); + // return; + // } if (!containerRef.current || scheduleList.length === 0) { - setBarPositions([]); + // setBarPositions([]); return; } - + + // setBarPositions([]); const containerRect = containerRef.current.getBoundingClientRect(); const dayCells = containerRef.current.querySelectorAll('.rdp-day button'); @@ -138,15 +151,20 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { dayCells.forEach((cell) => { const dayButton = cell as HTMLButtonElement; - const ariaLabel = dayButton.getAttribute('aria-label'); - + let ariaLabel = dayButton.getAttribute('aria-label'); if (ariaLabel) { try { + if (ariaLabel.startsWith('Today, ')) { + ariaLabel = ariaLabel.replace('Today, ', ''); + } + if (ariaLabel.endsWith(', selected')) { + ariaLabel = ariaLabel.replace(', selected', ''); + } const parsedDate = parse(ariaLabel, DATE_FORMAT_ARIA, new Date()); if (!isNaN(parsedDate.getTime())) { const dateKey = format(parsedDate, DATE_FORMAT_KEY); - + console.log(dateKey); cellInfoMap.set(dateKey, { cell: dayButton, rect: dayButton.getBoundingClientRect() @@ -156,6 +174,7 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { } catch (e) {} } }); + cellInfoMapRef.current = cellInfoMap; const scheduleListWithTrack: (ScheduleListData & { trackIndex: number })[] = []; const occupiedTrackList = new Map(); @@ -292,7 +311,7 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { } }); setBarPositions([...regularPositions, ...overflowPositions]); - }, [scheduleList, month, currentDataMonth, windowSize]); + }, [scheduleList, month, windowSize]); const createAndPushPosition = ( positions: EventBarPosition[], @@ -334,6 +353,7 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { } const handleMonthChange = async (month: Date) => { + if (isLoading) return; setMonth(month); } @@ -351,6 +371,8 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { setPopoverOpen(false); setTimeout(() => { setSelectedDate(undefined); + setPopoverDetailId(''); + setPopoverMode('list'); }, 150); return; } @@ -385,6 +407,51 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { } } + const findDateFromClick = ( + clientX: number, + clientY: number + ): string | null => { + if (!cellInfoMapRef.current) { + return null; + } + const cellInfoMap = cellInfoMapRef.current; + + for (const [dateKey, info] of cellInfoMap.entries()) { + const rect = info.rect; + + if ( + clientX >= rect.left + && clientX < rect.right + && clientY >= rect.top + && clientY < rect.bottom + ) { + return dateKey; + } + } + + return null; + } + + const handleEventBarClick = (e: React.MouseEvent, bar: EventBarPosition) => { + const clientX = e.clientX; + const clientY = e.clientY; + const eventId = bar.id; + + const dateKey = findDateFromClick(clientX, clientY); + + if (dateKey && !selectedDate) { + const clickedDate = parse(dateKey, DATE_FORMAT_KEY, new Date()); + if (!(eventId.includes('overflow'))) { + setPopoverMode('detail'); + setPopoverDetailId(eventId); + } + handleDaySelect(clickedDate); + } else { + handleDaySelect(undefined); + + } + } + return (
{ /> { barPositions.map(pos => ( -
- {pos.name} -
+
) => handleEventBarClick(e, pos)} + key={pos.segmentId} + id={pos.segmentId} + className={cn( + `flex flex-row justify-start items-center absolute select-none`, + "py-0.5 px-2 rounded-sm text-xs text-white overflow-hidden" + )} + style={{...pos.positionStyle, backgroundColor: pos.style}} + > + {pos.name} +
)) } +
) diff --git a/src/ui/component/calendar/CustomCalendarCN.ts b/src/ui/component/calendar/CustomCalendarCN.ts index 475905b..6447897 100644 --- a/src/ui/component/calendar/CustomCalendarCN.ts +++ b/src/ui/component/calendar/CustomCalendarCN.ts @@ -10,7 +10,7 @@ export const CustomCalendarCN = { ), nav: cn( defaultCN.nav, - "flex w-full item-center gap-1 justify-around absolute top-0 inset-x-0" + "flex w-full item-center gap-1 justify-center gap-30 absolute top-0 inset-x-0" ), month: cn( defaultCN.month, diff --git a/src/ui/component/schedule/SchedulePopover.tsx b/src/ui/component/schedule/SchedulePopover.tsx index cf0b385..4e89bf4 100644 --- a/src/ui/component/schedule/SchedulePopover.tsx +++ b/src/ui/component/schedule/SchedulePopover.tsx @@ -3,17 +3,22 @@ import { useEffect, useState } from 'react'; import { ScheduleCreateContent } from './content/ScheduleCreateContent'; import { ScheduleListContent } from './content/ScheduleListContent'; import { ScheduleDetailContent } from './content/ScheduleDetailContent'; +import type { SchedulePopoverMode } from '@/const/schedule/SchedulePopoverMode'; interface ScheduleSheetProps { date: Date | undefined; open: boolean; popoverSide: 'left' | 'right'; popoverAlign: 'start' | 'end'; + mode: SchedulePopoverMode; + setMode: (mode: SchedulePopoverMode) => void; + detailId: string; + setDetailId: (id: string) => void; + onScheduleCreated: () => void; } -export const SchedulePopover = ({ date, open, popoverSide, popoverAlign }: ScheduleSheetProps) => { - const [mode, setMode] = useState<'list' | 'create' | 'detail' | 'update'>('list'); - const [detailId, setDetailId] = useState(''); +export const SchedulePopover = ({ date, open, mode, setMode, detailId, setDetailId, popoverSide, popoverAlign, onScheduleCreated }: ScheduleSheetProps) => { + useEffect(() => { if (!open) { @@ -57,6 +62,7 @@ export const SchedulePopover = ({ date, open, popoverSide, popoverAlign }: Sched popoverAlign={popoverAlign} popoverSide={popoverSide} open={open} + refetchList={onScheduleCreated} /> case 'detail': return void; + setMode: (mode: SchedulePopoverMode) => void; } export interface ScheduleCreateContentProps extends BaseProps { - + refetchList: () => void; } export interface ScheduleListContentProps extends BaseProps { diff --git a/src/ui/component/schedule/content/ScheduleCreateContent.tsx b/src/ui/component/schedule/content/ScheduleCreateContent.tsx index 2d47386..64bf240 100644 --- a/src/ui/component/schedule/content/ScheduleCreateContent.tsx +++ b/src/ui/component/schedule/content/ScheduleCreateContent.tsx @@ -28,7 +28,7 @@ import { TypePickPopover } from '../popover/TypePickPopover'; import type { ScheduleCreateContentProps } from './ContentProps'; import { Converter } from '@/util/Converter'; -export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign }: ScheduleCreateContentProps) => { +export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign, refetchList }: ScheduleCreateContentProps) => { const [colorPopoverOpen, setColorPopoverOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const { getPaletteByKey } = usePalette(); @@ -74,6 +74,7 @@ export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign const reqCreate = async () => { if (isLoading) return; + const data = { name, startDate: Converter.dateToUTC9(startDate), @@ -87,28 +88,35 @@ export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign style }; - const createPromise = scheduleNetwork.create(data); setIsLoading(true); - toast.promise( - createPromise, - { - loading: '일정 생성 중입니다', - success: (res) => { - setIsLoading(false); - if (res.data.success) { - setMode('list'); - return '일정이 생성되었습니다' - } else { - throw new Error(res.data.error); - } - }, - error: (err: Error) => { - setIsLoading(false); - return err.message || "에러 발생"; - } + + const createPromise = scheduleNetwork.create(data); + + toast.promise(createPromise, { + loading: '일정 생성 중입니다', + }); + + try { + const res = await createPromise; + + if (!res.data.success) { + throw new Error(res.data.error); } - ) - } + + toast.success('일정이 생성되었습니다'); + + // ✅ 기존 동작 그대로 + setMode('list'); + refetchList(); + } catch (err) { + const message = + err instanceof Error ? err.message : '에러 발생'; + + toast.error(message); + } finally { + setIsLoading(false); + } + }; const selectColor = (color: ColorPaletteType) => { createScheduleForm.setValue('style', color.style); diff --git a/src/ui/component/schedule/content/ScheduleDetailContent.tsx b/src/ui/component/schedule/content/ScheduleDetailContent.tsx index 6c58a6c..7c59f5f 100644 --- a/src/ui/component/schedule/content/ScheduleDetailContent.tsx +++ b/src/ui/component/schedule/content/ScheduleDetailContent.tsx @@ -1,11 +1,117 @@ +import { ScheduleNetwork } from '@/network/ScheduleNetwork'; import type { ScheduleDetailContentProps } from './ContentProps'; +import { useEffect, useState } from 'react'; +import { ArrowLeft, ChevronUp, Clock } from 'lucide-react'; +import type { ScheduleDetailData } from '@/data/response'; +import { cn } from '@/lib/utils'; +import { ScrollArea } from '@/components/ui/scroll-area'; + +export const ScheduleDetailContent = ({ setMode, popoverSide, popoverAlign, id }: ScheduleDetailContentProps) => { + const scheduleNetwork = new ScheduleNetwork(); + const [data, setData] = useState(); + const [commentFold, setCommentFold] = useState(true); + + useEffect(() => { + if (!id || id.trim().length === 0) { + return; + } + reqDetail(); + }, [id]); + + const reqDetail = async () => { + const result = await scheduleNetwork.getDetail(id); + if (result.data.success && result.data) { + setData(result.data.data!); + } + } + + const moveToList = () => { + setMode('list'); + } -export const ScheduleDetailContent = ({ date, setMode, popoverSide, popoverAlign, id }: ScheduleDetailContentProps) => { return (
- {id} +
+
+ +
+ {data?.name} +
+
+
+ 생성일 : {data?.createdAt} +
+ + + {data?.content && data?.content} + + +
+
+
+ + 댓글 [12] + + +
setCommentFold(prev => !prev)} + > + + { commentFold ? "열기" : "닫기" } +
+
+ +
+ a +
+ b +
+ c +
+ d +
+
+
) } \ No newline at end of file diff --git a/src/ui/component/schedule/tile/ScheduleListTile.tsx b/src/ui/component/schedule/tile/ScheduleListTile.tsx index fea1c29..217508c 100644 --- a/src/ui/component/schedule/tile/ScheduleListTile.tsx +++ b/src/ui/component/schedule/tile/ScheduleListTile.tsx @@ -1,9 +1,10 @@ +import type { SchedulePopoverMode } from "@/const/schedule/SchedulePopoverMode"; import { ScheduleTypeLabel } from "@/const/schedule/ScheduleType"; import { ScheduleListData } from "@/data/response"; import { Converter } from "@/util/Converter"; interface ScheduleListTileProps { - setMode: (mode: 'list' | 'create' | 'detail' | 'update') => void; + setMode: (mode: SchedulePopoverMode) => void; data: ScheduleListData; onClick: (id: string) => void; } @@ -31,12 +32,12 @@ export const ScheduleListTile = ({ setMode, data, onClick }: ScheduleListTilePro >
{data.name}
-
+
{formatter(data.startDate)} - {formatter(data.endDate)}