326 lines
8.1 KiB
JavaScript
326 lines
8.1 KiB
JavaScript
import {
|
|
AllCommunityModule,
|
|
ModuleRegistry,
|
|
createGrid,
|
|
} from "ag-grid-community";
|
|
import "ag-grid-community/styles/ag-grid.css";
|
|
import "ag-grid-community/styles/ag-theme-quartz.css";
|
|
|
|
ModuleRegistry.registerModules([AllCommunityModule]);
|
|
|
|
const DEFAULT_SELECTOR = "[data-portfolio-list-panel]";
|
|
const DEFAULT_PAGE_SIZE = 5;
|
|
|
|
function toDate(value) {
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
|
|
const date = new Date(value);
|
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
}
|
|
|
|
function formatDate(value) {
|
|
const date = toDate(value);
|
|
if (!date) {
|
|
return "";
|
|
}
|
|
|
|
return new Intl.DateTimeFormat("ko-KR", {
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
}).format(date);
|
|
}
|
|
|
|
function formatRelativeDate(value) {
|
|
const date = toDate(value);
|
|
if (!date) {
|
|
return "";
|
|
}
|
|
|
|
const diffMs = Date.now() - date.getTime();
|
|
if (diffMs < 0) {
|
|
return formatDate(value);
|
|
}
|
|
|
|
const diffMinutes = Math.floor(diffMs / 60000);
|
|
if (diffMinutes < 1) {
|
|
return "방금 전";
|
|
}
|
|
|
|
if (diffMinutes < 60) {
|
|
return `${diffMinutes}분 전`;
|
|
}
|
|
|
|
const diffHours = Math.floor(diffMinutes / 60);
|
|
if (diffHours < 24) {
|
|
return `${diffHours}시간 전`;
|
|
}
|
|
|
|
return formatDate(value);
|
|
}
|
|
|
|
function getPostUrl(portfolioIdntfNo) {
|
|
return `/portfolio/${encodeURIComponent(portfolioIdntfNo)}`;
|
|
}
|
|
|
|
function createTitleCell(params) {
|
|
const link = document.createElement("a");
|
|
link.className = "portfolio-post-title-link";
|
|
link.href = getPostUrl(params.data.portfolioIdntfNo);
|
|
link.textContent = params.value || "";
|
|
link.title = params.value || "";
|
|
return link;
|
|
}
|
|
|
|
function createPageButton(label, options = {}) {
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = "portfolio-post-page-button";
|
|
button.textContent = label;
|
|
|
|
if (options.active) {
|
|
button.classList.add("is-active");
|
|
button.setAttribute("aria-current", "page");
|
|
}
|
|
|
|
if (options.disabled) {
|
|
button.disabled = true;
|
|
}
|
|
|
|
if (typeof options.onClick === "function") {
|
|
button.addEventListener("click", options.onClick);
|
|
}
|
|
|
|
return button;
|
|
}
|
|
|
|
function renderPagination(gridApi, paginationElement) {
|
|
if (!gridApi || !paginationElement) {
|
|
return;
|
|
}
|
|
|
|
const totalPages = gridApi.paginationGetTotalPages();
|
|
const currentPage = gridApi.paginationGetCurrentPage();
|
|
paginationElement.replaceChildren();
|
|
|
|
if (totalPages <= 0) {
|
|
return;
|
|
}
|
|
|
|
const maxVisiblePages = 10;
|
|
const currentPageGroup = Math.floor(currentPage / maxVisiblePages);
|
|
const startPage = currentPageGroup * maxVisiblePages;
|
|
const endPage = Math.min(startPage + maxVisiblePages, totalPages);
|
|
|
|
if (startPage > 0) {
|
|
paginationElement.appendChild(
|
|
createPageButton("이전", {
|
|
onClick: () => gridApi.paginationGoToPage(startPage - 1),
|
|
}),
|
|
);
|
|
}
|
|
|
|
for (let page = startPage; page < endPage; page += 1) {
|
|
paginationElement.appendChild(
|
|
createPageButton(String(page + 1), {
|
|
active: page === currentPage,
|
|
onClick: () => gridApi.paginationGoToPage(page),
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (endPage < totalPages) {
|
|
paginationElement.appendChild(
|
|
createPageButton("다음 >", {
|
|
onClick: () => gridApi.paginationGoToPage(endPage),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
function createGridOptions(rows, currentId, paginationElement) {
|
|
return {
|
|
rowData: rows,
|
|
columnDefs: [
|
|
{
|
|
field: "title",
|
|
headerName: "글 제목",
|
|
flex: 1,
|
|
minWidth: 260,
|
|
cellRenderer: createTitleCell,
|
|
},
|
|
{
|
|
field: "viewCount",
|
|
headerName: "조회수",
|
|
width: 96,
|
|
type: "numericColumn",
|
|
headerClass: "portfolio-post-number-header",
|
|
cellClass: "portfolio-post-number-cell",
|
|
},
|
|
{
|
|
field: "registeredAt",
|
|
headerName: "작성일",
|
|
width: 132,
|
|
valueFormatter: (params) => formatRelativeDate(params.value),
|
|
headerClass: "portfolio-post-date-header",
|
|
cellClass: "portfolio-post-date-cell",
|
|
},
|
|
],
|
|
defaultColDef: {
|
|
sortable: true,
|
|
resizable: false,
|
|
suppressMovable: true,
|
|
},
|
|
domLayout: "autoHeight",
|
|
headerHeight: 34,
|
|
rowHeight: 40,
|
|
pagination: true,
|
|
paginationPageSize: DEFAULT_PAGE_SIZE,
|
|
suppressPaginationPanel: true,
|
|
suppressCellFocus: true,
|
|
suppressDragLeaveHidesColumns: true,
|
|
suppressHorizontalScroll: true,
|
|
getRowId: (params) => params.data.portfolioIdntfNo,
|
|
getRowClass: (params) =>
|
|
params.data.portfolioIdntfNo === currentId ? "ag-row-current-post" : "",
|
|
onGridReady: (params) => renderPagination(params.api, paginationElement),
|
|
onPaginationChanged: (event) => renderPagination(event.api, paginationElement),
|
|
localeText: {
|
|
page: "페이지",
|
|
more: "더 보기",
|
|
to: "-",
|
|
of: "/",
|
|
next: "다음",
|
|
last: "마지막",
|
|
first: "처음",
|
|
previous: "이전",
|
|
loadingOoo: "불러오는 중...",
|
|
noRowsToShow: "등록된 글이 없습니다.",
|
|
pageSizeSelectorLabel: "줄 보기",
|
|
},
|
|
};
|
|
}
|
|
|
|
async function fetchPortfolioItems(listUrl) {
|
|
const response = await fetch(listUrl, {
|
|
headers: {
|
|
Accept: "application/json",
|
|
},
|
|
credentials: "same-origin",
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error("포트폴리오 목록을 불러오지 못했습니다.");
|
|
}
|
|
|
|
const rows = await response.json();
|
|
return Array.isArray(rows) ? rows : [];
|
|
}
|
|
|
|
function initPortfolioListPanel(selector = DEFAULT_SELECTOR) {
|
|
const panel = document.querySelector(selector);
|
|
if (!panel) {
|
|
return null;
|
|
}
|
|
|
|
const toggleButton = panel.querySelector("[data-portfolio-list-toggle]");
|
|
const body = panel.querySelector("[data-portfolio-list-body]");
|
|
const gridElement = panel.querySelector("[data-portfolio-list-grid]");
|
|
const paginationElement = panel.querySelector("[data-portfolio-pagination]");
|
|
const pageSizeSelect = panel.querySelector("[data-portfolio-page-size]");
|
|
const countElement = panel.querySelector(".portfolio-post-list-summary span");
|
|
const listUrl = panel.dataset.listUrl;
|
|
const currentId = panel.dataset.currentId || "";
|
|
const initialOpen = panel.dataset.initialOpen === "true";
|
|
|
|
if (!toggleButton || !body || !gridElement || !paginationElement || !listUrl) {
|
|
return null;
|
|
}
|
|
|
|
let isOpen = false;
|
|
let rowsPromise = null;
|
|
let gridApi = null;
|
|
|
|
const loadRows = () => {
|
|
rowsPromise ||= fetchPortfolioItems(listUrl);
|
|
return rowsPromise;
|
|
};
|
|
|
|
const renderRows = async () => {
|
|
const rows = await loadRows();
|
|
|
|
if (countElement) {
|
|
countElement.textContent = `${rows.length}개의 글`;
|
|
}
|
|
|
|
if (!gridApi) {
|
|
gridApi = createGrid(
|
|
gridElement,
|
|
createGridOptions(rows, currentId, paginationElement),
|
|
);
|
|
renderPagination(gridApi, paginationElement);
|
|
return;
|
|
}
|
|
|
|
gridApi.setGridOption("rowData", rows);
|
|
renderPagination(gridApi, paginationElement);
|
|
};
|
|
|
|
const setOpen = async (nextOpen) => {
|
|
isOpen = nextOpen;
|
|
toggleButton.textContent = isOpen ? "목록닫기" : "목록열기";
|
|
toggleButton.setAttribute("aria-expanded", String(isOpen));
|
|
body.hidden = !isOpen;
|
|
|
|
if (!isOpen) {
|
|
return;
|
|
}
|
|
|
|
toggleButton.disabled = true;
|
|
try {
|
|
await renderRows();
|
|
} catch (error) {
|
|
window.alert(error.message || "포트폴리오 목록을 불러오지 못했습니다.");
|
|
setOpen(false);
|
|
} finally {
|
|
toggleButton.disabled = false;
|
|
}
|
|
};
|
|
|
|
toggleButton.addEventListener("click", () => {
|
|
void setOpen(!isOpen);
|
|
});
|
|
|
|
pageSizeSelect?.addEventListener("change", () => {
|
|
if (!gridApi) {
|
|
return;
|
|
}
|
|
|
|
gridApi.setGridOption(
|
|
"paginationPageSize",
|
|
Number.parseInt(pageSizeSelect.value, 10) || DEFAULT_PAGE_SIZE,
|
|
);
|
|
gridApi.paginationGoToPage(0);
|
|
renderPagination(gridApi, paginationElement);
|
|
});
|
|
|
|
void setOpen(initialOpen);
|
|
|
|
return {
|
|
open: () => setOpen(true),
|
|
close: () => setOpen(false),
|
|
reload: async () => {
|
|
rowsPromise = null;
|
|
await renderRows();
|
|
},
|
|
};
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
initPortfolioListPanel();
|
|
});
|
|
|
|
export { initPortfolioListPanel };
|