diff --git a/package.json b/package.json index 262c7fd5..c463c222 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "prebuild": "rimraf dist", "build": "nest build", - "_esbuild": "esbuild src/assets/main.js src/assets/faircalendar.js --bundle --splitting --outdir=dist/public --format=esm", + "_esbuild": "esbuild src/assets/main.js --bundle --splitting --outdir=dist/public --format=esm", "assets:build": "npm run _esbuild -- --minify", "assets:watch": "npm run _esbuild -- --watch", "format": "prettier --write \"src/**/*.ts\" \"src/assets/**/*\" \"e2e/**/*.js\"", diff --git a/src/Application/IDateUtils.ts b/src/Application/IDateUtils.ts index 09b0f738..9d2ca39c 100644 --- a/src/Application/IDateUtils.ts +++ b/src/Application/IDateUtils.ts @@ -3,6 +3,7 @@ import { MonthDate } from './Common/MonthDate'; export interface IDateUtils { format(date: Date, format: string): string; getDaysInMonth(date: Date): number; + getWeekDaysOfMonth(date: Date): Date[]; isWeekend(date: Date): boolean; getCurrentDate(): Date; getCurrentDateToISOString(): string; diff --git a/src/Infrastructure/Adapter/DateUtilsAdapter.ts b/src/Infrastructure/Adapter/DateUtilsAdapter.ts index 2dfc4116..5bfb5225 100644 --- a/src/Infrastructure/Adapter/DateUtilsAdapter.ts +++ b/src/Infrastructure/Adapter/DateUtilsAdapter.ts @@ -4,7 +4,8 @@ import { isWeekend as fnsIsWeekend, getDaysInMonth as fnsGetDaysInMonth, eachDayOfInterval, - addDays + addDays, + isWeekend } from 'date-fns'; import { MonthDate } from 'src/Application/Common/MonthDate'; import { IDateUtils } from 'src/Application/IDateUtils'; @@ -55,6 +56,17 @@ export class DateUtilsAdapter implements IDateUtils { return fnsGetDaysInMonth(date); } + public getWeekDaysOfMonth(date: Date): Date[] { + return eachDayOfInterval({ + start: new Date(date.getFullYear(), date.getUTCMonth(), 1), + end: new Date( + date.getFullYear(), + date.getUTCMonth(), + this.getDaysInMonth(date) + ) + }).filter(day => !isWeekend(day)); + } + public isWeekend(date: Date): boolean { return fnsIsWeekend(date); } diff --git a/src/Infrastructure/Common/Utils/ArrayUtils.ts b/src/Infrastructure/Common/Utils/ArrayUtils.ts index 9dd0c060..727b7f23 100644 --- a/src/Infrastructure/Common/Utils/ArrayUtils.ts +++ b/src/Infrastructure/Common/Utils/ArrayUtils.ts @@ -15,4 +15,15 @@ export class ArrayUtils { public static zip(left: T[], right: U[]): [T, U][] { return left.map((value, idx) => [value, right[idx]]); } + + public static groupBy( + xs: Array, + key: (x: T) => string + ): { [key: string]: T } { + // Credit: https://stackoverflow.com/a/34890276 + return xs.reduce(function(rv, x) { + (rv[key(x)] = rv[key(x)] || []).push(x); + return rv; + }, {}); + } } diff --git a/src/Infrastructure/Common/Utils/dateUtils.ts b/src/Infrastructure/Common/Utils/dateUtils.ts index 2718c793..9b87ae30 100644 --- a/src/Infrastructure/Common/Utils/dateUtils.ts +++ b/src/Infrastructure/Common/Utils/dateUtils.ts @@ -1,4 +1,5 @@ import { format, parseISO } from 'date-fns'; +import { fr } from 'date-fns/locale'; export const minutesToHours = (value: number): string => { const hours = Math.floor(value / 60); @@ -29,3 +30,10 @@ export const formatHtmlDate = (value: Date): string => { export const formatHtmlYearMonth = (value: Date): string => { return format(value, 'yyyy-MM'); }; + +export const formatEventDate = (value: Date | string): string => { + if (typeof value === 'string') { + value = parseISO(value); + } + return format(value, 'eee dd', { locale: fr }); +}; diff --git a/src/Infrastructure/FairCalendar/Controller/AddEventController.ts b/src/Infrastructure/FairCalendar/Controller/AddEventController.ts index ee3f58ca..2b5d3918 100644 --- a/src/Infrastructure/FairCalendar/Controller/AddEventController.ts +++ b/src/Infrastructure/FairCalendar/Controller/AddEventController.ts @@ -24,6 +24,7 @@ import { GetTasksQuery } from 'src/Application/Task/Query/GetTasksQuery'; import { GetProjectsQuery } from 'src/Application/Project/Query/GetProjectsQuery'; import { GetCooperativeQuery } from 'src/Application/Settings/Query/GetCooperativeQuery'; import { ArrayUtils } from 'src/Infrastructure/Common/Utils/ArrayUtils'; +import { RouteNameResolver } from 'src/Infrastructure/Common/ExtendedRouting/RouteNameResolver'; @Controller('app/faircalendar/events/add') @UseGuards(IsAuthenticatedGuard) @@ -32,7 +33,8 @@ export class AddEventController { @Inject('ICommandBus') private readonly commandBus: ICommandBus, @Inject('IQueryBus') - private readonly queryBus: IQueryBus + private readonly queryBus: IQueryBus, + private readonly resolver: RouteNameResolver ) {} @Get(':startDate--:endDate') @@ -97,7 +99,7 @@ export class AddEventController { ) ); - res.redirect(303, '/app/faircalendar'); + res.redirect(303, this.resolver.resolve('faircalendar_index')); } catch (e) { throw new BadRequestException(e.message); } diff --git a/src/Infrastructure/FairCalendar/Controller/DeleteEventController.ts b/src/Infrastructure/FairCalendar/Controller/DeleteEventController.ts index 3ce4bf44..9534f42c 100644 --- a/src/Infrastructure/FairCalendar/Controller/DeleteEventController.ts +++ b/src/Infrastructure/FairCalendar/Controller/DeleteEventController.ts @@ -15,13 +15,15 @@ import { User } from 'src/Domain/HumanResource/User/User.entity'; import { IdDTO } from 'src/Infrastructure/Common/DTO/IdDTO'; import { WithName } from 'src/Infrastructure/Common/ExtendedRouting/WithName'; import { DeleteEventCommand } from 'src/Application/FairCalendar/Command/DeleteEventCommand'; +import { RouteNameResolver } from 'src/Infrastructure/Common/ExtendedRouting/RouteNameResolver'; @Controller('app/faircalendar/events/delete') @UseGuards(IsAuthenticatedGuard) export class DeleteEventController { constructor( @Inject('ICommandBus') - private readonly commandBus: ICommandBus + private readonly commandBus: ICommandBus, + private readonly resolver: RouteNameResolver ) {} @Post(':id') @@ -33,7 +35,7 @@ export class DeleteEventController { ) { try { await this.commandBus.execute(new DeleteEventCommand(dto.id, user)); - res.redirect(303, '/app/faircalendar'); + res.redirect(303, this.resolver.resolve('faircalendar_index')); } catch (e) { throw new BadRequestException(e.message); } diff --git a/src/Infrastructure/FairCalendar/Controller/EditEventController.ts b/src/Infrastructure/FairCalendar/Controller/EditEventController.ts index c6cf7153..10862d99 100644 --- a/src/Infrastructure/FairCalendar/Controller/EditEventController.ts +++ b/src/Infrastructure/FairCalendar/Controller/EditEventController.ts @@ -13,7 +13,6 @@ import { import { ICommandBus } from 'src/Application/ICommandBus'; import { IsAuthenticatedGuard } from 'src/Infrastructure/HumanResource/User/Security/IsAuthenticatedGuard'; import { WithName } from 'src/Infrastructure/Common/ExtendedRouting/WithName'; -import { AddEventControllerDTO } from '../DTO/AddEventControllerDTO'; import { LoggedUser } from 'src/Infrastructure/HumanResource/User/Decorator/LoggedUser'; import { User } from 'src/Domain/HumanResource/User/User.entity'; import { IQueryBus } from 'src/Application/IQueryBus'; @@ -30,6 +29,7 @@ import { IdDTO } from 'src/Infrastructure/Common/DTO/IdDTO'; import { EditEventDTO } from '../DTO/EditEventDTO'; import { UpdateEventCommand } from 'src/Application/FairCalendar/Command/UpdateEventCommand'; import { GetEventByIdQuery } from 'src/Application/FairCalendar/Query/GetEventByIdQuery'; +import { RouteNameResolver } from 'src/Infrastructure/Common/ExtendedRouting/RouteNameResolver'; @Controller('app/faircalendar/events/edit') @UseGuards(IsAuthenticatedGuard) @@ -38,7 +38,8 @@ export class EditEventController { @Inject('ICommandBus') private readonly commandBus: ICommandBus, @Inject('IQueryBus') - private readonly queryBus: IQueryBus + private readonly queryBus: IQueryBus, + private readonly resolver: RouteNameResolver ) {} @Get(':id') @@ -99,7 +100,7 @@ export class EditEventController { ) ); - res.redirect(303, '/app/faircalendar'); + res.redirect(303, this.resolver.resolve('faircalendar_index')); } catch (e) { throw new BadRequestException(e.message); } diff --git a/src/Infrastructure/FairCalendar/Controller/FairCalendarController.ts b/src/Infrastructure/FairCalendar/Controller/FairCalendarController.ts index 106b1db7..d4b53815 100644 --- a/src/Infrastructure/FairCalendar/Controller/FairCalendarController.ts +++ b/src/Infrastructure/FairCalendar/Controller/FairCalendarController.ts @@ -4,8 +4,10 @@ import { Inject, Query, Render, + Req, UseGuards } from '@nestjs/common'; +import { Request } from 'express'; import { User } from 'src/Domain/HumanResource/User/User.entity'; import { GetMonthlyFairCalendarQuery } from 'src/Application/FairCalendar/Query/GetMonthlyFairCalendarQuery'; import { IQueryBus } from 'src/Application/IQueryBus'; @@ -20,6 +22,9 @@ import { GetUsersQuery } from 'src/Application/HumanResource/User/Query/GetUsers import { FairCalendarView } from 'src/Application/FairCalendar/View/FairCalendarView'; import { FairCalendarOverviewFactory } from 'src/Domain/FairCalendar/FairCalendarOverviewFactory'; import { FairCalendarOverviewTableFactory } from '../Table/FairCalendarOverviewTableFactory'; +import { IDateUtils } from 'src/Application/IDateUtils'; +import { ArrayUtils } from 'src/Infrastructure/Common/Utils/ArrayUtils'; +import { RouteNameResolver } from 'src/Infrastructure/Common/ExtendedRouting/RouteNameResolver'; @Controller('app/faircalendar') @UseGuards(IsAuthenticatedGuard) @@ -29,8 +34,11 @@ export class FairCalendarController { private readonly queryBus: IQueryBus, @Inject('ITranslator') private readonly translator: ITranslator, + @Inject('IDateUtils') + private readonly dateUtils: IDateUtils, private overviewFactory: FairCalendarOverviewFactory, - private overviewTableFactory: FairCalendarOverviewTableFactory + private overviewTableFactory: FairCalendarOverviewTableFactory, + private readonly resolver: RouteNameResolver ) {} @Get() @@ -38,7 +46,8 @@ export class FairCalendarController { @Render('pages/faircalendar/index.njk') public async get( @Query() dto: FairCalendarControllerDTO, - @LoggedUser() user: User + @LoggedUser() user: User, + @Req() req: Request ) { let date = new Date(); @@ -87,7 +96,9 @@ export class FairCalendarController { ...(event.id ? { extendedProps: { - url: `/app/faircalendar/events/edit/${event.id}` + url: this.resolver.resolve('faircalendar_events_edit', { + id: event.id + }) } } : {}), @@ -97,6 +108,18 @@ export class FairCalendarController { }; }); + const eventsByStartDate = ArrayUtils.groupBy( + fullCalendarEvents, + event => event.start + ); + + const listViewDays = this.dateUtils.getWeekDaysOfMonth(date).map(day => { + return [ + day, + eventsByStartDate[this.dateUtils.format(day, 'yyyy-MM-dd')] || [] + ]; + }); + return { users, overviewTable, @@ -104,7 +127,9 @@ export class FairCalendarController { date, currentMonth: date.getMonth() + 1, currentYear: date.getFullYear(), - userId + userId, + viewName: req.cookies.faircalendar_view, + listViewDays }; } } diff --git a/src/Infrastructure/FairCalendar/faircalendar.module.ts b/src/Infrastructure/FairCalendar/faircalendar.module.ts index 593c0af6..d2be43c8 100644 --- a/src/Infrastructure/FairCalendar/faircalendar.module.ts +++ b/src/Infrastructure/FairCalendar/faircalendar.module.ts @@ -29,6 +29,7 @@ import { FairCalendarOverviewFactory } from 'src/Domain/FairCalendar/FairCalenda import { FairCalendarOverviewTableFactory } from './Table/FairCalendarOverviewTableFactory'; import { TranslationsModule } from '../Translations/translations.module'; import { TablesModule } from '../Tables/tables.module'; +import { ExtendedRoutingModule } from '../Common/ExtendedRouting/extendedRouting.module'; @Module({ imports: [ @@ -36,7 +37,8 @@ import { TablesModule } from '../Tables/tables.module'; ConfigModule, TypeOrmModule.forFeature([Project, Event, Task, Leave, Cooperative]), TranslationsModule, - TablesModule + TablesModule, + ExtendedRoutingModule ], controllers: [ FairCalendarController, diff --git a/src/Infrastructure/Templates/NunjucksTemplates/NunjucksTemplates.ts b/src/Infrastructure/Templates/NunjucksTemplates/NunjucksTemplates.ts index ee57b5d9..73f7386d 100644 --- a/src/Infrastructure/Templates/NunjucksTemplates/NunjucksTemplates.ts +++ b/src/Infrastructure/Templates/NunjucksTemplates/NunjucksTemplates.ts @@ -9,6 +9,7 @@ import { ITranslator } from 'src/Infrastructure/Translations/ITranslator'; import { formatFullName } from '../../Common/Utils/formatUtils'; import { formatDate, + formatEventDate, formatHtmlDate, formatHtmlYearMonth, minutesToHours @@ -44,6 +45,7 @@ export class NunjucksTemplates implements ITemplates { env.addFilter('date', value => value === 'now' ? new Date() : formatDate(value) ); + env.addFilter('eventDate', value => formatEventDate(value)); env.addFilter('htmlDate', value => formatHtmlDate(value)); env.addFilter('htmlYearMonth', value => formatHtmlYearMonth(value)); env.addFilter('longMonth', (month: number) => diff --git a/src/assets/customElements/autoForm.js b/src/assets/customElements/autoForm.js index c4cd5f24..415d8928 100644 --- a/src/assets/customElements/autoForm.js +++ b/src/assets/customElements/autoForm.js @@ -6,18 +6,13 @@ export default class extends HTMLElement { onParsed(() => { // Progressive enhancement: // If this custom element activates, submit the form whenever - // a form control changes value, and remove any manual submit button. + // a form control changes value. 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/clipboardButton.js b/src/assets/customElements/clipboardButton.js index 49c2e4d3..e43dc37b 100644 --- a/src/assets/customElements/clipboardButton.js +++ b/src/assets/customElements/clipboardButton.js @@ -16,11 +16,6 @@ export default class extends HTMLElement { throw new Error(`input at '${selector}' was not found`); } - const template = /** @type {HTMLTemplateElement} */ (this.querySelector( - 'template' - )); - this.appendChild(document.importNode(template.content, true)); - const btn = /** @type {HTMLButtonElement} */ (this.querySelector('button')); btn.addEventListener('click', () => { diff --git a/src/assets/customElements/eventCalendar.js b/src/assets/customElements/eventCalendar.js index 2b6d6561..f559fa61 100644 --- a/src/assets/customElements/eventCalendar.js +++ b/src/assets/customElements/eventCalendar.js @@ -1,8 +1,6 @@ // @ts-check -import Calendar from '@event-calendar/core'; -import DayGrid from '@event-calendar/day-grid'; -import Interaction from '@event-calendar/interaction'; -import { format, subDays } from 'date-fns'; +import { format } from 'date-fns'; +import { createCookie } from '../lib/cookie'; export default class extends HTMLElement { /** @type {string} */ @@ -20,7 +18,58 @@ export default class extends HTMLElement { } this.#addUrlTemplate = addUrlTemplate; - this.#createCalendar(date, events); + + const viewToggleFieldset = /** @type {HTMLFieldSetElement} */ (this.querySelector( + '#view-toggle' + )); + viewToggleFieldset.hidden = false; + + const listView = /** @type {HTMLElement} */ (this.querySelector( + '#list-view' + )); + listView.hidden = true; + const listViewRadio = /** @type {HTMLInputElement} */ (this.querySelector( + '#view-radio-list' + )); + + const calendarView = /** @type {HTMLElement} */ (this.querySelector( + '#calendar-view' + )); + const calendarViewRadio = /** @type {HTMLInputElement} */ (this.querySelector( + '#view-radio-calendar' + )); + + const initialView = + this.getAttribute('data-view') || calendarViewRadio.value; + + const ensureCalendarCreated = async () => { + if (!calendarView.hasAttribute('data-created')) { + const { createCalendar } = await import('../lib/lazy/eventCalendar'); + createCalendar(calendarView, date, events, this.#goToEventCreate); + calendarView.setAttribute('data-created', 'true'); + } + }; + + if (initialView === calendarViewRadio.value) { + ensureCalendarCreated(); + } + + calendarView.hidden = initialView === listViewRadio.value; + listView.hidden = initialView === calendarViewRadio.value; + + listViewRadio.addEventListener('change', () => { + calendarView.hidden = true; + listView.hidden = false; + this._storePreferredView(listViewRadio.value); + }); + + calendarViewRadio.addEventListener('change', () => { + listView.hidden = true; + ensureCalendarCreated().then(() => { + calendarView.hidden = false; + this._storePreferredView(calendarViewRadio.value); + }); + }); } /** @@ -36,50 +85,9 @@ export default class extends HTMLElement { }; /** - * @param {string} date - * @param {any[]} events + * @param {string} viewName */ - #createCalendar = (date, events) => { - const ec = new Calendar({ - target: this, - props: { - plugins: [DayGrid, Interaction], - options: { - view: 'dayGridMonth', - events, - locale: 'fr', - hiddenDays: [ - 6, // Saturday - 0 // Sunday - ], - date, - headerToolbar: { start: '', center: '', end: '' }, - dayMaxEvents: true, - eventStartEditable: false, - eventDurationEditable: false, - // TODO: add testid on days - eventContent: ({ event }) => { - const url = event.extendedProps.url; - if (url) { - return { - html: `${event.title}` - }; - } - return event.title; - }, - dateClick: ({ event, date }) => { - this.#goToEventCreate(date, date); - }, - selectable: true, - selectBackgroundColor: 'var(--background-action-violet)', - select: ({ start, end }) => { - // By default, range will stay selected if navigating using the back button. - ec.unselect(); - - this.#goToEventCreate(start, subDays(end, 1)); - } - } - } - }); - }; + _storePreferredView(viewName) { + createCookie('faircalendar_view', viewName); + } } diff --git a/src/assets/customElements/index.js b/src/assets/customElements/index.js index 58872681..da8d1d8a 100644 --- a/src/assets/customElements/index.js +++ b/src/assets/customElements/index.js @@ -1,6 +1,7 @@ // @ts-check import autoForm from './autoForm'; import clipboardButton from './clipboardButton'; +import eventCalendar from './eventCalendar'; import eventForm from './eventForm'; import frame from './frame'; import frameForm from './frameForm'; @@ -10,6 +11,7 @@ import themeToggler from './themeToggler'; customElements.define('pc-auto-form', autoForm); customElements.define('pc-clipboard-button', clipboardButton); +customElements.define('pc-eventcalendar', eventCalendar); customElements.define('pc-event-form', eventForm); customElements.define('pc-frame', frame); customElements.define('pc-frame-form', frameForm); diff --git a/src/assets/customElements/monthNavigator.js b/src/assets/customElements/monthNavigator.js index cf0f680e..feb0c391 100644 --- a/src/assets/customElements/monthNavigator.js +++ b/src/assets/customElements/monthNavigator.js @@ -28,12 +28,6 @@ export default class extends HTMLElement { this.#month = month; this.#year = year; - const template = /** @type {HTMLTemplateElement} */ (this.querySelector( - 'template' - )); - - this.appendChild(document.importNode(template.content, true)); - const previousBtn = /** @type {HTMLButtonElement} */ (this.querySelector( 'button[data-previous]' )); diff --git a/src/assets/customElements/themeToggler.js b/src/assets/customElements/themeToggler.js index 50663102..d00e5d53 100644 --- a/src/assets/customElements/themeToggler.js +++ b/src/assets/customElements/themeToggler.js @@ -3,20 +3,12 @@ import { createCookie, removeCookie } from '../lib/cookie'; export default class extends HTMLElement { connectedCallback() { - // Progressive enhancement: show toggle button only if this JavaScript loads. - const template = /** @type {HTMLTemplateElement} */ (this.querySelector( - 'template' - )); - this.appendChild(document.importNode(template.content, true)); - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)'); - let theme = this.dataset.theme; // Comes from the cookie + let theme = document.documentElement.dataset.theme || null; // Comes from the cookie // Theme coming from the cookie has priority, use system default as a fallback. - if (theme) { - document.documentElement.setAttribute('data-theme', theme); - } else { + if (!theme) { theme = prefersDark.matches ? 'dark' : 'light'; } @@ -40,14 +32,14 @@ export default class extends HTMLElement { * @param {string} theme */ _storeTheme(theme) { - document.documentElement.setAttribute('data-theme', theme); + document.documentElement.dataset.theme = theme; // Store in a cookie so the server sets next time. // This will avoid FOUC (flash of unstyled content) when dark mode is used. createCookie('theme', theme); } _clearStoredTheme() { - document.documentElement.removeAttribute('data-theme'); + delete document.documentElement.dataset.theme; removeCookie('theme'); } } diff --git a/src/assets/faircalendar.js b/src/assets/faircalendar.js deleted file mode 100644 index adb96106..00000000 --- a/src/assets/faircalendar.js +++ /dev/null @@ -1,5 +0,0 @@ -import './styles/faircalendar/index.css'; - -import eventCalendar from './customElements/eventCalendar'; - -customElements.define('pc-eventcalendar', eventCalendar); diff --git a/src/assets/lib/lazy/eventCalendar.js b/src/assets/lib/lazy/eventCalendar.js new file mode 100644 index 00000000..8d825297 --- /dev/null +++ b/src/assets/lib/lazy/eventCalendar.js @@ -0,0 +1,54 @@ +import { subDays } from 'date-fns'; +import Calendar from '@event-calendar/core'; +import DayGrid from '@event-calendar/day-grid'; +import Interaction from '@event-calendar/interaction'; + +/** + * @param {HTMLElement} target + * @param {string} date + * @param {any[]} events + * @param {(startDate: Date, endDate: Date) => void} goToEventCreate + */ +export function createCalendar(target, date, events, goToEventCreate) { + const ec = new Calendar({ + target, + props: { + plugins: [DayGrid, Interaction], + options: { + view: 'dayGridMonth', + events, + locale: 'fr', + hiddenDays: [ + 6, // Saturday + 0 // Sunday + ], + date, + headerToolbar: { start: '', center: '', end: '' }, + dayMaxEvents: true, + eventStartEditable: false, + eventDurationEditable: false, + // TODO: add testid on days + eventContent: ({ event }) => { + const url = event.extendedProps.url; + if (url) { + return { + html: `${event.title}` + }; + } + return event.title; + }, + dateClick: ({ event, date }) => { + goToEventCreate(date, date); + }, + selectable: true, + selectBackgroundColor: 'var(--background-action-violet)', + select: ({ start, end }) => { + // By default, range will stay selected if navigating using the back button. + ec.unselect(); + + goToEventCreate(start, subDays(end, 1)); + } + } + } + }); +} diff --git a/src/assets/styles/faircalendar/components/eventcalendar.css b/src/assets/styles/components/event_calendar.css similarity index 82% rename from src/assets/styles/faircalendar/components/eventcalendar.css rename to src/assets/styles/components/event_calendar.css index ae462654..947dd818 100644 --- a/src/assets/styles/faircalendar/components/eventcalendar.css +++ b/src/assets/styles/components/event_calendar.css @@ -9,10 +9,12 @@ pc-eventcalendar .ec { } pc-eventcalendar .ec-event { + padding: 4px; font-weight: bold; } -pc-eventcalendar .ec-day.ec-today { +/* note: duplicate class name to increase specificity */ +pc-eventcalendar .ec-today.ec-today { background-color: var(--event-today); } @@ -23,7 +25,7 @@ pc-eventcalendar .ec-toolbar { pc-eventcalendar .ec-day-grid .ec-uniform .ec-day { /* Fix: by default EventCalendar sets the min-height of day boxes to 0 when dayMaxEvents is true. */ - min-height: 5rem; + min-height: 7rem; } pc-eventcalendar .ec-event-body > a { diff --git a/src/assets/styles/components/index.css b/src/assets/styles/components/index.css index c094ecb4..d796bc20 100644 --- a/src/assets/styles/components/index.css +++ b/src/assets/styles/components/index.css @@ -5,6 +5,7 @@ @import './card.css'; @import './dropdown.css'; @import './event.css'; +@import './event_calendar.css'; @import './form_errors.css'; @import './header.css'; @import './icon.css'; diff --git a/src/assets/styles/components/table.css b/src/assets/styles/components/table.css index 6b80dccf..3bf9ef88 100644 --- a/src/assets/styles/components/table.css +++ b/src/assets/styles/components/table.css @@ -35,7 +35,15 @@ table.pc-table { .pc-table th, .pc-table td { - padding: calc(3 * var(--v)) calc(4 * var(--v)); + --default-table-padding: calc(3 * var(--v)) calc(4 * var(--v)); + padding: var(--table-padding, var(--default-table-padding)); +} + +@media screen and (min-width: 840px) { + .pc-table th, + .pc-table td { + padding: var(--table-desktop-padding, var(--default-table-padding)); + } } .pc-table tbody { diff --git a/src/assets/styles/defaults.css b/src/assets/styles/defaults.css index d12cfd2b..47e62f12 100644 --- a/src/assets/styles/defaults.css +++ b/src/assets/styles/defaults.css @@ -28,3 +28,7 @@ p { font-size: 1rem; margin: var(--paragraph-spacing); } + +fieldset { + border: none; +} diff --git a/src/assets/styles/faircalendar/components/index.css b/src/assets/styles/faircalendar/components/index.css deleted file mode 100644 index f869f26c..00000000 --- a/src/assets/styles/faircalendar/components/index.css +++ /dev/null @@ -1 +0,0 @@ -@import './eventcalendar.css'; diff --git a/src/assets/styles/faircalendar/index.css b/src/assets/styles/faircalendar/index.css deleted file mode 100644 index 9747bf44..00000000 --- a/src/assets/styles/faircalendar/index.css +++ /dev/null @@ -1 +0,0 @@ -@import './components/index.css'; diff --git a/src/assets/styles/layouts/stack.css b/src/assets/styles/layouts/stack.css index f0b04a32..6425ca82 100644 --- a/src/assets/styles/layouts/stack.css +++ b/src/assets/styles/layouts/stack.css @@ -3,5 +3,5 @@ } .pc-stack > * + * { - margin-top: var(--stack-spacing, calc(2 * var(--w))); + margin-top: var(--stack-gap, calc(2 * var(--w))); } diff --git a/src/assets/styles/utilities/a11y.css b/src/assets/styles/utilities/a11y.css new file mode 100644 index 00000000..cbfb64ce --- /dev/null +++ b/src/assets/styles/utilities/a11y.css @@ -0,0 +1,11 @@ +/* Make content available only to screen readers */ +/* Credit: https://css-tricks.com/inclusively-hidden/ */ +.pc-visually-hidden:not(:focus):not(:active) { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} diff --git a/src/assets/styles/utilities/index.css b/src/assets/styles/utilities/index.css index 2143db89..f4857aa1 100644 --- a/src/assets/styles/utilities/index.css +++ b/src/assets/styles/utilities/index.css @@ -1,3 +1,4 @@ +@import './a11y.css'; @import './background.css'; @import './gap.css'; @import './list.css'; diff --git a/src/assets/styles/utilities/list.css b/src/assets/styles/utilities/list.css index 58b72df3..1b451067 100644 --- a/src/assets/styles/utilities/list.css +++ b/src/assets/styles/utilities/list.css @@ -1,5 +1,5 @@ .pc-raw-list { - list-style-type: ''; - margin: 0; - padding: 0; + list-style-type: none; + margin-inline: 0; + padding-inline: 0; } diff --git a/src/assets/styles/variables.css b/src/assets/styles/variables.css index 6bb72428..50110cff 100644 --- a/src/assets/styles/variables.css +++ b/src/assets/styles/variables.css @@ -101,8 +101,8 @@ } :root[data-theme='dark'] { + /* DO NOT MODIFY! COPY-PASTE THE ABOVE */ /* Colors */ - /* COPY-PASTE OF THE ABOVE */ --background-default: #1a1b23; --background-default-hover: #2a2b36; --background-alt-grey: #121317; diff --git a/src/templates/components/theme_toggler.njk b/src/templates/components/theme_toggler.njk index 3a18679d..1cfef84f 100644 --- a/src/templates/components/theme_toggler.njk +++ b/src/templates/components/theme_toggler.njk @@ -1,10 +1,8 @@ {% import 'macros/icons.njk' as icons %} - - + + diff --git a/src/templates/layouts/_base.njk b/src/templates/layouts/_base.njk index f667b1d3..7a03544c 100644 --- a/src/templates/layouts/_base.njk +++ b/src/templates/layouts/_base.njk @@ -1,5 +1,5 @@ - + @@ -26,6 +26,14 @@ Permacoop {% endblock %} + + diff --git a/src/templates/macros/month_navigator.njk b/src/templates/macros/month_navigator.njk index 45f747e1..71ae9ef6 100644 --- a/src/templates/macros/month_navigator.njk +++ b/src/templates/macros/month_navigator.njk @@ -1,20 +1,17 @@ {% import 'macros/icons.njk' as icons %} {% macro month_navigator(monthTarget, yearTarget) %} - - + +
+ + + +
{% endmacro %} diff --git a/src/templates/pages/faircalendar/_event_list.njk b/src/templates/pages/faircalendar/_event_list.njk new file mode 100644 index 00000000..19cdd60e --- /dev/null +++ b/src/templates/pages/faircalendar/_event_list.njk @@ -0,0 +1,69 @@ +{% macro event_list() %} + +
+ + {{ 'faircalendar-view-toggle'|trans }} + +
+ + +
+
+ + +
+
+ +
+ + + + + + + + {% for startDate, dayEvents in listViewDays %} + + + + + + {% endfor %} + +
{{ startDate|eventDate }} +
    + {% for event in dayEvents %} +
  • + {% if event.extendedProps.url %} + + {{ event.title }} + + {% else %} + {{ event.title }} + {% endif %} +
  • + {% endfor %} +
+
+ + {{ icons.plus() }} + +
+
+ +
+
+{% endmacro %} diff --git a/src/templates/pages/faircalendar/_filters_form.njk b/src/templates/pages/faircalendar/_filters_form.njk index 6bfb5974..5e6d9120 100644 --- a/src/templates/pages/faircalendar/_filters_form.njk +++ b/src/templates/pages/faircalendar/_filters_form.njk @@ -1,42 +1,42 @@ {% from 'macros/month_navigator.njk' import month_navigator %} {% macro filters_form() %} -
+ {{ month_navigator(monthTarget='#month', yearTarget='#year') }} - - -
-
- - -
-
- - -
-
- - -
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + + + {% endmacro %} diff --git a/src/templates/pages/faircalendar/index.njk b/src/templates/pages/faircalendar/index.njk index 57a54411..64113f42 100644 --- a/src/templates/pages/faircalendar/index.njk +++ b/src/templates/pages/faircalendar/index.njk @@ -1,25 +1,11 @@ {% extends 'layouts/app.njk' %} {% import 'macros/icons.njk' as icons %} {% from 'macros/breadcrumb.njk' import breadcrumb %} +{% from './_event_list.njk' import event_list with context %} {% from './_filters_form.njk' import filters_form with context %} {% set title = 'faircalendar-page-title'|trans({ date: date }) %} -{% block preload %} - {{ super() }} - -{% endblock %} - -{% block styles %} - {{ super() }} - -{% endblock %} - -{% block scripts %} - {{ super() }} - -{% endblock %} - {% block main %}
@@ -33,11 +19,7 @@
{% table overviewTable, { attr: {class: 'pc-table--center pc-table--no-shadow'} } %} - + {{ event_list() }}
diff --git a/src/templates/pages/leaves/list.njk b/src/templates/pages/leaves/list.njk index 0c763c1b..9ab286f7 100644 --- a/src/templates/pages/leaves/list.njk +++ b/src/templates/pages/leaves/list.njk @@ -22,12 +22,10 @@
- - + +
diff --git a/src/templates/pages/payroll_elements/_filters_form.njk b/src/templates/pages/payroll_elements/_filters_form.njk index 350af911..c723f6ca 100644 --- a/src/templates/pages/payroll_elements/_filters_form.njk +++ b/src/templates/pages/payroll_elements/_filters_form.njk @@ -25,9 +25,11 @@ - + diff --git a/src/translations/fr-FR.ftl b/src/translations/fr-FR.ftl index 369c2c3f..26e2d658 100644 --- a/src/translations/fr-FR.ftl +++ b/src/translations/fr-FR.ftl @@ -11,6 +11,9 @@ common-table-empty = Aucun élément common-money = {NUMBER($value, style: "currency", currency: "EUR", minimumFractionDigits: 2, maximumFractionDigits: 2)} € common-date = {DATETIME($date, month: "numeric", year: "numeric", day: "numeric")} common-month-long = {DATETIME($date, month: "long")} +common-month-previous = Mois précédent +common-month-today = Aujourd'hui +common-month-next = Mois suivant coop-name = Fairness site-title = Permacoop @@ -69,6 +72,10 @@ faircalendar-overview-days = {$days -> [1] 1 jour *[other] {$days} jours } +faircalendar-view-toggle = Vue +faircalendar-view-list = Vue liste +faircalendar-view-list-add-event = Ajouter un CRA +faircalendar-view-calendar = Vue calendrier crm-title = FairCRM