From 7974881a3cf2f20f3a0eb09d6a93a358b64a40a8 Mon Sep 17 00:00:00 2001 From: florimondmanca Date: Tue, 19 Dec 2023 19:20:47 +0100 Subject: [PATCH] Progressively enhance payroll element filters form and download --- .vscode/settings.json | 2 +- package-lock.json | 2 +- package.json | 2 +- src/Infrastructure/Common/Utils/dateUtils.ts | 4 ++ .../GetPayrollElementsController.ts | 42 +++++++++++++---- .../DTO/GetPayrollElementsControllerDTO.ts | 4 +- .../NunjucksTemplates/NunjucksTemplates.ts | 3 +- src/assets/customElements/autoForm.js | 17 +++++-- src/assets/customElements/blobLink.js | 17 ------- .../customElements/fairCalendarFiltersForm.js | 10 +++-- src/assets/customElements/index.js | 4 +- .../pages/faircalendar/_filters_form.njk | 2 +- .../pages/payroll_elements/_filters_form.njk | 45 ++++++++++--------- .../pages/payroll_elements/index.njk | 2 +- src/translations/fr-FR.ftl | 2 +- 15 files changed, 94 insertions(+), 64 deletions(-) delete mode 100644 src/assets/customElements/blobLink.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 904ab4ea..a31ddce9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "editor.codeActionsOnSave": { - "source.fixAll": true + "source.fixAll": "explicit" }, "[javascript]": { "editor.formatOnSave": false diff --git a/package-lock.json b/package-lock.json index ebbefdab..b5cdcc57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,7 +77,7 @@ "tsconfig-paths": "^3.10.1" }, "engines": { - "node": "16" + "node": "18" } }, "node_modules/@ampproject/remapping": { diff --git a/package.json b/package.json index d96f8aef..ff49e862 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "server", "version": "1.0.0", "description": "ERP building for cooperatives", - "author": "Mathieu MARCHOIS", + "author": "Fairness", "license": "MIT", "scripts": { "prebuild": "rimraf dist", diff --git a/src/Infrastructure/Common/Utils/dateUtils.ts b/src/Infrastructure/Common/Utils/dateUtils.ts index b8cd41fd..2718c793 100644 --- a/src/Infrastructure/Common/Utils/dateUtils.ts +++ b/src/Infrastructure/Common/Utils/dateUtils.ts @@ -25,3 +25,7 @@ export const formatDate = (value: Date | string): string => { export const formatHtmlDate = (value: Date): string => { return format(value, 'yyyy-MM-dd'); }; + +export const formatHtmlYearMonth = (value: Date): string => { + return format(value, 'yyyy-MM'); +}; diff --git a/src/Infrastructure/HumanResource/PayrollElements/Controller/GetPayrollElementsController.ts b/src/Infrastructure/HumanResource/PayrollElements/Controller/GetPayrollElementsController.ts index d0e75db2..a61386f2 100644 --- a/src/Infrastructure/HumanResource/PayrollElements/Controller/GetPayrollElementsController.ts +++ b/src/Infrastructure/HumanResource/PayrollElements/Controller/GetPayrollElementsController.ts @@ -4,7 +4,9 @@ import { Get, UseGuards, Render, - Query + Query, + Res, + Param } from '@nestjs/common'; import { IQueryBus } from 'src/Application/IQueryBus'; import { GetUsersElementsQuery } from 'src/Application/HumanResource/Payslip/Query/GetUsersElementsQuery'; @@ -14,6 +16,9 @@ import { WithName } from 'src/Infrastructure/Common/ExtendedRouting/WithName'; import { TableCsvFactory } from 'src/Infrastructure/Tables/TableCsvFactory'; import { PayrollElementsTableFactory } from '../Table/PayrollElementsTableFactory'; import { GetPayrollElementsControllerDTO } from '../DTO/GetPayrollElementsControllerDTO'; +import { Response } from 'express'; +import { ITranslator } from 'src/Infrastructure/Translations/ITranslator'; +import { formatHtmlYearMonth } from 'src/Infrastructure/Common/Utils/dateUtils'; @Controller('app/people/payroll_elements') @UseGuards(IsAuthenticatedGuard) @@ -22,31 +27,50 @@ export class GetPayrollElementsController { @Inject('IQueryBus') private readonly queryBus: IQueryBus, private readonly tableCsvFactory: TableCsvFactory, - private readonly tableFactory: PayrollElementsTableFactory + private readonly tableFactory: PayrollElementsTableFactory, + @Inject('ITranslator') + private readonly translator: ITranslator ) {} @Get() @WithName('people_payroll_elements') @Render('pages/payroll_elements/index.njk') public async get(@Query() dto: GetPayrollElementsControllerDTO) { - const date = - dto.month !== undefined && dto.year !== undefined - ? new Date(dto.year, dto.month, 15) - : new Date(); + let date = new Date(); + + if (dto.year !== undefined && dto.month !== undefined) { + date = new Date(dto.year, dto.month - 1, 15); + } const elements: UserElementsView[] = await this.queryBus.execute( new GetUsersElementsQuery(date) ); const table = this.tableFactory.create(elements); - const csv = this.tableCsvFactory.toCsv(table); return { table, - csv, date, year: date.getUTCFullYear(), - month: date.getUTCMonth() + month: date.getUTCMonth() + 1 }; } + + @Get('/download/:year/:month') + @WithName('people_payroll_elements_download') + public async download( + @Param() dto: GetPayrollElementsControllerDTO, + @Res() res: Response + ) { + const { table, date } = await this.get(dto); + + const csv = this.tableCsvFactory.toCsv(table); + const filename = this.translator.translate('payroll-elements-filename', { + date: formatHtmlYearMonth(date) + }); + + res.setHeader('Content-Disposition', `attachment; filename=${filename}`); + res.setHeader('Content-Type', 'application/csv'); + res.send(csv); + } } diff --git a/src/Infrastructure/HumanResource/PayrollElements/DTO/GetPayrollElementsControllerDTO.ts b/src/Infrastructure/HumanResource/PayrollElements/DTO/GetPayrollElementsControllerDTO.ts index 9739b739..b4f619a2 100644 --- a/src/Infrastructure/HumanResource/PayrollElements/DTO/GetPayrollElementsControllerDTO.ts +++ b/src/Infrastructure/HumanResource/PayrollElements/DTO/GetPayrollElementsControllerDTO.ts @@ -3,9 +3,9 @@ import { IsNumber, IsOptional } from 'class-validator'; export class GetPayrollElementsControllerDTO { @IsNumber() @IsOptional() - public month: number; + public year: number; @IsNumber() @IsOptional() - public year: number; + public month: number; } diff --git a/src/Infrastructure/Templates/NunjucksTemplates/NunjucksTemplates.ts b/src/Infrastructure/Templates/NunjucksTemplates/NunjucksTemplates.ts index c064d01e..99780207 100644 --- a/src/Infrastructure/Templates/NunjucksTemplates/NunjucksTemplates.ts +++ b/src/Infrastructure/Templates/NunjucksTemplates/NunjucksTemplates.ts @@ -10,6 +10,7 @@ import { formatFullName } from '../../Common/Utils/formatUtils'; import { formatDate, formatHtmlDate, + formatHtmlYearMonth, minutesToHours } from '../../Common/Utils/dateUtils'; import { ArrayUtils } from '../../Common/Utils/ArrayUtils'; @@ -44,7 +45,7 @@ export class NunjucksTemplates implements ITemplates { value === 'now' ? new Date() : formatDate(value) ); env.addFilter('htmlDate', value => formatHtmlDate(value)); - env.addFilter('htmlYearMonth', value => formatHtmlDate(value).slice(0, 7)); + env.addFilter('htmlYearMonth', value => formatHtmlYearMonth(value)); env.addFilter('longMonth', (month: number) => this.translator.translate('common-month-long', { date: new Date(2023, month, 15) diff --git a/src/assets/customElements/autoForm.js b/src/assets/customElements/autoForm.js index 74cb6a04..c4cd5f24 100644 --- a/src/assets/customElements/autoForm.js +++ b/src/assets/customElements/autoForm.js @@ -1,11 +1,22 @@ // @ts-check import { onParsed } from '../lib/customElements'; -export default class extends HTMLFormElement { +export default class extends HTMLElement { connectedCallback() { onParsed(() => { - for (const formControl of this) { - formControl.addEventListener('change', () => this.requestSubmit()); + // Progressive enhancement: + // If this custom element activates, submit the form whenever + // a form control changes value, and remove any manual submit button. + + const form = /** @type {HTMLFormElement} */ (this.querySelector('form')); + const submitBtn = this.querySelector('button[type="submit"]'); + + for (const formControl of form.elements) { + formControl.addEventListener('change', () => form.requestSubmit()); + } + + if (submitBtn) { + submitBtn.remove(); } }); } diff --git a/src/assets/customElements/blobLink.js b/src/assets/customElements/blobLink.js deleted file mode 100644 index 1bd40c7d..00000000 --- a/src/assets/customElements/blobLink.js +++ /dev/null @@ -1,17 +0,0 @@ -// @ts-check - -export default class extends HTMLAnchorElement { - connectedCallback() { - const blobContent = this.dataset.blobContent; - - if (!blobContent) { - throw new Error('data-blob-content is missing or empty'); - } - - const blob = new Blob([blobContent], { - type: this.dataset.blobMimeType - }); - - this.href = URL.createObjectURL(blob); - } -} diff --git a/src/assets/customElements/fairCalendarFiltersForm.js b/src/assets/customElements/fairCalendarFiltersForm.js index fb263635..bcf88617 100644 --- a/src/assets/customElements/fairCalendarFiltersForm.js +++ b/src/assets/customElements/fairCalendarFiltersForm.js @@ -10,9 +10,13 @@ export default class extends HTMLElement { const form = /** @type {HTMLFormElement} */ (this.querySelector('form')); // Progressive enhancement: - // If this custom element activates, then we enable auto-submission and remove the manual submit button. - const submitBtn = /** @type {HTMLButtonElement} */ (this.querySelector('button[type="submit"]')); - const navigationTemplate = /** @type {HTMLTemplateElement} */ (this.querySelector('#faircalendar-filters-form-navigation')); + // If this custom element activates, enable auto-submission and remove the manual submit button. + const submitBtn = /** @type {HTMLButtonElement} */ (this.querySelector( + 'button[type="submit"]' + )); + const navigationTemplate = /** @type {HTMLTemplateElement} */ (this.querySelector( + '#faircalendar-filters-form-navigation' + )); const navigation = document.importNode(navigationTemplate.content, true); navigationTemplate.after(navigation); submitBtn.remove(); diff --git a/src/assets/customElements/index.js b/src/assets/customElements/index.js index e5a367c0..294cf02a 100644 --- a/src/assets/customElements/index.js +++ b/src/assets/customElements/index.js @@ -1,14 +1,12 @@ // @ts-check import autoForm from './autoForm'; -import blobLink from './blobLink'; import clipboardButton from './clipboardButton'; import eventForm from './eventForm'; import fairCalendarFiltersForm from './fairCalendarFiltersForm'; import navMenuButton from './navMenuButton'; import themeToggler from './themeToggler'; -customElements.define('pc-auto-form', autoForm, { extends: 'form' }); -customElements.define('pc-blob-link', blobLink, { extends: 'a' }); +customElements.define('pc-auto-form', autoForm); customElements.define('pc-clipboard-button', clipboardButton, { extends: 'button' }); diff --git a/src/templates/pages/faircalendar/_filters_form.njk b/src/templates/pages/faircalendar/_filters_form.njk index 502a03ba..85a189e7 100644 --- a/src/templates/pages/faircalendar/_filters_form.njk +++ b/src/templates/pages/faircalendar/_filters_form.njk @@ -37,7 +37,7 @@ diff --git a/src/templates/pages/payroll_elements/_filters_form.njk b/src/templates/pages/payroll_elements/_filters_form.njk index 65eb20c9..38ae5250 100644 --- a/src/templates/pages/payroll_elements/_filters_form.njk +++ b/src/templates/pages/payroll_elements/_filters_form.njk @@ -1,23 +1,28 @@ {% macro filters_form() %} -
-
- - -
+ + +
+ + +
-
- - -
- +
+ + +
+ + + +
{% endmacro %} diff --git a/src/templates/pages/payroll_elements/index.njk b/src/templates/pages/payroll_elements/index.njk index e6ab741a..791903dc 100644 --- a/src/templates/pages/payroll_elements/index.njk +++ b/src/templates/pages/payroll_elements/index.njk @@ -22,7 +22,7 @@
{{ filters_form() }} - + {{ 'payroll-elements-download'|trans }}
diff --git a/src/translations/fr-FR.ftl b/src/translations/fr-FR.ftl index 37bf3efb..1739ee21 100644 --- a/src/translations/fr-FR.ftl +++ b/src/translations/fr-FR.ftl @@ -1,4 +1,5 @@ common-form-save = Enregistrer +common-form-update = Mettre à jour common-form-delete = Supprimer common-view = Voir common-add = Ajouter @@ -63,7 +64,6 @@ faircalendar-time-title = Temps passé faircalendar-filters-month-title = Mois faircalendar-filters-year-title = Année faircalendar-filters-userId-title = Coopérateur·ice - salarié·e -faircalendar-filters-submit = Mettre à jour faircalendar-overview-days = {$days -> [0] 0 [1] 1 jour