diff --git a/src/app/App.tsx b/src/app/App.tsx index 58d12d1..aa33fd7 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -8,6 +8,7 @@ import CalendarPage from "@/pages/CalendarPage"; import SchedulePage from "@/pages/SchedulePage"; import ScheduleEditPage from "@/pages/ScheduleEditPage"; import CreateTripPage from "@/pages/CreateTripPage"; +import EditTripPage from "@/pages/EditTripPage"; export default function App() { return ( @@ -20,8 +21,9 @@ export default function App() { } /> } /> } /> - } /> - } /> + } /> + } /> + } /> diff --git a/src/features/member/api/member.api.ts b/src/features/member/api/member.api.ts index dba4f5b..9009aaa 100644 --- a/src/features/member/api/member.api.ts +++ b/src/features/member/api/member.api.ts @@ -2,7 +2,7 @@ import type { MemberInvite, Member } from "../types/member.type"; import { MOCK_MEMBERS } from "../mock/members.mock"; const USE_MOCK = import.meta.env.VITE_USE_MOCK === "true"; -const API_BASE = import.meta.env.VITE_API_BASE_URL || ""; +// const API_BASE = import.meta.env.VITE_API_BASE_URL || ""; export const inviteMembers = async ( tripId: number, diff --git a/src/features/schedule/components/ScheduleHeader.tsx b/src/features/schedule/components/ScheduleHeader.tsx index 3049f59..aee3dbc 100644 --- a/src/features/schedule/components/ScheduleHeader.tsx +++ b/src/features/schedule/components/ScheduleHeader.tsx @@ -1,9 +1,10 @@ -import { useNavigate } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import { ArrowLeft, MoreVertical } from "lucide-react"; import { useState, useRef, useEffect } from "react"; export default function ScheduleHeader({ tripId }: { tripId: number }) { const navigate = useNavigate(); + const { tripId } = useParams<{ tripId: string }>(); const [menuOpen, setMenuOpen] = useState(false); const menuRef = useRef(null); @@ -43,10 +44,10 @@ export default function ScheduleHeader({ tripId }: { tripId: number }) { {menuOpen && (
- ))} -
- - {mode === "date" ? ( - - ) : ( -
-
-
- {period === 0 - ? "당일 일정이에요." - : `${period}박 ${period + 1}일 일정`} -
-
- - - {period} - - -
-
-
- )} - - - - - } label="폴더 선택"> - updateField("folder", folderName)} - /> - - -
- - -
- - {showCalendar && ( - setShowCalendar(false)} - onApply={(start, end) => { - updateField("startDate", start); - updateField("endDate", end); - setShowCalendar(false); - }} - /> - )} - - ); -} diff --git a/src/features/trip/components/SectionField.tsx b/src/features/trip/components/TripForm/SectionField.tsx similarity index 100% rename from src/features/trip/components/SectionField.tsx rename to src/features/trip/components/TripForm/SectionField.tsx diff --git a/src/features/trip/components/TripForm/TripDatePickerModal.tsx b/src/features/trip/components/TripForm/TripDatePickerModal.tsx new file mode 100644 index 0000000..ed40ac8 --- /dev/null +++ b/src/features/trip/components/TripForm/TripDatePickerModal.tsx @@ -0,0 +1,180 @@ +import { useState, useEffect, useRef } from "react"; +import { + addMonths, + eachDayOfInterval, + endOfMonth, + format, + getDay, + isAfter, + isBefore, + isSameDay, + startOfMonth, +} from "date-fns"; +import { ko } from "date-fns/locale"; + +interface TripDatePickerModalProps { + onClose: () => void; + onApply: (start: Date, end: Date) => void; + mode?: "range" | "month"; +} + +export default function TripDatePickerModal({ + onClose, + onApply, + mode = "range", +}: TripDatePickerModalProps) { + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); + const [monthCount, setMonthCount] = useState(2); + const today = new Date(); + const scrollRef = useRef(null); + + const handleDateClick = (day: Date) => { + if (mode === "month") { + const start = startOfMonth(day); + const end = endOfMonth(day); + onApply(start, end); + onClose(); + return; + } + if (!startDate || (startDate && endDate)) { + setStartDate(day); + setEndDate(null); + } else if (startDate && !endDate) { + if (isBefore(day, startDate)) { + setEndDate(startDate); + setStartDate(day); + } else { + setEndDate(day); + } + } + }; + + const inRange = (day: Date) => { + if (!startDate || !endDate) return false; + return isAfter(day, startDate) && isBefore(day, endDate); + }; + + const months = Array.from({ length: monthCount }, (_, i) => addMonths(today, i)); + + useEffect(() => { + const el = scrollRef.current; + if (!el) return; + const onScroll = () => { + if (el.scrollTop + el.clientHeight >= el.scrollHeight - 50) { + setMonthCount((prev) => prev + 1); + } + }; + el.addEventListener("scroll", onScroll); + return () => el.removeEventListener("scroll", onScroll); + }, []); + + const nights = + startDate && endDate + ? Math.round( + (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24) + ) + : 0; + + return ( +
+
+

+ {mode === "month" ? "월 선택" : "여행 기간 선택"} +

+ +
+ {months.map((month) => { + const start = startOfMonth(month); + const end = endOfMonth(month); + const days = eachDayOfInterval({ start, end }); + const offset = getDay(start); + const paddedDays = [...Array(offset).fill(null), ...days]; + + return ( +
+

+ {format(month, "yyyy년 M월", { locale: ko })} +

+
+ {["일", "월", "화", "수", "목", "금", "토"].map((d) => ( +
{d}
+ ))} +
+ +
+ {paddedDays.map((day, idx) => { + if (!day) return
; + const isStart = startDate && isSameDay(day, startDate); + const isEnd = endDate && isSameDay(day, endDate); + const isInRange = inRange(day); + const isToday = isSameDay(day, today); + + return ( + + ); + })} +
+
+ ); + })} +
+ + {mode === "range" && ( + <> +
+ {startDate && endDate ? ( + <> + + {format(startDate, "yyyy. M. d.", { locale: ko })} ~{" "} + {format(endDate, "yyyy. M. d.", { locale: ko })} + +
+ {nights > 0 + ? `${nights}박 ${nights + 1}일 일정` + : "당일 일정"} +
+ + ) : ( + 기간을 선택하세요 + )} +
+ +
+ + +
+ + )} +
+
+ ); +} diff --git a/src/features/trip/components/TripForm/TripFolderSelect.tsx b/src/features/trip/components/TripForm/TripFolderSelect.tsx new file mode 100644 index 0000000..7529ae7 --- /dev/null +++ b/src/features/trip/components/TripForm/TripFolderSelect.tsx @@ -0,0 +1,16 @@ +import { Folder } from "lucide-react"; +import SectionField from "@/features/trip/components/TripForm/SectionField"; +import FolderSelect from "@/features/folder/components/FolderSelect"; + +interface Props { + selected: string; + onChange: (folderName: string) => void; +} + +export default function TripFolderSelect({ selected, onChange }: Props) { + return ( + } label="폴더 선택"> + + + ); +} diff --git a/src/features/trip/components/TripForm/TripForm.tsx b/src/features/trip/components/TripForm/TripForm.tsx new file mode 100644 index 0000000..9d40ffd --- /dev/null +++ b/src/features/trip/components/TripForm/TripForm.tsx @@ -0,0 +1,190 @@ +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +// hooks / api +import { useTripForm } from "@/features/trip/hooks/useTripForm"; +import { createTrip, updateTrip } from "@/features/trip/api/trip.api"; +import { useFolders } from "@/features/folder/hooks/useFolders"; + +// components +import TripTitleInput from "./TripTitleInput"; +import TripRegionInput from "./TripRegionInput"; +import TripPeriodSelector from "./TripPeriodSelector"; +import TripFolderSelect from "./TripFolderSelect"; +import TripMemberInvite from "./TripMemberInvite"; + +// types +import type { TripStatus} from "@/features/trip/types/trip.type"; +import type { TripResponseDto } from "@/features/trip/types/trip.dto"; +import type { + TripCreatePayload, + TripUpdatePayload, +} from "@/features/trip/types/trip.payload"; + +interface TripFormProps { + coverFile?: File | null; + editMode?: boolean; + initialTrip?: TripResponseDto | null; +} + +export default function TripForm({ + coverFile, + editMode = false, + initialTrip = null, +}: TripFormProps) { + const navigate = useNavigate(); + const { folders } = useFolders(); + const { form, updateField, resetForm, setForm } = useTripForm(); + + const [loading, setLoading] = useState(false); + const [mode, setMode] = useState<"date" | "period">("date"); + const [period, setPeriod] = useState(0); + + /* ---------------- util ---------------- */ + + const toISO = (d: Date | null) => (d ? d.toISOString() : null); + + const resolveFolderId = (folderName: string) => + folders.find((f) => f.name === folderName)?.id ?? null; + + const resolveFolderName = (folderId: number | null | undefined) => { + if (!folderId) return "선택 안함"; + const folder = folders.find((f) => f.id === folderId); + return folder?.name ?? "선택 안함"; + }; + + /* ---------------- edit init ---------------- */ + + useEffect(() => { + if (!editMode || !initialTrip) return; + + setForm({ + title: initialTrip.title, + region: initialTrip.region ?? "", + folder: resolveFolderName(initialTrip.folderId), + startDate: initialTrip.startDate + ? new Date(initialTrip.startDate) + : null, + endDate: initialTrip.endDate + ? new Date(initialTrip.endDate) + : null, + coverImage: null, + }); + }, [editMode, initialTrip, folders, setForm]); + + /* ---------------- payload ---------------- */ + + const buildPayload = (): TripCreatePayload | TripUpdatePayload => { + const isDateMode = mode === "date" && form.startDate && form.endDate; + + const days = isDateMode + ? Math.round( + (form.endDate!.getTime() - form.startDate!.getTime()) / + (1000 * 60 * 60 * 24) + ) + 1 + : period + 1; + + const base = { + title: form.title.trim(), + region: form.region.trim(), + startDate: isDateMode ? toISO(form.startDate) : null, + endDate: isDateMode ? toISO(form.endDate) : null, + days, + coverImage: coverFile ?? null, + status: "예정" as TripStatus, + destinations: [form.region], + participants: 1, + folderId: resolveFolderId(form.folder), + }; + + return editMode && initialTrip + ? { tripId: initialTrip.id, ...base } + : base; + }; + + /* ---------------- submit ---------------- */ + + const handleSubmit = async () => { + if (!form.title.trim()) return alert("여행 제목을 입력해주세요"); + if (!form.region.trim()) return alert("여행 지역을 입력해주세요"); + if (mode === "date" && (!form.startDate || !form.endDate)) + return alert("여행 기간을 선택해주세요"); + + setLoading(true); + try { + const payload = buildPayload(); + + editMode && initialTrip + ? await updateTrip(initialTrip.id, payload as TripUpdatePayload) + : await createTrip(payload as TripCreatePayload); + + alert(editMode ? "여행이 수정되었습니다" : "여행이 생성되었습니다"); + navigate("/folders"); + resetForm(); + setMode("date"); + setPeriod(0); + } finally { + setLoading(false); + } + }; + + /* ---------------- render ---------------- */ + + return ( +
+ updateField("title", v)} + /> + + updateField("region", v)} + /> + + updateField("startDate", d)} + onChangeEndDate={(d) => updateField("endDate", d)} + period={period} + setPeriod={setPeriod} +/> + + updateField("folder", f)} + /> + + + +
+ + + {!editMode && ( + + )} +
+
+ ); +} diff --git a/src/features/trip/components/TripForm/TripMemberInvite.tsx b/src/features/trip/components/TripForm/TripMemberInvite.tsx new file mode 100644 index 0000000..4bfe83c --- /dev/null +++ b/src/features/trip/components/TripForm/TripMemberInvite.tsx @@ -0,0 +1,5 @@ +import MemberInviteForm from "@/features/member/components/MemberInviteForm"; + +export default function TripMemberInvite() { + return ; +} diff --git a/src/features/trip/components/TripForm/TripPeriodSelector.tsx b/src/features/trip/components/TripForm/TripPeriodSelector.tsx new file mode 100644 index 0000000..7e4ee69 --- /dev/null +++ b/src/features/trip/components/TripForm/TripPeriodSelector.tsx @@ -0,0 +1,115 @@ +import { useState } from "react"; +import TripDatePickerModal from "./TripDatePickerModal"; +import type { Dispatch, SetStateAction } from "react"; + +interface Props { + mode: "date" | "period"; + setMode: (mode: "date" | "period") => void; + + startDate: Date | null; + endDate: Date | null; + + onChangeStartDate: (date: Date | null) => void; + onChangeEndDate: (date: Date | null) => void; + + period: number; + setPeriod: Dispatch>; +} + +export default function TripPeriodSelector({ + mode, + setMode, + startDate, + endDate, + onChangeStartDate, + onChangeEndDate, + period, + setPeriod, +}: Props) { + const [showCalendar, setShowCalendar] = useState(false); + + return ( +
+
+ {(["date", "period"] as const).map((type) => ( + + ))} +
+ + {mode === "date" ? ( + + ) : ( +
+
+
+ {period === 0 + ? "당일 일정이에요." + : `${period}박 ${period + 1}일 일정`} +
+
+ + + {period} + + +
+
+
+ )} + + {showCalendar && ( + setShowCalendar(false)} + onApply={(start, end) => { + onChangeStartDate(start); + onChangeEndDate(end); + setShowCalendar(false); + }} + /> + )} +
+ ); +} diff --git a/src/features/trip/components/TripForm/TripRegionInput.tsx b/src/features/trip/components/TripForm/TripRegionInput.tsx new file mode 100644 index 0000000..4f7e1a6 --- /dev/null +++ b/src/features/trip/components/TripForm/TripRegionInput.tsx @@ -0,0 +1,23 @@ +import { MapPin } from "lucide-react"; +import SectionField from "@/features/trip/components/TripForm/SectionField"; + +interface Props { + value: string; + onChange: (v: string) => void; +} + +export default function TripRegionInput({ value, onChange }: Props) { + return ( + } label="여행 지역"> + onChange(e.target.value)} + className="w-full border border-[var(--color-border)] rounded-lg px-4 py-3 + text-[var(--color-text-main)] placeholder-[var(--color-text-sub)] + focus:ring-2 focus:ring-[var(--color-primary)] outline-none" + /> + + ); +} diff --git a/src/features/trip/components/TripForm/TripTitleInput.tsx b/src/features/trip/components/TripForm/TripTitleInput.tsx new file mode 100644 index 0000000..512e492 --- /dev/null +++ b/src/features/trip/components/TripForm/TripTitleInput.tsx @@ -0,0 +1,23 @@ +import { Bookmark } from "lucide-react"; +import SectionField from "@/features/trip/components/TripForm/SectionField"; + +interface Props { + value: string; + onChange: (v: string) => void; +} + +export default function TripTitleInput({ value, onChange }: Props) { + return ( + } label="여행 제목"> + onChange(e.target.value)} + className="w-full border border-[var(--color-border)] rounded-lg px-4 py-3 + text-[var(--color-text-main)] placeholder-[var(--color-text-sub)] + focus:ring-2 focus:ring-[var(--color-primary)] outline-none" + /> + + ); +} diff --git a/src/features/trip/hooks/useTripForm.ts b/src/features/trip/hooks/useTripForm.ts index a0f913d..4053fd5 100644 --- a/src/features/trip/hooks/useTripForm.ts +++ b/src/features/trip/hooks/useTripForm.ts @@ -1,32 +1,49 @@ import { useState } from "react"; -import type { TripFormData } from "../types/form"; + +/** + * TripForm 전용 UI 상태 + * - API / DTO / Payload 와 완전히 분리 + * - 외부 export ❌ + */ +interface TripFormState { + title: string; + region: string; + folder: string; + startDate: Date | null; + endDate: Date | null; + coverImage: File | null; +} + +const INITIAL_FORM: TripFormState = { + title: "", + region: "", + folder: "선택 안함", + startDate: null, + endDate: null, + coverImage: null, +}; export function useTripForm() { - const [form, setForm] = useState({ - title: "", - region: "", - folder: "선택 안함", - startDate: null, - endDate: null, - coverImage: null, - }); + const [form, setForm] = useState(INITIAL_FORM); - const updateField = ( + const updateField = ( key: K, - value: TripFormData[K] + value: TripFormState[K] ) => { - setForm((prev) => ({ ...prev, [key]: value })); + setForm((prev) => ({ + ...prev, + [key]: value, + })); }; - const resetForm = () => - setForm({ - title: "", - region: "", - folder: "선택 안함", - startDate: null, - endDate: null, - coverImage: null, - }); + const resetForm = () => { + setForm(INITIAL_FORM); + }; - return { form, updateField, resetForm }; + return { + form, + setForm, + updateField, + resetForm, + }; } diff --git a/src/features/trip/mock/trips.mock.ts b/src/features/trip/mock/trips.mock.ts index 6bae27b..7c8df5a 100644 --- a/src/features/trip/mock/trips.mock.ts +++ b/src/features/trip/mock/trips.mock.ts @@ -11,6 +11,7 @@ export const MOCK_TRIPS: Trip[] = [ status: "완료", startDate: parseLocalDate("2025-09-10"), endDate: parseLocalDate("2025-09-15"), + days: 4, destinations: ["도쿄"], participants: 3, thumbnail: sampleThumbnail, @@ -22,6 +23,7 @@ export const MOCK_TRIPS: Trip[] = [ status: "여행중", startDate: parseLocalDate("2025-09-29"), endDate: parseLocalDate("2025-10-05"), + days: 4, destinations: ["제주시"], participants: 2, thumbnail: sampleThumbnail, @@ -33,6 +35,7 @@ export const MOCK_TRIPS: Trip[] = [ status: "예정", startDate: parseLocalDate("2025-10-20"), endDate: parseLocalDate("2025-10-24"), + days: 4, destinations: ["경주시"], participants: 2, thumbnail: sampleThumbnail, diff --git a/src/features/trip/types/form.ts b/src/features/trip/types/form.ts deleted file mode 100644 index b47453b..0000000 --- a/src/features/trip/types/form.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { TripStatus } from "./../types/trip.type"; - -export interface TripFormData { - title: string; - region: string; - folder: string; - startDate: Date | null; - endDate: Date | null; - coverImage?: File | null; - days?: number; -} - -export interface TripCreatePayload { - title: string; - region: string; - folderId: number | null; - startDate: string | null; - endDate: string | null; - days: number; - coverImage?: File | null; - status?: TripStatus; - destinations?: string[]; - participants?: number; -} diff --git a/src/features/trip/types/trip.dto.ts b/src/features/trip/types/trip.dto.ts new file mode 100644 index 0000000..3e6dd79 --- /dev/null +++ b/src/features/trip/types/trip.dto.ts @@ -0,0 +1,18 @@ +// features/trip/types/trip.dto.ts +import type { TripStatus } from "./trip.type"; + +export interface TripResponseDto { + id: number; + title: string; + region: string; + status?: TripStatus; + startDate?: string | null; + endDate?: string | null; + days?: number; + thumbnail?: string | null; + destinations?: string[]; + participants?: number; + folderId?: number | null; + createdAt?: string; + updatedAt?: string; +} diff --git a/src/features/trip/types/trip.payload.ts b/src/features/trip/types/trip.payload.ts new file mode 100644 index 0000000..c3eba23 --- /dev/null +++ b/src/features/trip/types/trip.payload.ts @@ -0,0 +1,29 @@ +// features/trip/types/trip.payload.ts +import type { TripStatus } from "./trip.type"; + +export interface TripCreatePayload { + title: string; + region: string; + folderId?: number | null; + startDate: string | null; + endDate: string | null; + days: number; + coverImage?: File | null; + status?: TripStatus; + destinations?: string[]; + participants?: number; +} + +export interface TripUpdatePayload { + tripId: number; + title?: string; + region?: string; + folderId?: number | null; + startDate?: string | null; + endDate?: string | null; + days?: number; + coverImage?: File | null; + status?: TripStatus; + destinations?: string[]; + participants?: number; +} diff --git a/src/features/trip/types/trip.type.ts b/src/features/trip/types/trip.type.ts index 9a76be8..aa98a34 100644 --- a/src/features/trip/types/trip.type.ts +++ b/src/features/trip/types/trip.type.ts @@ -1,3 +1,5 @@ +// features/trip/types/trip.type.ts + export type TripStatus = "예정" | "여행중" | "완료"; export interface Trip { @@ -5,8 +7,9 @@ export interface Trip { folderId: number; title: string; status: TripStatus; - startDate: Date; - endDate: Date; + startDate: Date | null; + endDate: Date | null; + days: number; destinations: string[]; participants: number; thumbnail?: string; diff --git a/src/pages/CalendarPage.tsx b/src/pages/CalendarPage.tsx index 1aa375e..f1cd6c5 100644 --- a/src/pages/CalendarPage.tsx +++ b/src/pages/CalendarPage.tsx @@ -12,7 +12,7 @@ export default function CalendarPage() { const handleTripSelect = (trip: Trip | null) => { if (!trip) return; - navigate(`/trips/${trip.id}`); + navigate(`/trips/${trip.id}/schedule`); }; const currentMonth = currentDate.getMonth(); @@ -21,6 +21,7 @@ export default function CalendarPage() { const filteredTrips = trips.filter((trip) => { const start = trip.startDate; const end = trip.endDate; + if (!start || !end) return false; return ( (start.getFullYear() === currentYear && start.getMonth() === currentMonth) || diff --git a/src/pages/CreateTripPage.tsx b/src/pages/CreateTripPage.tsx index 6cc6440..cd4f13a 100644 --- a/src/pages/CreateTripPage.tsx +++ b/src/pages/CreateTripPage.tsx @@ -1,7 +1,7 @@ import { useNavigate } from "react-router-dom"; import { X } from "lucide-react"; import TripCoverUploader from "@/features/trip/components/TripCoverUploader"; -import TripForm from "@/features/trip/components/TripForm"; +import TripForm from "@/features/trip/components/TripForm/TripForm"; import { useState } from "react"; export default function CreateTripPage() { diff --git a/src/pages/EditTripPage.tsx b/src/pages/EditTripPage.tsx new file mode 100644 index 0000000..63dd324 --- /dev/null +++ b/src/pages/EditTripPage.tsx @@ -0,0 +1,53 @@ +import { useNavigate, useParams } from "react-router-dom"; +import { X } from "lucide-react"; +import TripCoverUploader from "@/features/trip/components/TripCoverUploader"; +import TripForm from "@/features/trip/components/TripForm/TripForm"; +import { useState, useEffect } from "react"; +import {getTripDetail } from "@/features/trip/api/trip.api"; +import type { TripResponseDto } from "@/features/trip/types/trip.dto"; + +export default function EditTripPage() { + const { tripId } = useParams<{ tripId: string }>(); + const navigate = useNavigate(); + const [coverFile, setCoverFile] = useState(null); + const [initialTrip, setInitialTrip] = useState(null); + + // 서버에서 trip 정보 가져오기 + useEffect(() => { + if (!tripId) return; + + const fetchTrip = async () => { + const data = await getTripDetail(Number(tripId)); // API 호출 + setInitialTrip(data); + }; + + fetchTrip(); + }, [tripId]); + + const handleSelectCover = (file: File) => { + setCoverFile(file); + }; + + return ( +
+
+ + +
+ + +
+ ); +} diff --git a/src/pages/FolderDetailPage.tsx b/src/pages/FolderDetailPage.tsx index 7d41981..da643fe 100644 --- a/src/pages/FolderDetailPage.tsx +++ b/src/pages/FolderDetailPage.tsx @@ -40,7 +40,7 @@ export default function FolderDetailPage() { title={`${folder?.name ?? "폴더"}의 여행`} trips={filteredTrips} emptyText="이 폴더에는 여행이 없습니다" - onItemClick={(trip) => navigate(`/trips/${trip.id}`)} + onItemClick={(trip) => navigate(`/trips/${trip.id}/schedule`)} /> )}
diff --git a/src/pages/ScheduleEditPage.tsx b/src/pages/ScheduleEditPage.tsx index 3cc0e43..32dabf2 100644 --- a/src/pages/ScheduleEditPage.tsx +++ b/src/pages/ScheduleEditPage.tsx @@ -14,12 +14,14 @@ export default function ScheduleEditPage() { const navigate = useNavigate(); const trip = MOCK_TRIPS.find((t) => t.id === Number(tripId))!; const [selectedDay, setSelectedDay] = useState(1); - const { grouped,setSchedules, setPlaces, schedules, places, loading, error } = useScheduleWithPlaces(Number(tripId)); - const days = getTripDays(trip.startDate, trip.endDate); + const { grouped, setSchedules, loading, error } = useSchedule(Number(tripId)); + const days = trip.startDate && trip.endDate + ? getTripDays(trip.startDate, trip.endDate) + : []; const handleSave = () => { alert("✅ 여행 일정이 저장되었습니다!"); - navigate(`/trips/${tripId}`); + navigate(`/trips/${tripId}/schedule`); }; return ( @@ -29,7 +31,9 @@ export default function ScheduleEditPage() { /> 0 + ? `${days[0].date} ~ ${days[days.length - 1].date}` + : "날짜 미정"} status={trip.status} thumbnail={trip.thumbnail} /> diff --git a/src/pages/SchedulePage.tsx b/src/pages/SchedulePage.tsx index 522a119..a595064 100644 --- a/src/pages/SchedulePage.tsx +++ b/src/pages/SchedulePage.tsx @@ -13,19 +13,22 @@ export default function SchedulePage() { const { tripId } = useParams(); const trip = MOCK_TRIPS.find((t) => t.id === Number(tripId))!; const [selectedDay, setSelectedDay] = useState(1); - const days = getTripDays(trip.startDate, trip.endDate); + const { grouped, loading, error } = useSchedule(Number(tripId)); + const days = trip.startDate && trip.endDate + ? getTripDays(trip.startDate, trip.endDate) + : []; // 커스텀 훅을 사용하여 일정 및 장소 데이터 가져오기 const { grouped, places, loading, error } = useScheduleWithPlaces(Number(tripId)); return ( -
- +
+ 0 + ? `${days[0].date} ~ ${days[days.length - 1].date}` + : "날짜 미정"} status={trip.status} thumbnail={trip.thumbnail} /> diff --git a/src/shared/config/pageConfig.ts b/src/shared/config/pageConfig.ts index e3bb8d7..eef29b1 100644 --- a/src/shared/config/pageConfig.ts +++ b/src/shared/config/pageConfig.ts @@ -31,15 +31,19 @@ export const PAGE_UI_CONFIG: Record = { floatingIcon: "plus", floatingAction: "/trips/new", }, - "/trips/new": { + "/trips/new": { header: false, floatingButton: false, }, - "/trips/:tripId": { + "/trips/:tripId/edit": { header: false, floatingButton: false, }, - "/trips/:tripId/edit": { + "/trips/:tripId/schedule": { + header: false, + floatingButton: false, + }, + "/trips/:tripId/schedule/edit": { header: false, floatingButton: false, }, @@ -52,6 +56,7 @@ export const PAGE_TITLE_CONFIG: Record = { "/folders": "여행 폴더", "/folders/:id": "폴더 상세", "/trips/new": "여행 생성", - "/trips/:tripId": "일정 상세", - "/trips/:tripId/edit": "일정 수정", + "/trips/:tripId/edit": "여행 정보 수정", + "/trips/:tripId/schedule": "일정 상세", + "/trips/:tripId/schedule/edit": "일정 수정", }; \ No newline at end of file