Files
ai_platform/views/ai-use-case-submission-detail.ejs
dsyoon f7df18f181 feat: XAVIS 브랜드 이미지를 NCue 로고·favicon으로 교체
로그인·네비·F-Scan 로고, favicon, 페이지 타이틀, 인증 메일 브랜딩을 NCue로 통일.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:46:40 +09:00

309 lines
15 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<%- include('partials/favicon') %>
<title><%= title %> - AI 활용 사례 - NCue</title>
<link rel="stylesheet" href="/public/styles.css" />
</head>
<body>
<div class="app-shell">
<%- include('partials/nav', { activeMenu: 'ai-cases' }) %>
<div class="content-area">
<main class="viewer-wrap ai-case-viewer">
<a href="/ai-cases" class="back-link">← AI 활용 사례로 돌아가기</a>
<% if (typeof updatedOk !== "undefined" && updatedOk) { %>
<p class="form-message" style="margin: 0 0 12px; color: #047857">수정이 저장되었습니다.</p>
<% } %>
<h1><%= title %></h1>
<p class="description">일반 제출 사례 · <%= author %><% if (submitterEmail) { %> (<%= submitterEmail %>)<% } %></p>
<div class="ppt-tools ai-case-ppt-tools">
<div class="ai-case-tools-meta">
<span>조회 <b id="submissionViewCount"><%= typeof viewCount !== "undefined" ? viewCount : 0 %></b></span>
<span class="ai-case-tool-sep" aria-hidden="true">|</span>
<button
type="button"
class="prompt-lib-like ai-case-submission-like<%= typeof myLike !== "undefined" && myLike ? " is-liked" : "" %>"
id="submissionLikeBtn"
title="<%= typeof opsUserEmail !== "undefined" && opsUserEmail ? "좋아요" : "로그인 후 사용" %>"
<% if (typeof opsUserEmail === "undefined" || !opsUserEmail) { %>disabled<% } %>
>
<span class="prompt-lib-like-icon" aria-hidden="true">♥</span>
<span id="submissionLikeCount"><%= typeof likeCount !== "undefined" ? likeCount : 0 %></span>
</button>
<span class="ai-case-tool-sep" aria-hidden="true">|</span>
<span><%= typeof department !== "undefined" ? department : "일반 제출" %> · <%= author %><% if (publishedAt) { %> · <%= publishedAt %><% } %></span>
<% if (typeof canEditSubmission !== "undefined" && canEditSubmission && typeof submissionId !== "undefined" && submissionId) { %>
<span class="ai-case-tool-sep" aria-hidden="true">|</span>
<a href="/ai-cases/compose?edit=<%= encodeURIComponent(submissionId) %>" class="ai-case-inline-link">수정하기</a>
<% } %>
</div>
</div>
<% if ((tags || []).length) { %>
<div class="tag-row ai-case-tag-row">
<% (tags || []).forEach((oneTag) => { %>
<span class="tag-chip">#<%= oneTag %></span>
<% }) %>
</div>
<% } %>
<section class="slide-list">
<article class="slide-card">
<header>
<h2>1. Situation (배경)</h2>
</header>
<div class="chat-md-body success-detail-body-in-card success-submission-html"><%- situationHtml %></div>
</article>
<article class="slide-card">
<header>
<h2>2. Task (과제/목표)</h2>
</header>
<div class="chat-md-body success-detail-body-in-card success-submission-html"><%- taskHtml %></div>
</article>
<article class="slide-card">
<header>
<h2>3. Action (행동)</h2>
</header>
<div class="chat-md-body success-detail-body-in-card success-submission-html"><%- actionHtml %></div>
</article>
<article class="slide-card">
<header>
<h2>4. Result (결과)</h2>
</header>
<div class="chat-md-body success-detail-body-in-card success-submission-html"><%- resultHtml %></div>
</article>
</section>
<% if (typeof thumbnailImages !== "undefined" && thumbnailImages && thumbnailImages.length) { %>
<section class="success-submission-cover-section" id="submissionScreenshots">
<div class="success-submission-cover-header">
<div>
<h2 class="success-submission-cover-title">실행 화면</h2>
<p class="success-submission-cover-hint">이미지를 클릭하면 원본 크기로 자세히 볼 수 있습니다.</p>
</div>
<% if (thumbnailImages.length > 1) { %>
<div class="slide-layout-toggle" role="group" aria-label="실행 화면 단 수">
<span class="slide-layout-toggle-label">보기</span>
<button type="button" class="slide-layout-btn js-screenshot-cols-btn" data-cols="1" aria-pressed="false">1단</button>
<button type="button" class="slide-layout-btn js-screenshot-cols-btn is-active" data-cols="2" aria-pressed="true">2단</button>
<button type="button" class="slide-layout-btn js-screenshot-cols-btn" data-cols="4" aria-pressed="false">4단</button>
</div>
<% } %>
</div>
<div class="success-submission-cover-grid success-submission-cover-grid--cols-2" id="submissionScreenshotGrid">
<% thumbnailImages.forEach(function (t, idx) { var tfn = (t.originalName || "실행 화면").replace(/[<>"]/g, ""); %>
<figure class="success-submission-cover-item">
<button
type="button"
class="success-submission-screenshot-btn js-screenshot-open"
data-screenshot-index="<%= idx %>"
aria-label="<%= tfn %> 크게 보기"
>
<img src="<%= t.relativePath %>" alt="<%= tfn %>" loading="lazy" decoding="async" />
</button>
<figcaption class="success-submission-screenshot-caption"><%= tfn %></figcaption>
</figure>
<% }); %>
</div>
</section>
<% } else if (coverImageUrl) { %>
<section class="success-submission-cover-section success-submission-cover--single" id="submissionScreenshots">
<h2 class="success-submission-cover-title">실행 화면</h2>
<p class="success-submission-cover-hint">이미지를 클릭하면 원본 크기로 자세히 볼 수 있습니다.</p>
<figure class="success-submission-cover-item">
<button type="button" class="success-submission-screenshot-btn js-screenshot-open" data-screenshot-index="0" aria-label="실행 화면 크게 보기">
<img src="<%= coverImageUrl %>" alt="실행 화면" loading="lazy" decoding="async" />
</button>
</figure>
</section>
<% } %>
<% if (attachments && attachments.length) { %>
<section class="slide-card" style="margin-top: 12px">
<header>
<h2>첨부 파일</h2>
</header>
<ul class="success-submission-attach-list">
<% attachments.forEach((a) => { %>
<li>
<a href="<%= a.relativePath %>" target="_blank" rel="noopener noreferrer"><%= a.originalName || "파일" %></a>
</li>
<% }) %>
</ul>
</section>
<% } %>
</main>
</div>
</div>
<div class="ai-case-lightbox" id="aiCaseLightbox" hidden role="dialog" aria-modal="true" aria-labelledby="aiCaseLightboxCaption">
<button type="button" class="ai-case-lightbox__close" id="aiCaseLightboxClose" aria-label="닫기">×</button>
<button type="button" class="ai-case-lightbox__nav ai-case-lightbox__nav--prev" id="aiCaseLightboxPrev" aria-label="이전 이미지"></button>
<button type="button" class="ai-case-lightbox__nav ai-case-lightbox__nav--next" id="aiCaseLightboxNext" aria-label="다음 이미지"></button>
<div class="ai-case-lightbox__dialog">
<img class="ai-case-lightbox__img" id="aiCaseLightboxImg" alt="" />
<p class="ai-case-lightbox__caption" id="aiCaseLightboxCaption"></p>
<p class="ai-case-lightbox__meta" id="aiCaseLightboxMeta"></p>
</div>
</div>
<script>
(function () {
var screenshotSection = document.getElementById("submissionScreenshots");
if (screenshotSection) {
var openButtons = screenshotSection.querySelectorAll(".js-screenshot-open");
var lightbox = document.getElementById("aiCaseLightbox");
var lightboxImg = document.getElementById("aiCaseLightboxImg");
var lightboxCaption = document.getElementById("aiCaseLightboxCaption");
var lightboxMeta = document.getElementById("aiCaseLightboxMeta");
var lightboxClose = document.getElementById("aiCaseLightboxClose");
var lightboxPrev = document.getElementById("aiCaseLightboxPrev");
var lightboxNext = document.getElementById("aiCaseLightboxNext");
var slides = [];
var activeIndex = 0;
var lastFocus = null;
openButtons.forEach(function (btn) {
var img = btn.querySelector("img");
if (!img) return;
slides.push({
src: img.getAttribute("src") || "",
alt: img.getAttribute("alt") || "실행 화면",
});
});
var screenshotGrid = document.getElementById("submissionScreenshotGrid");
var colButtons = screenshotSection.querySelectorAll(".js-screenshot-cols-btn");
var colsStorageKey = "aiCaseScreenshotColumns";
function applyScreenshotCols(n) {
if (!screenshotGrid) return;
screenshotGrid.classList.remove(
"success-submission-cover-grid--cols-1",
"success-submission-cover-grid--cols-2",
"success-submission-cover-grid--cols-4",
);
screenshotGrid.classList.add("success-submission-cover-grid--cols-" + n);
colButtons.forEach(function (btn) {
var on = btn.getAttribute("data-cols") === String(n);
btn.setAttribute("aria-pressed", on ? "true" : "false");
btn.classList.toggle("is-active", on);
});
try {
localStorage.setItem(colsStorageKey, String(n));
} catch (e) {}
}
if (screenshotGrid && colButtons.length) {
var savedCols = null;
try {
savedCols = localStorage.getItem(colsStorageKey);
} catch (e) {}
var initialCols = savedCols === "1" || savedCols === "2" || savedCols === "4" ? savedCols : "2";
applyScreenshotCols(initialCols);
colButtons.forEach(function (btn) {
btn.addEventListener("click", function () {
var n = btn.getAttribute("data-cols");
if (n === "1" || n === "2" || n === "4") applyScreenshotCols(n);
});
});
}
function renderLightbox(index) {
if (!slides.length || !lightboxImg) return;
activeIndex = (index + slides.length) % slides.length;
var slide = slides[activeIndex];
lightboxImg.src = slide.src;
lightboxImg.alt = slide.alt;
if (lightboxCaption) lightboxCaption.textContent = slide.alt;
if (lightboxMeta) {
lightboxMeta.textContent = slides.length > 1 ? activeIndex + 1 + " / " + slides.length : "";
}
if (lightboxPrev) lightboxPrev.hidden = slides.length <= 1;
if (lightboxNext) lightboxNext.hidden = slides.length <= 1;
}
function openLightbox(index) {
if (!lightbox || !slides.length) return;
lastFocus = document.activeElement;
renderLightbox(index);
lightbox.hidden = false;
document.body.style.overflow = "hidden";
if (lightboxClose) lightboxClose.focus();
}
function closeLightbox() {
if (!lightbox) return;
lightbox.hidden = true;
document.body.style.overflow = "";
if (lightboxImg) lightboxImg.removeAttribute("src");
if (lastFocus && typeof lastFocus.focus === "function") lastFocus.focus();
}
openButtons.forEach(function (btn) {
btn.addEventListener("click", function () {
var idx = parseInt(btn.getAttribute("data-screenshot-index") || "0", 10);
openLightbox(Number.isFinite(idx) ? idx : 0);
});
});
if (lightboxClose) lightboxClose.addEventListener("click", closeLightbox);
if (lightboxPrev) {
lightboxPrev.addEventListener("click", function () {
renderLightbox(activeIndex - 1);
});
}
if (lightboxNext) {
lightboxNext.addEventListener("click", function () {
renderLightbox(activeIndex + 1);
});
}
if (lightbox) {
lightbox.addEventListener("click", function (ev) {
if (ev.target === lightbox) closeLightbox();
});
}
document.addEventListener("keydown", function (ev) {
if (!lightbox || lightbox.hidden) return;
if (ev.key === "Escape") closeLightbox();
if (ev.key === "ArrowLeft" && lightboxPrev && !lightboxPrev.hidden) renderLightbox(activeIndex - 1);
if (ev.key === "ArrowRight" && lightboxNext && !lightboxNext.hidden) renderLightbox(activeIndex + 1);
});
}
var likeBtn = document.getElementById("submissionLikeBtn");
var likeCountEl = document.getElementById("submissionLikeCount");
var submissionId = <%- JSON.stringify(typeof submissionId !== "undefined" ? submissionId : null) %>;
if (!likeBtn || !submissionId) return;
likeBtn.addEventListener("click", function () {
if (likeBtn.disabled) return;
likeBtn.disabled = true;
fetch("/api/ai-use-case-submissions/" + encodeURIComponent(submissionId) + "/like/toggle", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "same-origin",
body: "{}",
})
.then(function (r) {
return r.json().then(function (j) {
return { ok: r.ok, j: j };
});
})
.then(function (o) {
if (o.ok && o.j && o.j.ok) {
if (likeCountEl) likeCountEl.textContent = String(o.j.likeCount || 0);
likeBtn.classList.toggle("is-liked", !!o.j.liked);
} else if (o.j && o.j.error) {
alert(o.j.error);
}
})
.catch(function () {
alert("좋아요 처리에 실패했습니다.");
})
.finally(function () {
likeBtn.disabled = false;
});
});
})();
</script>
</body>
</html>