diff --git a/src/components/rooms/RoomTripEditDialog.test.tsx b/src/components/rooms/RoomTripEditDialog.test.tsx index 21f598c4..ca3089a1 100644 --- a/src/components/rooms/RoomTripEditDialog.test.tsx +++ b/src/components/rooms/RoomTripEditDialog.test.tsx @@ -97,11 +97,12 @@ it("uses the existing trimmed mutation payload and closes only after save succee expect(dialog()).toBeNull(); }); -it("retains the original date floor, date ordering and required destination validation", async () => { +it("allows an earlier start date and keeps date ordering and required destination validation", async () => { await open(); + // 저장된 시작일보다 이른 날짜로도 시작일을 앞당길 수 있다 await pickDates("start", "2026-10-09"); - expect(dateField("start").getAttribute("aria-label")).toContain("2026년 10월 10일"); - expect(button("적용하기", dialog()!).disabled).toBe(true); + expect(dateField("start").getAttribute("aria-label")).toContain("2026년 10월 9일"); + expect(button("적용하기", dialog()!).disabled).toBe(false); // 종료 칸에서 시작일보다 이른 날짜를 고르면 기간을 거꾸로 만들지 않고 새 시작일이 되며, 종료일은 그대로다 await pickDates("start", "2026-10-11", "2026-10-12"); await pickDates("end", "2026-10-10"); diff --git a/src/components/rooms/RoomTripEditFields.tsx b/src/components/rooms/RoomTripEditFields.tsx index aaaa9600..7309df9c 100644 --- a/src/components/rooms/RoomTripEditFields.tsx +++ b/src/components/rooms/RoomTripEditFields.tsx @@ -11,12 +11,10 @@ import { } from "@/lib/plan/schedulePolicy"; import { isTripDateRangeInvalid, - isTripStartBeforeMin, ROOM_TRIP_TITLE_MAX_LENGTH, TRIP_DATE_RANGE_INVALID_MESSAGE, TRIP_DESTINATION_MAX_LENGTH, TRIP_DESTINATIONS_MAX_COUNT, - TRIP_START_BEFORE_MIN_MESSAGE, type TripFormValues, } from "@/lib/rooms/trip-form"; import { cn } from "@/lib/utils"; @@ -29,7 +27,6 @@ type Props = { idPrefix: string; values: TripFormValues; readOnly?: boolean; - startDateMin?: string; onTitleChange: (value: string) => void; onDestinationsChange: (next: string[]) => void; onStartDateChange: (value: string) => void; @@ -53,7 +50,6 @@ export function RoomTripEditFields({ idPrefix, values, readOnly = false, - startDateMin, onTitleChange, onDestinationsChange, onStartDateChange, @@ -64,11 +60,9 @@ export function RoomTripEditFields({ const [destinationWarning, setDestinationWarning] = useState(null); const destinationsFull = destinations.length >= TRIP_DESTINATIONS_MAX_COUNT; const dateRangeInvalid = isTripDateRangeInvalid(startDate, endDate); - const startBeforeMin = startDateMin != null && isTripStartBeforeMin(startDate, startDateMin); const scheduleDayLimitExceeded = isTripScheduleDayLimitExceeded(startDate, endDate); let dateMessage = null; - if (startBeforeMin) dateMessage = TRIP_START_BEFORE_MIN_MESSAGE; - else if (dateRangeInvalid) dateMessage = TRIP_DATE_RANGE_INVALID_MESSAGE; + if (dateRangeInvalid) dateMessage = TRIP_DATE_RANGE_INVALID_MESSAGE; else if (scheduleDayLimitExceeded) dateMessage = TRIP_SCHEDULE_DAY_LIMIT_MESSAGE; function addDestination(place: { description: string; placeId: string } | null) { @@ -168,7 +162,6 @@ export function RoomTripEditFields({ { onStartDateChange(next.start); diff --git a/src/components/rooms/RoomTripEditForm.test.tsx b/src/components/rooms/RoomTripEditForm.test.tsx index 6b901fc5..ec489a1d 100644 --- a/src/components/rooms/RoomTripEditForm.test.tsx +++ b/src/components/rooms/RoomTripEditForm.test.tsx @@ -22,7 +22,7 @@ vi.mock("./TripDateShrinkConfirmModal", () => ({ TripDateShrinkConfirmModal: () =>
, })); Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); -it("preserves drafts and original date floor on same-room refresh; reopening and room switch reset correctly", async () => { +it("preserves drafts on same-room refresh; reopening and room switch reset correctly", async () => { const container = document.createElement("div"); const root = createRoot(container); const room = { @@ -50,9 +50,7 @@ it("preserves drafts and original date floor on same-room refresh; reopening and await render(room); expect(state.fields!.values.startDate).toBe("2026-10-10"); expect(state.fields!.values.endDate).toBe("2026-10-12"); - expect(state.fields!.startDateMin).toBe("2026-10-10"); await render({ ...room, startDate: "2026-10-11" }); - expect(state.fields!.startDateMin).toBe("2026-10-10"); await act(async () => state.fields!.onEndDateChange("2026-10-11")); await act(async () => container.querySelector("button")!.click()); expect(container.querySelector("[data-confirm]")).not.toBeNull(); @@ -64,7 +62,6 @@ it("preserves drafts and original date floor on same-room refresh; reopening and endDate: "2026-11-12", }); expect(state.fields!.values.title).toBe("B"); - expect(state.fields!.startDateMin).toBe("2026-11-10"); expect(container.querySelector("[data-confirm]")).toBeNull(); expect(state.update).not.toHaveBeenCalled(); await act(async () => root.unmount()); @@ -109,6 +106,24 @@ it("keeps the values from when it opened while the server changes and saves the await act(async () => second.unmount()); }); +it("allows moving the start date earlier than the saved start date", async () => { + state.update.mockClear(); + const container = document.createElement("div"); + const root = createRoot(container); + const room = { id: "r", title: "A", destinations: ["서울"], startDate: "2027-01-04", endDate: "2027-01-11" }; + await act(async () => root.render()); + expect("startDateMin" in state.fields!).toBe(false); + await act(async () => state.fields!.onStartDateChange("2027-01-02")); + await act(async () => container.querySelector("button")!.click()); + expect(state.update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ startDate: "2027-01-02", endDate: "2027-01-11" }), + }), + expect.anything(), + ); + await act(async () => root.unmount()); +}); + it("does not submit draft edits and submits once for rapid apply clicks", async () => { state.update.mockClear(); const container = document.createElement("div"); diff --git a/src/components/rooms/RoomTripEditForm.tsx b/src/components/rooms/RoomTripEditForm.tsx index 764004de..80c680d5 100644 --- a/src/components/rooms/RoomTripEditForm.tsx +++ b/src/components/rooms/RoomTripEditForm.tsx @@ -18,7 +18,6 @@ import { areDestinationsEqual, isTripDateRangeInvalid, isTripDestinationsValid, - isTripStartBeforeMin, ROOM_TRIP_TITLE_MAX_LENGTH, toTripFormValues, type RoomTripFormSource, @@ -50,9 +49,6 @@ function RoomTripEditSession({ room, readOnly = false, onSaved }: Props) { const [endDate, setEndDate] = useState(opened.endDate); const [shrinkConfirmOpen, setShrinkConfirmOpen] = useState(false); - // Keep the original date floor throughout this room's edit session. - const [baselineStartYmd] = useState(opened.startDate); - const { data: schedules } = useRoomSchedules(room.id); const { mutate: updateRoom, isPending, error } = useUpdateRoom(); @@ -76,7 +72,6 @@ function RoomTripEditSession({ room, readOnly = false, onSaved }: Props) { startDate && endDate && !dateRangeInvalid && - !isTripStartBeforeMin(startDate, baselineStartYmd) && !scheduleDayLimitExceeded && !isPending && isDirty; @@ -136,7 +131,6 @@ function RoomTripEditSession({ room, readOnly = false, onSaved }: Props) { idPrefix={`trip-${room.id}`} values={readOnly ? latest : { title, destinations, startDate, endDate }} readOnly={readOnly} - startDateMin={baselineStartYmd || undefined} onTitleChange={setTitle} onDestinationsChange={setDestinations} onStartDateChange={setStartDate}