feat(ai-cases-write): 등록된 사례 목록 5건 단위 페이징

- 최신순 정렬 후 페이지당 5개, 이전/다음 네비
- edit·page 쿼리 유지, 목록 영역 스타일 보강

Made-with: Cursor
This commit is contained in:
2026-04-08 20:20:41 +09:00
parent b9baa7ec12
commit 6126291257
3 changed files with 60 additions and 4 deletions

View File

@@ -677,6 +677,16 @@ button.ai-case-inline-link:hover {
margin-left: auto; margin-left: auto;
} }
.admin-story-list-pagination {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid #f3f4f6;
}
.btn-sm { .btn-sm {
padding: 6px 10px; padding: 6px 10px;
font-size: 13px; font-size: 13px;

View File

@@ -1167,6 +1167,8 @@ pageRouter.get("/ai-explore/task-checklist", (req, res) =>
opsState: normalizeOpsState(), opsState: normalizeOpsState(),
}) })
); );
const AI_SUCCESS_ADMIN_LIST_PAGE_SIZE = 5;
pageRouter.get("/ai-cases/write", (req, res) => { pageRouter.get("/ai-cases/write", (req, res) => {
if (!res.locals.adminMode) { if (!res.locals.adminMode) {
return res.status(403).send( return res.status(403).send(
@@ -1180,11 +1182,31 @@ pageRouter.get("/ai-cases/write", (req, res) => {
const m = meta.find((x) => x.slug === editSlug); const m = meta.find((x) => x.slug === editSlug);
if (m) story = enrichAiSuccessStory(m); if (m) story = enrichAiSuccessStory(m);
} }
const sortedStories = [...meta].sort((a, b) => {
const da = new Date(a.publishedAt || a.updatedAt || a.createdAt || 0);
const db = new Date(b.publishedAt || b.updatedAt || b.createdAt || 0);
return db - da;
});
const pageRaw = req.query.page;
const pageNum = Math.max(1, parseInt(Array.isArray(pageRaw) ? pageRaw[0] : pageRaw, 10) || 1);
const totalCount = sortedStories.length;
const totalPages = Math.max(1, Math.ceil(totalCount / AI_SUCCESS_ADMIN_LIST_PAGE_SIZE));
const currentPage = Math.min(pageNum, totalPages);
const start = (currentPage - 1) * AI_SUCCESS_ADMIN_LIST_PAGE_SIZE;
const allStories = sortedStories.slice(start, start + AI_SUCCESS_ADMIN_LIST_PAGE_SIZE);
const listPagination = {
page: currentPage,
totalPages,
totalCount,
hasPrev: currentPage > 1,
hasNext: currentPage < totalPages,
};
res.render("ai-cases-write", { res.render("ai-cases-write", {
activeMenu: "ai-cases", activeMenu: "ai-cases",
adminMode: true, adminMode: true,
story, story,
allStories: meta, allStories,
listPagination,
editSlug: editSlug || null, editSlug: editSlug || null,
}); });
}); });

View File

@@ -46,18 +46,42 @@
<p class="muted admin-hint">슬러그는 URL에 쓰이므로 영문·숫자·하이픈만 사용하세요. <strong>원문 PDF 경로</strong>(<code>/public/...</code>)가 있으면 상세는 PDF 페이지 이미지로 보여 주며, 이때 <strong>본문(Markdown)은 비워도 됩니다</strong>. PDF가 없을 때는 본문이 필수입니다.</p> <p class="muted admin-hint">슬러그는 URL에 쓰이므로 영문·숫자·하이픈만 사용하세요. <strong>원문 PDF 경로</strong>(<code>/public/...</code>)가 있으면 상세는 PDF 페이지 이미지로 보여 주며, 이때 <strong>본문(Markdown)은 비워도 됩니다</strong>. PDF가 없을 때는 본문이 필수입니다.</p>
</section> </section>
<% if (allStories.length) { %> <% if (typeof listPagination !== 'undefined' && listPagination.totalCount > 0) { %>
<section class="panel"> <section class="panel">
<h2>등록된 사례</h2> <div class="section-head" style="margin-bottom:8px;align-items:baseline;">
<h2 style="margin:0;">등록된 사례</h2>
<% if (listPagination.totalPages > 1) { %>
<span class="muted" style="font-size:13px;">총 <%= listPagination.totalCount %>건 · <%= listPagination.page %>/<%= listPagination.totalPages %> 페이지</span>
<% } else { %>
<span class="muted" style="font-size:13px;">총 <%= listPagination.totalCount %>건</span>
<% } %>
</div>
<ul class="admin-story-list"> <ul class="admin-story-list">
<% allStories.forEach(function(s) { %> <% allStories.forEach(function(s) { %>
<li> <li>
<a href="/ai-cases/write?edit=<%= s.slug %>"><strong><%= s.title %></strong></a> <% var _listPageQ = listPagination.page > 1 ? ('&page=' + listPagination.page) : ''; %>
<a href="/ai-cases/write?edit=<%= encodeURIComponent(s.slug) %><%= _listPageQ %>"><strong><%= s.title %></strong></a>
<span class="muted">/<%= s.slug %></span> <span class="muted">/<%= s.slug %></span>
<button type="button" class="btn-ghost btn-sm js-delete-story" data-id="<%= s.id %>" data-title="<%= s.title %>">삭제</button> <button type="button" class="btn-ghost btn-sm js-delete-story" data-id="<%= s.id %>" data-title="<%= s.title %>">삭제</button>
</li> </li>
<% }) %> <% }) %>
</ul> </ul>
<% if (listPagination.totalPages > 1) { %>
<nav class="admin-story-list-pagination" aria-label="등록된 사례 페이지">
<% var _pq = editSlug ? ('edit=' + encodeURIComponent(editSlug) + '&') : ''; %>
<% if (listPagination.hasPrev) { %>
<a class="btn-ghost btn-sm" href="/ai-cases/write?<%= _pq %>page=<%= listPagination.page - 1 %>">이전</a>
<% } else { %>
<span class="btn-ghost btn-sm" style="opacity:0.45;pointer-events:none;">이전</span>
<% } %>
<span class="muted" style="font-size:13px;padding:0 8px;"><%= listPagination.page %> / <%= listPagination.totalPages %></span>
<% if (listPagination.hasNext) { %>
<a class="btn-ghost btn-sm" href="/ai-cases/write?<%= _pq %>page=<%= listPagination.page + 1 %>">다음</a>
<% } else { %>
<span class="btn-ghost btn-sm" style="opacity:0.45;pointer-events:none;">다음</span>
<% } %>
</nav>
<% } %>
</section> </section>
<% } %> <% } %>