Skip to content

Commit 55df036

Browse files
authored
Merge pull request #72 from SSASINSA/feature/71
행사 보고서 다운로드 기능 추가 (#71)
2 parents adade42 + 164ec8f commit 55df036

2 files changed

Lines changed: 104 additions & 7 deletions

File tree

src/components/pages/events/EventDetail/EventDetail.module.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,22 @@
790790
filter: brightness(0) invert(1);
791791
}
792792

793+
.download-button {
794+
background-color: #111827;
795+
border-color: #111827;
796+
color: white;
797+
}
798+
799+
.download-button:hover {
800+
background-color: #1f2937;
801+
border-color: #1f2937;
802+
color: white;
803+
}
804+
805+
.download-button img {
806+
filter: brightness(0) invert(1);
807+
}
808+
793809

794810
.event-info-section {
795811
background-color: white;

src/components/pages/events/EventDetail/EventDetail.tsx

Lines changed: 88 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const editIcon = "/admin/img/icon/edit.svg";
1414
const co2Icon = "/admin/img/icon/co2.svg";
1515
const waterIcon = "/admin/img/icon/water.svg";
1616
const energyIcon = "/admin/img/icon/energy.svg";
17+
const reportIcon = "/admin/img/icon/document.svg";
1718

1819
interface EventImage {
1920
imageId: number;
@@ -84,6 +85,13 @@ interface StaffCodeResponse {
8485
issuedAt: string;
8586
}
8687

88+
interface EventReportResponse {
89+
reportId: string;
90+
status: string;
91+
downloadUrl?: string;
92+
message?: string;
93+
}
94+
8795
const EventDetail: React.FC = () => {
8896
const { id } = useParams<{ id: string }>();
8997
const navigate = useNavigate();
@@ -97,6 +105,7 @@ const EventDetail: React.FC = () => {
97105
const [staffCodeError, setStaffCodeError] = useState<string | null>(null);
98106
const [showIssueModal, setShowIssueModal] = useState<boolean>(false);
99107
const [showReissueModal, setShowReissueModal] = useState<boolean>(false);
108+
const [isDownloadingReport, setIsDownloadingReport] = useState<boolean>(false);
100109

101110
const sidebarRef = useRef<HTMLDivElement | null>(null);
102111
const sidebarInnerRef = useRef<HTMLDivElement | null>(null);
@@ -322,6 +331,61 @@ const EventDetail: React.FC = () => {
322331
}
323332
};
324333

334+
const buildDownloadUrl = (rawUrl: string): string => {
335+
if (!rawUrl) return "";
336+
const base = process.env.REACT_APP_API_BASE_URL || window.location.origin;
337+
try {
338+
if (rawUrl.startsWith("http")) {
339+
return rawUrl;
340+
}
341+
return new URL(rawUrl, base).toString();
342+
} catch (error) {
343+
console.error("보고서 다운로드 URL 변환 실패:", error);
344+
return rawUrl;
345+
}
346+
};
347+
348+
const handleDownloadReport = async () => {
349+
if (!id) return;
350+
351+
try {
352+
setIsDownloadingReport(true);
353+
const response = await apiRequest(`/admin/reports?eventId=${id}`, {
354+
method: "POST",
355+
});
356+
357+
if (!response.ok) {
358+
const errorData = await response.json().catch(() => ({}));
359+
throw new Error(errorData.message || "행사 보고서 다운로드에 실패했습니다.");
360+
}
361+
362+
const data: EventReportResponse = await response.json();
363+
const status = data.status ? data.status.toUpperCase() : "";
364+
365+
if (status === "FAILED") {
366+
throw new Error("보고서 집계가 완료되지 않았습니다. 잠시 후 다시 시도해주세요.");
367+
}
368+
369+
if (status === "READY" && data.downloadUrl) {
370+
const downloadUrl = buildDownloadUrl(data.downloadUrl);
371+
const anchor = document.createElement("a");
372+
anchor.href = downloadUrl;
373+
anchor.target = "_blank";
374+
anchor.rel = "noopener noreferrer";
375+
anchor.click();
376+
return;
377+
}
378+
379+
alert("보고서를 준비 중입니다. 잠시 후 다시 시도해주세요.");
380+
} catch (err) {
381+
console.error("행사 보고서 다운로드 실패:", err);
382+
const message = err instanceof Error ? err.message : "행사 보고서 다운로드에 실패했습니다.";
383+
alert(message);
384+
} finally {
385+
setIsDownloadingReport(false);
386+
}
387+
};
388+
325389
const calculateTotalCapacity = (options: EventOption[]): number => {
326390
let total = 0;
327391
const traverse = (opts: EventOption[]) => {
@@ -373,6 +437,10 @@ const EventDetail: React.FC = () => {
373437
}
374438
};
375439

440+
const isEventCompleted = (status: string): boolean => {
441+
return mapApiStatusToDisplayStatus(status) === "completed";
442+
};
443+
376444
const getStatusBadge = (status: string) => {
377445
const displayStatus = mapApiStatusToDisplayStatus(status);
378446
switch (displayStatus) {
@@ -512,6 +580,7 @@ const EventDetail: React.FC = () => {
512580
const totalCapacity = calculateTotalCapacity(eventData.options);
513581
const totalAppliedCount = calculateTotalAppliedCount(eventData.options);
514582
const totalRemainingCount = totalCapacity - totalAppliedCount;
583+
const isCompletedEvent = isEventCompleted(eventData.status);
515584

516585
return (
517586
<div className={styles["event-detail-page"]}>
@@ -839,13 +908,25 @@ const EventDetail: React.FC = () => {
839908

840909
{/* 액션 버튼들 */}
841910
<div style={{ display: "flex", gap: "4px", flexDirection: "column" }}>
842-
<button
843-
className={styles["edit-button"]}
844-
onClick={() => navigate(`/events/${id}/edit`)}
845-
>
846-
<img src={editIcon} alt="수정 아이콘" />
847-
수정하기
848-
</button>
911+
{isCompletedEvent && (
912+
<button
913+
className={`${styles["edit-button"]} ${styles["download-button"]}`}
914+
onClick={handleDownloadReport}
915+
disabled={isDownloadingReport}
916+
>
917+
<img src={reportIcon} alt="보고서 다운로드 아이콘" />
918+
{isDownloadingReport ? "다운로드 중..." : "보고서 다운로드"}
919+
</button>
920+
)}
921+
{!isCompletedEvent && (
922+
<button
923+
className={styles["edit-button"]}
924+
onClick={() => navigate(`/events/${id}/edit`)}
925+
>
926+
<img src={editIcon} alt="수정 아이콘" />
927+
수정하기
928+
</button>
929+
)}
849930
<button
850931
className={`${styles["edit-button"]} ${styles["participants-button"]}`}
851932
onClick={() => navigate(`/events/${id}/participants`)}

0 commit comments

Comments
 (0)