diff --git a/src/api/owner/stores.ts b/src/api/owner/stores.ts index 64ad79d..174aa04 100644 --- a/src/api/owner/stores.ts +++ b/src/api/owner/stores.ts @@ -11,7 +11,6 @@ export interface StoreDetail { isApproved: boolean; rating?: number; reviewCount?: number; - tableImages?: TableImage[]; } export interface BusinessHour { @@ -57,13 +56,13 @@ interface MyStoreResponse { } export interface TableImage { - tableId: number; + tableImageId: number; tableImageUrl: string; } export interface TableImagesResponse { storeId: number; - tableImageUrls: string[]; + tableImages: TableImage[]; } export function getStore(storeId: number | string) { @@ -99,17 +98,13 @@ export const getMyStores = async (): Promise => { export const getTableImages = async ( storeId: number | string, ): Promise => { - const res = await api.get< - ApiResponse<{ storeId: number; tableImageUrls: string[] }> - >(`/api/v1/stores/${storeId}/table-images`); + const res = await api.get>( + `/api/v1/stores/${storeId}/table-images`, + ); if (!res.data.isSuccess) throw new Error(res.data.message); - // ⚠️ API 응답에 tableId가 포함되지 않아 삭제용 ID로 사용할 수 없음 - return res.data.result.tableImageUrls.map((url) => ({ - tableId: -1, // placeholder — 삭제 기능에 사용 금지 - tableImageUrl: url, - })); + return res.data.result?.tableImages ?? []; }; export const uploadTableImages = async ( @@ -139,12 +134,12 @@ export const uploadTableImages = async ( export const deleteTableImages = async ( storeId: number | string, - tableIds: number[], -): Promise> => { - const response = await api.delete>( + tableImageIds: number[], +): Promise> => { + const response = await api.delete>( `/api/v1/stores/${storeId}/table-images`, { - data: tableIds, + data: tableImageIds, }, ); const data = response.data; diff --git a/src/components/owner/StoreSettings.tsx b/src/components/owner/StoreSettings.tsx index 6107bc2..3fb5cab 100644 --- a/src/components/owner/StoreSettings.tsx +++ b/src/components/owner/StoreSettings.tsx @@ -1,12 +1,13 @@ import React, { useEffect, useState } from "react"; -import { Mail, Phone, MapPin, Clock, ChevronDown, X } from "lucide-react"; +import { Mail, Phone, MapPin, Clock, ChevronDown } from "lucide-react"; import { getStore, updateStore, updateBusinessHours, + uploadTableImages, + getTableImages, type TableImage, deleteTableImages, - uploadTableImages, } from "@/api/owner/stores"; interface StoreSettingsProps { @@ -36,13 +37,11 @@ const dayMapToApi: Record = { }; const StoreSettings: React.FC = ({ storeId }) => { - const [storeName, setStoreName] = useState("맛있는 레스토랑"); - const [description, setDescription] = useState( - "신선한 재료로 만드는 정성 가득한 음식을 제공합니다.", - ); - const [phone, setPhone] = useState("02-1234-5678"); - const [email, setEmail] = useState("store@example.com"); - const [address, setAddress] = useState("서울특별시 강남구 테헤란로 123"); + const [storeName, setStoreName] = useState(""); + const [description, setDescription] = useState(""); + const [phone, setPhone] = useState(""); + const [email, setEmail] = useState(""); + const [address, setAddress] = useState(""); const [openTime, setOpenTime] = useState("11:00"); const [closeTime, setCloseTime] = useState("22:00"); @@ -53,41 +52,42 @@ const StoreSettings: React.FC = ({ storeId }) => { const [maxGuests, setMaxGuests] = useState(20); const [tableImages, setTableImages] = useState([]); + const [deletedIds, setDeletedIds] = useState>(new Set()); const [newFiles, setNewFiles] = useState([]); - const [deletedIds, setDeletedIds] = useState([]); useEffect(() => { if (!storeId) return; - getStore(storeId) - .then((res) => { + (async () => { + try { + const res = await getStore(storeId); const store = res.data.result; setStoreName(store.storeName); setDescription(store.description ?? ""); setPhone(store.phone ?? ""); setAddress(store.address ?? ""); - if (store.businessHours?.length) { const open = store.businessHours.find((b) => !b.isClosed); if (open?.openTime && open?.closeTime) { setOpenTime(open.openTime); setCloseTime(open.closeTime); } - const closed = store.businessHours .filter((b) => b.isClosed) .map((b) => dayMapFromApi[b.day]); - setClosedDays(closed); } - if (store.tableImages) { - setTableImages(store.tableImages); - } - }) - .catch(() => { - alert("가게 정보를 불러오는 데 실패했습니다."); - }); + + const imgs = await getTableImages(storeId); + setTableImages(imgs); + setDeletedIds(new Set()); + setNewFiles([]); + } catch (e) { + alert("가게 정보를 불러오는데 실패했습니다."); + return; + } + })(); }, [storeId]); const toggleDay = (day: string) => { @@ -117,19 +117,12 @@ const StoreSettings: React.FC = ({ storeId }) => { e.target.value = ""; }; - const handleDeleteImage = (tableId?: number, fileIndex?: number) => { - if (tableId !== undefined) { - setTableImages((prev) => prev.filter((img) => img.tableId !== tableId)); - setDeletedIds((prev) => [...prev, tableId]); - } - - if (fileIndex !== undefined) { - setNewFiles((prev) => prev.filter((_, i) => i !== fileIndex)); - } + const handleDeleteImage = (fileIndex: number) => { + setNewFiles((prev) => prev.filter((_, i) => i !== fileIndex)); }; const inputStyle = - "w-full border border-gray-200 rounded-lg p-4 mt-2 text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all placeholder:text-gray-400"; + "w-full border border-gray-200 rounded-lg p-4 mt-2 text-sm text-black focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all placeholder:text-gray-400"; const labelStyle = "block text-md text-gray-700"; const sectionStyle = "bg-white border border-gray-200 rounded-lg p-8 mb-6"; @@ -140,7 +133,7 @@ const StoreSettings: React.FC = ({ storeId }) => { return (
-

기본 정보

+

기본 정보

-

영업 시간

+

영업 시간

@@ -282,7 +275,7 @@ const StoreSettings: React.FC = ({ storeId }) => {
-

예약 정책

+

예약 정책

@@ -290,7 +283,7 @@ const StoreSettings: React.FC = ({ storeId }) => { -
-

• 최대 용량: 1MB

-

• 형식: JPG(JPEG), PNG

+
+
+

+ 식당 테이블 이미지 +

+

+ • 최대 용량: 1MB • 형식: JPG(JPEG), PNG +

+
+ +
+ +
-
- {tableImages.map((img) => ( -
- {`테이블 - -
- ))} + {tableImages + .filter((img) => !deletedIds.has(img.tableImageId)) + .map((img, idx) => ( +
+ {`테이블 + +
+ ))} {newFiles.map((file, index) => { const previewUrl = URL.createObjectURL(file); @@ -408,10 +421,11 @@ const StoreSettings: React.FC = ({ storeId }) => { />
); @@ -458,32 +472,26 @@ const StoreSettings: React.FC = ({ storeId }) => { return; } try { - if (deletedIds.length > 0) { - await deleteTableImages(storeId, deletedIds); + if (deletedIds.size > 0) { + await deleteTableImages(storeId, Array.from(deletedIds)); } } catch (e) { - alert("일부 이미지 삭제에 실패했습니다."); + alert("이미지 삭제에 실패했습니다."); return; } - if (newFiles.length > 0) { - for (const file of newFiles) { - try { - await uploadTableImages(storeId, [file]); - } catch (e) { - console.error(`${file.name} 업로드 실패`); - } - } - } - setNewFiles([]); - setDeletedIds([]); try { - const res = await getStore(storeId); - if (res.data.result.tableImages) { - setTableImages(res.data.result.tableImages); + if (newFiles.length > 0) { + await uploadTableImages(storeId, newFiles); } } catch (e) { - console.error("저장 후 데이터 갱신 실패", e); + alert("이미지 업로드에 실패했습니다."); + return; } + + const imgs = await getTableImages(storeId); + setTableImages(imgs); + setDeletedIds(new Set()); + setNewFiles([]); alert("설정이 저장되었습니다."); }} className="cursor-pointer bg-blue-600 text-white px-12 py-4 rounded-xl shadow-lg hover:bg-blue-700 active:scale-95 transition-all text-lg" diff --git a/src/components/reservation/modals/ReservationModal.tsx b/src/components/reservation/modals/ReservationModal.tsx index 89fc2c7..f088956 100644 --- a/src/components/reservation/modals/ReservationModal.tsx +++ b/src/components/reservation/modals/ReservationModal.tsx @@ -148,7 +148,8 @@ export default function ReservationModal({ return { msg: "날짜를 먼저 선택해주세요", times: [] as string[] }; if (timesQuery.isLoading) return { msg: "예약 가능시간 기다리는중..", times: [] }; - if (timesQuery.isError) return { msg: "서버 조회 실패", times: [] }; + if (timesQuery.isError) + return { msg: "휴무일 입니다. 다른 날짜를 선택해주세요", times: [] }; if (times.length === 0) return { msg: "예약 가능한 시간이 없어요", times: [] }; return { msg: null as string | null, times };