import type { SubmissionItem, SubmissionRendererOptions } from './consts'; /** * @class SubmissionsRenderer * Renders the submissions list and handles pagination. */ export default class SubmissionsRenderer { public paginationDiv: HTMLDivElement; public submissionListDiv: HTMLDivElement; public prevPageLabel: string; public nextPageLabel: string; constructor(public options: SubmissionRendererOptions) { this.options.currentPage = this.options.currentPage || 1; this.submissionListDiv = document.createElement('div'); this.submissionListDiv.className = 'submissions__list'; this.paginationDiv = document.createElement('div'); this.paginationDiv.className = 'submissions__pagination'; this.prevPageLabel = window?.petitionerSubmissionSettings?.labels?.prevPage || 'Previous page'; this.nextPageLabel = window?.petitionerSubmissionSettings?.labels?.nextPage || 'Next page'; if (!this.options.wrapper) { throw new Error('Element not found'); } } /** * Format a MySQL datetime string (e.g. "2026-05-26 13:08:14") as a * human-readable date without time. */ public formatDate(val: string): string { // Take just the date portion to avoid timezone shifts const datePart = val.split(' ')[0]; const date = new Date(datePart + 'T00:00:00'); if (isNaN(date.getTime())) { return val; } return date.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric', }); } public attachEventListeners() { if (!this.paginationDiv) return; this.paginationDiv.addEventListener('click', async (event) => { const target = (event.target as HTMLElement).closest('button'); if (target && !target.disabled && target.dataset.page) { const page = parseInt(target.dataset.page, 10); if (!isNaN(page) && page !== this.options.currentPage) { this.options.currentPage = page; const newSubmissions = await this.options.onPageChange(page); this.options.submissions = newSubmissions; this.update(); } } }); } public render() { if (!this.options.submissions) { return; } this.options.wrapper.appendChild(this.submissionListDiv); this.options.wrapper.appendChild(this.paginationDiv); this.submissionListDiv.innerHTML = this.renderSubmissionsList(); this.paginationDiv.innerHTML = this.renderPagination(); this.attachEventListeners(); } public update() { // Update the submissions list this.submissionListDiv.innerHTML = this.renderSubmissionsList(); // Update the pagination completely to reflect ellipses and new active states this.paginationDiv.innerHTML = this.renderPagination(); } public renderSubmissionsList() { if ( !this.options.submissions || this.options.submissions.length === 0 ) { return ''; } return this.options.submissions .map((submission) => { return this.renderSubmissionItem(submission); }) .join(', '); } public renderSubmissionItem(submission: SubmissionItem): string { return `${submission.name}`; } private getPaginationRange( totalPages: number, currentPage: number ): (number | string)[] { const adjacentPages = 1; const range: number[] = []; const rangeWithDots: (number | string)[] = []; let lastNum = 0; const start = Math.max(1, currentPage - adjacentPages); const end = Math.min(totalPages, currentPage + adjacentPages); range.push(1); for (let i = start; i <= end; i++) { if (i > 1 && i < totalPages) { range.push(i); } } if (totalPages > 1) { range.push(totalPages); } // Insert ellipses where there are gaps for (const i of range) { if (lastNum > 0 && i - lastNum !== 1) { rangeWithDots.push('...'); } rangeWithDots.push(i); lastNum = i; } return rangeWithDots; } public renderPagination(): string { if ( !this.options.total || !this.options.perPage || !this.options.pagination ) { return ''; } const totalPages = Math.ceil(this.options.total / this.options.perPage); if (totalPages <= 1) { return ''; } const currentPage = this.options.currentPage || 1; let paginationHTML = '