diff --git a/src/controllers/publish.ts b/src/controllers/publish.ts index 3374bbc..8e17ee6 100644 --- a/src/controllers/publish.ts +++ b/src/controllers/publish.ts @@ -43,6 +43,8 @@ import { RelatedLinkDTO } from '../dtos/related-link'; import { DatasetProviderDTO } from '../dtos/dataset-provider'; import { ProviderSourceDTO } from '../dtos/provider-source'; import { ProviderDTO } from '../dtos/provider'; +import { generateSequenceForNumber } from '../utils/pagination'; +import { fileMimeTypeHandler } from '../utils/file-mimetype-handler'; import { TopicDTO } from '../dtos/topic'; import { DatasetTopicDTO } from '../dtos/dataset-topic'; import { nestTopics } from '../utils/nested-topics'; @@ -95,35 +97,7 @@ export const uploadFile = async (req: Request, res: Response, next: NextFunction throw new Error('errors.csv.invalid'); } const fileName = req.file.originalname; - if (req.file.mimetype === 'application/octet-stream') { - const ext = path.extname(req.file.originalname); - switch (ext) { - case '.parquet': - req.file.mimetype = 'application/vnd.apache.parquet'; - break; - case '.json': - req.file.mimetype = 'application/json'; - break; - case '.xls': - case '.xlsx': - req.file.mimetype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; - break; - case '.csv': - req.file.mimetype = 'text/csv'; - break; - default: - throw new Error(`unsupported format ${ext}`); - } - } else if (req.file.mimetype === 'application/x-gzip') { - const ext = req.file.originalname.split('.').reverse()[1]; - switch (ext) { - case 'json': - case 'csv': - break; - default: - throw new Error(`unsupported format ${ext}`); - } - } + req.file.mimetype = fileMimeTypeHandler(req.file.mimetype, req.file.originalname); const fileData = new Blob([req.file.buffer], { type: req.file.mimetype }); await req.swapi.uploadCSVToDataset(dataset.id, fileData, fileName); res.redirect(req.buildUrl(`/publish/${dataset.id}/preview`, req.language)); @@ -138,39 +112,6 @@ export const uploadFile = async (req: Request, res: Response, next: NextFunction res.render('publish/upload', { revisit, errors }); }; -// Special thanks ChatGPT... The GovUK pagination algorithm -function generateSequenceForNumber(highlight: number, end: number): (string | number)[] { - const sequence: (string | number)[] = []; - - // Validate input - if (highlight > end) { - throw new Error(`Highlighted number must be between 1 and ${end}.`); - } - - // Numbers before the highlighted number - if (highlight - 1 > 1) { - sequence.push(1, '...'); - sequence.push(highlight - 1); - } else { - // eslint-disable-next-line @typescript-eslint/naming-convention - sequence.push(...Array.from({ length: highlight - 1 }, (_, index) => index + 1)); - } - - // Highlighted number - sequence.push(highlight); - - // Numbers after the highlighted number - if (highlight + 1 < end) { - sequence.push(highlight + 1); - sequence.push('...', end); - } else { - // eslint-disable-next-line @typescript-eslint/naming-convention - sequence.push(...Array.from({ length: end - highlight }, (_, index) => highlight + 1 + index)); - } - - return sequence; -} - export const factTablePreview = async (req: Request, res: Response, next: NextFunction) => { const { dataset, revision, factTable } = res.locals; let errors: ViewErrDTO | undefined; diff --git a/src/middleware/services.ts b/src/middleware/services.ts index e49791a..8e4ab84 100644 --- a/src/middleware/services.ts +++ b/src/middleware/services.ts @@ -13,6 +13,7 @@ export const initServices = (req: Request, res: Response, next: NextFunction): v req.swapi = statsWalesApi; req.buildUrl = localeUrl; // for controllers res.locals.buildUrl = localeUrl; // for templates + res.locals.url = req.originalUrl; // Allows the passing through of the URL } next(); }; diff --git a/src/public/css/wales-overrides.css b/src/public/css/wales-overrides.css index e8f0e85..e336b41 100644 --- a/src/public/css/wales-overrides.css +++ b/src/public/css/wales-overrides.css @@ -75,10 +75,27 @@ h1, h2, h3, h4, p, ol, ul, a, table, .govuk-header__link, .govuk-task-list__stat } .table-display { + position: relative; overflow: auto; display: block; max-height: 50vh; width: 100%; + border: 1px solid #b1b4b6; + padding: 0; + margin-bottom: 1em; +} + +.table-display > table > thead > tr > th { + position: sticky; + top: 0; /* Don't forget this, required for the stickiness */ + box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.4); + background: #1d70b8; + color: white !important; +} + +.table-display > table > thead > tr > th:first-child, +.table-display > table > tbody > tr > td:first-child { + padding-left: 1em; } .top-links { @@ -96,3 +113,20 @@ h1, h2, h3, h4, p, ol, ul, a, table, .govuk-header__link, .govuk-task-list__stat pointer-events: none; z-index: 0; } + +.ignore-column { + background-color: #eeeeee; +} + +.line-number { + color: #7f7f7f; +} + +.region-subhead { + color: #aa1111; + font-family: Arial, Helvetica, sans-serif; + font-size: 14px; + letter-spacing: 0.08em; + line-height: 20px; + text-transform: uppercase; +} \ No newline at end of file diff --git a/src/routes/dataset.ts b/src/routes/dataset.ts index ba87a99..608a8ce 100644 --- a/src/routes/dataset.ts +++ b/src/routes/dataset.ts @@ -10,42 +10,10 @@ import { DatasetListItemDTO } from '../dtos/dataset-list-item'; import { hasError, factTableIdValidator } from '../validators'; import { RevisionDTO } from '../dtos/revision'; import { FactTableDto } from '../dtos/fact-table'; +import { generateSequenceForNumber } from '../utils/pagination'; export const dataset = Router(); -// Special thanks ChatGPT... The GovUK pagination algorithm -function generateSequenceForNumber(highlight: number, end: number): (string | number)[] { - const sequence: (string | number)[] = []; - - // Validate input - if (highlight > end) { - throw new Error(`Highlighted number must be between 1 and ${end}.`); - } - - // Numbers before the highlighted number - if (highlight - 1 > 1) { - sequence.push(1, '...'); - sequence.push(highlight - 1); - } else { - // eslint-disable-next-line @typescript-eslint/naming-convention - sequence.push(...Array.from({ length: highlight - 1 }, (_, index) => index + 1)); - } - - // Highlighted number - sequence.push(highlight); - - // Numbers after the highlighted number - if (highlight + 1 < end) { - sequence.push(highlight + 1); - sequence.push('...', end); - } else { - // eslint-disable-next-line @typescript-eslint/naming-convention - sequence.push(...Array.from({ length: end - highlight }, (_, index) => highlight + 1 + index)); - } - - return sequence; -} - dataset.get('/', async (req: Request, res: Response, next: NextFunction) => { try { const datasets: DatasetListItemDTO[] = await req.swapi.getActiveDatasetList(); diff --git a/src/utils/file-mimetype-handler.ts b/src/utils/file-mimetype-handler.ts new file mode 100644 index 0000000..a6e75ed --- /dev/null +++ b/src/utils/file-mimetype-handler.ts @@ -0,0 +1,31 @@ +import path from 'node:path'; + +export function fileMimeTypeHandler(mimetype: string, originalFileName: string): string { + let ext = 'unknown'; + if (mimetype === 'application/octet-stream') { + ext = path.extname(originalFileName); + switch (ext) { + case '.parquet': + return 'application/vnd.apache.parquet'; + case '.json': + return 'application/json'; + case '.xls': + case '.xlsx': + return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; + case '.csv': + return 'text/csv'; + default: + throw new Error(`unsupported format ${ext}`); + } + } else if (mimetype === 'application/x-gzip') { + ext = originalFileName.split('.').reverse()[1]; + switch (ext) { + case 'json': + case 'csv': + return 'application/x-gzip'; + default: + throw new Error(`unsupported format ${ext}`); + } + } + return mimetype; +} diff --git a/src/utils/pagination.ts b/src/utils/pagination.ts new file mode 100644 index 0000000..37d8000 --- /dev/null +++ b/src/utils/pagination.ts @@ -0,0 +1,64 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +// Special thanks ChatGPT... The GovUK pagination algorithm +export function generateSequenceForNumber(highlight: number, end: number): (string | number)[] { + const sequence: (string | number)[] = []; + + // Validate input + if (highlight < 1 || highlight > end) { + throw new Error(`Highlighted number must be between 1 and ${end}.`); + } + + if (end - 1 < 3) { + sequence.push( + ...Array.from({ length: end - 1 + 1 }, (_, index) => 1 + index).map((num) => + num === highlight ? num : num + ) + ); + return sequence; + } + + // Case 1: Highlight is within the first 3 pages + if (highlight <= 3) { + sequence.push(...Array.from({ length: 3 }, (_, index) => 1 + index)); + sequence[highlight - 1] = highlight; // Highlight the specific number + if (end > 4) { + sequence.push('...'); + sequence.push(end); + } + return sequence; + } + + // Case 2: Highlight is near or at the last 3 pages + if (highlight >= end - 2) { + if (end - 3 > 1) { + sequence.push(1, '...'); + } + for (let i = end - 3; i <= end; i++) { + if (i === highlight) { + sequence.push(i); + } else { + sequence.push(i); + } + } + return sequence; + } + + // Case 3: General case + if (highlight - 2 > 1) { + sequence.push(1, '...'); + sequence.push(highlight - 1); + } else { + sequence.push(...Array.from({ length: highlight - 1 }, (_, index) => index + 1)); + } + + sequence.push(highlight); // Highlight the number + + if (highlight + 1 < end) { + sequence.push(highlight + 1, '...', end); + } else { + sequence.push(...Array.from({ length: end - highlight }, (_, index) => highlight + 1 + index)); + } + + return sequence; +} diff --git a/src/views/partials/pagination.ejs b/src/views/partials/pagination.ejs new file mode 100644 index 0000000..e5d1659 --- /dev/null +++ b/src/views/partials/pagination.ejs @@ -0,0 +1,80 @@ +
+ <%= t('publish.preview.showing_rows', {start: locals.page_info.start_record, end: locals.page_info.end_record, total: locals.page_info.total_records}) %> +
+- <%= t('publish.preview.showing_rows', {start: locals.page_info.start_record, end: locals.page_info.end_record, total: locals.page_info.total_records}) %> -
-- <%= t('publish.preview.showing_rows', {start: locals.page_info.start_record, end: locals.page_info.end_record, total: locals.page_info.total_records}) %> -
-