Skip to content

Commit 6f206d5

Browse files
committed
2 parents 3a3a752 + c176a85 commit 6f206d5

13 files changed

Lines changed: 343 additions & 26 deletions

File tree

apps/ticket-admin/src/app/events/[eventId]/edit/_clientBoundary/EventEditFormClient/index.tsx

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ export function EventEditFormClient({ eventId }: Props) {
3838
"basic" | "ticket" | "guest" | "coupon" | "timeTable"
3939
>("basic");
4040
const [formData, setFormData] = useState<
41-
Omit<EventDetailResponse, "images"> & { images: PreviewMedia[] }
41+
Omit<EventDetailResponse, "images" | "siteMapImages"> & {
42+
images: PreviewMedia[];
43+
siteMapImages: PreviewMedia[];
44+
}
4245
>({
4346
eventId: 0,
4447
eventExposureStartDate: "",
@@ -57,7 +60,54 @@ export function EventEditFormClient({ eventId }: Props) {
5760
details: "",
5861
minAge: 0,
5962
images: [],
63+
siteMapImages: [],
6064
});
65+
66+
// [사이트맵 이미지] 핸들러
67+
const handleSiteMapFileChange = (files: FileList | null) => {
68+
if (!files || files.length === 0) return;
69+
70+
const toPreview = (file: File) =>
71+
new Promise<{ id: number; url: string; mediaType: "IMAGE" | "VIDEO" }>((resolve) => {
72+
const isVideo = file.type.startsWith("video/");
73+
const reader = new FileReader();
74+
75+
reader.onload = (ev) => {
76+
const dataUrl = (ev.target?.result as string) ?? "";
77+
78+
resolve({
79+
id: Date.now() + Math.floor(Math.random() * 1000),
80+
url: dataUrl,
81+
mediaType: isVideo ? "VIDEO" : "IMAGE",
82+
});
83+
};
84+
reader.readAsDataURL(file);
85+
});
86+
87+
Promise.all(Array.from(files).map(toPreview)).then((appended) => {
88+
setFormData((prev) => {
89+
const prevImages = (prev.siteMapImages as PreviewMedia[] | undefined) ?? [];
90+
const merged = [...prevImages, ...appended];
91+
92+
return {
93+
...prev,
94+
siteMapImages: merged,
95+
};
96+
});
97+
});
98+
};
99+
const handleRemoveSiteMapImage = (idOrUrl: number | string) => {
100+
setFormData((prev) => {
101+
const current = (prev.siteMapImages as PreviewMedia[] | undefined) ?? [];
102+
const next = current.filter((img) => img.id !== idOrUrl && img.imageUrl !== idOrUrl);
103+
104+
return {
105+
...prev,
106+
siteMapImages: next,
107+
};
108+
});
109+
};
110+
61111
const [isSubmitting, setIsSubmitting] = useState(false);
62112

63113
const { data: eventDetailData } = useEventDetailSuspenseQuery({
@@ -401,7 +451,8 @@ export function EventEditFormClient({ eventId }: Props) {
401451
useEffect(() => {
402452
if (eventDetailData) {
403453
// 폼 데이터 설정
404-
setFormData({
454+
setFormData((prev) => ({
455+
...prev,
405456
eventId: eventDetailData.eventId,
406457
eventExposureStartDate: eventDetailData.eventExposureStartDate,
407458
eventExposureEndDate: eventDetailData.eventExposureEndDate,
@@ -419,7 +470,8 @@ export function EventEditFormClient({ eventId }: Props) {
419470
details: eventDetailData.details || "",
420471
minAge: eventDetailData.minAge,
421472
images: eventDetailData.images || [],
422-
});
473+
siteMapImages: eventDetailData.siteMapImages || [],
474+
}));
423475

424476
// 각 필드의 value를 설정
425477
eventExposureStartDateField.selectProps.onChange(eventDetailData.eventExposureStartDate);
@@ -519,8 +571,6 @@ export function EventEditFormClient({ eventId }: Props) {
519571
return { mediaName, mediaType: m.mediaType! };
520572
});
521573

522-
console.log("@@toUpload", toUpload, mediaInfoRequests);
523-
524574
const presignedUrls = await postPresignedUrls({
525575
eventId: eventDetailData.eventId,
526576
mediaInfoRequests,
@@ -538,8 +588,6 @@ export function EventEditFormClient({ eventId }: Props) {
538588
}),
539589
);
540590

541-
console.log("@@@", formData.images);
542-
543591
const imagesData = formData.images.map((m) => {
544592
if (m.id) {
545593
const url = presignedUrls.preSignedUrlInfoList.find(
@@ -560,12 +608,60 @@ export function EventEditFormClient({ eventId }: Props) {
560608
};
561609
});
562610

563-
console.log("@@imagesData", imagesData);
611+
const toUploadSiteMap = (
612+
formData.siteMapImages as { id?: number; url?: string; mediaType?: "IMAGE" | "VIDEO" }[]
613+
).filter((m) => !!m?.url && !!m?.mediaType);
614+
615+
const mediaInfoRequestsSiteMap = toUploadSiteMap.map((m) => {
616+
const mediaName = `${m.id}`;
617+
618+
return { mediaName, mediaType: m.mediaType! };
619+
});
620+
621+
const siteMapPresignedUrls = await postPresignedUrls({
622+
eventId: eventDetailData.eventId,
623+
mediaInfoRequests: mediaInfoRequestsSiteMap,
624+
});
625+
626+
await Promise.all(
627+
siteMapPresignedUrls.preSignedUrlInfoList.map(async (url, index) => {
628+
const file = toUploadSiteMap[index];
629+
630+
const response = await fetch(file.url!);
631+
const blob = await response.blob();
632+
const newFile = new File([blob], `fileName-${file.id}`, { type: blob.type });
633+
634+
return putS3Upload({ url: url.preSignedUrl, file: newFile });
635+
}),
636+
);
637+
638+
const siteMapsData = formData.siteMapImages.map((m) => {
639+
if (m.id) {
640+
const url = siteMapPresignedUrls.preSignedUrlInfoList.find(
641+
(info) => info.mediaName === m.id?.toString(),
642+
)?.preSignedUrl;
643+
644+
console.log("@@", url);
645+
646+
const imageUrl = toCDNUrl(url?.split("?")[0] as string);
647+
648+
return {
649+
imageUrl: imageUrl as string,
650+
};
651+
}
652+
653+
return {
654+
imageUrl: m.imageUrl as string,
655+
};
656+
});
657+
658+
console.log("@@", siteMapsData);
564659

565660
await patchEvent({
566661
...formData,
567662
eventId: eventDetailData.eventId,
568663
images: imagesData,
664+
siteMapImages: siteMapsData,
569665
});
570666

571667
alert("이벤트 수정이 완료되었습니다.");
@@ -632,6 +728,8 @@ export function EventEditFormClient({ eventId }: Props) {
632728
roundSalesEndDate={roundSalesEndDate.selectProps}
633729
roundSalesStartTime={roundSalesStartTime}
634730
roundSalesEndTime={roundSalesEndTime}
731+
onSiteMapFileChange={handleSiteMapFileChange}
732+
onRemoveSiteMapImage={handleRemoveSiteMapImage}
635733
onDelete={handleDelete}
636734
isSubmitting={isSubmitting}
637735
isReadOnlyMode={isReadOnlyMode}

apps/ticket-admin/src/app/events/create/_clientBoundary/EventFormClient/index.tsx

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ import styles from "./index.module.scss";
2222

2323
const cx = classNames.bind(styles);
2424

25-
export type EventFormData = Omit<EventRequest, "ticketTypes" | "images"> & {
25+
export type EventFormData = Omit<EventRequest, "ticketTypes" | "images" | "siteMapImages"> & {
2626
ticketTypes: TicketData[];
2727
images: PreviewMedia[];
28+
siteMapImages: PreviewMedia[];
2829
};
2930

3031
type FormData = EventFormData;
@@ -52,6 +53,7 @@ const initialFormData: EventRequest = {
5253
roundSalesStartTime: "",
5354
roundSalesEndTime: "",
5455
ticketTypes: [],
56+
siteMapImages: [],
5557
};
5658

5759
export const eventTypeOptions = [
@@ -415,6 +417,40 @@ export function EventFormClient() {
415417
},
416418
});
417419

420+
// [사이트맵 이미지] 핸들러
421+
const handleSiteMapFileChange = (files: FileList | null) => {
422+
if (!files || files.length === 0) return;
423+
424+
const toPreview = (file: File) =>
425+
new Promise<{ id: number; url: string; mediaType: "IMAGE" | "VIDEO" }>((resolve) => {
426+
const isVideo = file.type.startsWith("video/");
427+
const reader = new FileReader();
428+
429+
reader.onload = (ev) => {
430+
const dataUrl = (ev.target?.result as string) ?? "";
431+
432+
resolve({
433+
id: Date.now() + Math.floor(Math.random() * 1000),
434+
url: dataUrl,
435+
mediaType: isVideo ? "VIDEO" : "IMAGE",
436+
});
437+
};
438+
reader.readAsDataURL(file);
439+
});
440+
441+
Promise.all(Array.from(files).map(toPreview)).then((appended) => {
442+
setFormData((prev) => {
443+
const prevImages = (prev.siteMapImages as PreviewMedia[] | undefined) ?? [];
444+
const merged = [...prevImages, ...appended];
445+
446+
return {
447+
...prev,
448+
siteMapImages: merged,
449+
};
450+
});
451+
});
452+
};
453+
418454
// 티켓 관리 함수들
419455
const addTicket = () => {
420456
const newTicket: TicketData = {
@@ -505,9 +541,55 @@ export function EventFormClient() {
505541
};
506542
});
507543

544+
const toUploadSiteMap = (
545+
formData.siteMapImages as { id?: number; url?: string; mediaType?: "IMAGE" | "VIDEO" }[]
546+
).filter((m) => !!m?.url && !!m?.mediaType);
547+
548+
const mediaInfoRequestsSiteMap = toUploadSiteMap.map((m) => {
549+
const mediaName = `${m.id}`;
550+
551+
return { mediaName, mediaType: m.mediaType! };
552+
});
553+
554+
const presignedUrlsSiteMap = await postPresignedUrls({
555+
eventId: (maxEventIdAll as number) + 1 || 1,
556+
mediaInfoRequests: mediaInfoRequestsSiteMap,
557+
});
558+
559+
await Promise.all(
560+
presignedUrlsSiteMap.preSignedUrlInfoList.map(async (url, index) => {
561+
const file = toUploadSiteMap[index];
562+
563+
const response = await fetch(file.url!);
564+
const blob = await response.blob();
565+
const newFile = new File([blob], `fileName-${file.id}`, { type: blob.type });
566+
567+
return putS3Upload({ url: url.preSignedUrl, file: newFile });
568+
}),
569+
);
570+
571+
const siteMapsData = formData.siteMapImages.map((m) => {
572+
if (m.id) {
573+
const url = presignedUrlsSiteMap.preSignedUrlInfoList.find(
574+
(info) => info.mediaName === m.id?.toString(),
575+
)?.preSignedUrl;
576+
577+
const imageUrl = toCDNUrl(url?.split("?")[0] as string);
578+
579+
return {
580+
imageUrl: imageUrl as string,
581+
};
582+
}
583+
584+
return {
585+
imageUrl: m.imageUrl as string,
586+
};
587+
});
588+
508589
const apiData: EventRequest = {
509590
...formData,
510591
images: imagesData,
592+
siteMapImages: siteMapsData,
511593
ticketTypes: formData.ticketTypes.map(({ id: _id, ...ticket }) => ticket),
512594
};
513595

@@ -562,6 +644,7 @@ export function EventFormClient() {
562644
roundSalesEndDate={roundSalesEndDate.selectProps}
563645
roundSalesStartTime={roundSalesStartTime}
564646
roundSalesEndTime={roundSalesEndTime}
647+
onSiteMapFileChange={handleSiteMapFileChange}
565648
isSubmitting={isSubmitting}
566649
onAddTicket={addTicket}
567650
onUpdateTicket={updateTicket}

apps/ticket-admin/src/app/events/create/_components/EventFormLayout/index.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type EventFormLayoutProps = {
2121
EventFormData,
2222
| "eventType"
2323
| "images"
24+
| "siteMapImages"
2425
| "ticketTypes"
2526
| "ticketRoundName"
2627
| "roundSalesStartDate"
@@ -30,6 +31,7 @@ type EventFormLayoutProps = {
3031
> & {
3132
eventType?: string;
3233
images?: { imageUrl?: string }[];
34+
siteMapImages?: { imageUrl?: string }[];
3335
ticketTypes?: TicketData[];
3436
ticketRoundName?: string;
3537
roundSalesStartDate?: string;
@@ -40,6 +42,8 @@ type EventFormLayoutProps = {
4042
onFileChange?: (files: FileList | null) => void;
4143
onRemoveOriginalImage?: (url: string) => void;
4244
onDelete?: () => void;
45+
onSiteMapFileChange?: (files: FileList | null) => void;
46+
onRemoveSiteMapImage?: (idOrUrl: number | string) => void;
4347
isSubmitting: boolean;
4448
isReadOnlyMode?: boolean;
4549
currentStep: "basic" | "ticket";
@@ -73,6 +77,8 @@ export function EventFormLayout({
7377
onFileChange,
7478
onRemoveOriginalImage,
7579
onDelete: _onDelete,
80+
onSiteMapFileChange,
81+
onRemoveSiteMapImage,
7682
isSubmitting: _isSubmitting,
7783
isReadOnlyMode = false,
7884
currentStep,
@@ -330,6 +336,27 @@ export function EventFormLayout({
330336
</Flex>
331337
)}
332338

339+
{onSiteMapFileChange && (
340+
<Flex gap={24}>
341+
<Flex className={cx("row")} direction="column" gap={12}>
342+
<Typography type="body16" weight="bold">
343+
Site Map
344+
</Typography>
345+
<ImageUploader
346+
disabled={isReadOnlyMode}
347+
value={(formData.siteMapImages as unknown as PreviewMedia[] | null) || null}
348+
onImagesUpload={(_images) => {
349+
// formData에 맞게 반영 (EventFormClient가 내려준 formData 구조에 맞춰 조정)
350+
// images는 dataURL 미리보기이며, 저장 시 서버 업로드 로직에서 변환 필요
351+
// 여기서는 상위 onFileChange와의 호환을 유지하기 위해 noop 처리
352+
}}
353+
onFileSelect={onSiteMapFileChange}
354+
onRemoveOriginalImage={onRemoveSiteMapImage}
355+
/>
356+
</Flex>
357+
</Flex>
358+
)}
359+
333360
<Flex gap={24}>
334361
<Flex className={cx("row")} direction="column" gap={12}>
335362
<Typography type="body16" weight="bold">

apps/ticket-admin/src/data/admin/getEventDetail/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,8 @@ export type EventDetailResponse = {
2121
images: {
2222
imageUrl?: string;
2323
}[];
24+
siteMapImages?: {
25+
imageUrl?: string;
26+
}[];
2427
minAge: number;
2528
};

apps/ticket-admin/src/data/admin/patchEvents/mutation.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export type EventRequest = {
2222
images: {
2323
imageUrl: string;
2424
}[];
25+
siteMapImages?: {
26+
imageUrl: string;
27+
}[];
2528
minAge: number;
2629
};
2730

apps/ticket-admin/src/data/admin/postEvents/mutation.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export type EventRequest = {
2222
images: {
2323
imageUrl: string;
2424
}[];
25+
siteMapImages?: {
26+
imageUrl: string;
27+
}[];
2528
minAge: number;
2629
ticketRoundName: string;
2730
roundSalesStartDate: string;

0 commit comments

Comments
 (0)