From 732f1f45a0f8628c6757a257580c0a5f5ef73d7e Mon Sep 17 00:00:00 2001 From: florimondmanca Date: Fri, 28 Jun 2024 15:19:32 +0200 Subject: [PATCH] =?UTF-8?q?Affiche=20le=20nb=20de=20cong=C3=A9s=20pay?= =?UTF-8?q?=C3=A9s=20restants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Query/GetLeaveRequestsOverviewQuery.ts | 5 ++ ...tLeaveRequestsOverviewQueryHandler.spec.ts | 55 +++++++++++++++++++ .../GetLeaveRequestsOverviewQueryHandler.ts | 41 ++++++++++++++ src/Application/IDateUtils.ts | 4 ++ .../Leave/ILeaveRequestsOverview.ts | 5 ++ .../Leave/Repository/ILeaveRepository.ts | 5 ++ .../Adapter/DateUtilsAdapter.spec.ts | 30 ++++++++++ .../Adapter/DateUtilsAdapter.ts | 34 ++++++++++++ .../Controller/ListLeaveRequestsController.ts | 30 ++++++++-- .../DTO/ListLeaveRequestsControllerDTO.ts | 8 +++ .../Leave/Repository/LeaveRepository.ts | 20 +++++++ .../Table/LeaveRequestOverviewTableFactory.ts | 55 +++++++++++++++++++ .../HumanResource/humanResource.module.ts | 6 ++ src/Infrastructure/Tables/HtmlColumn.ts | 13 +++++ src/Infrastructure/Tables/TableCsvFactory.ts | 4 +- src/Infrastructure/Tables/index.ts | 11 +++- src/assets/styles/components/button.css | 2 +- src/assets/styles/defaults.css | 17 ++++++ src/assets/styles/utilities/placement.css | 4 ++ src/assets/styles/variables.css | 3 + src/templates/pages/leave_requests/list.njk | 18 +++++- .../overview/days_remaining_cell.njk | 3 + .../overview/days_remaining_column_header.njk | 16 ++++++ .../leave_requests/overview/user_cell.njk | 22 ++++++++ src/templates/tables/table.njk | 8 ++- src/translations/fr-FR.ftl | 19 +++++++ 26 files changed, 425 insertions(+), 13 deletions(-) create mode 100644 src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQuery.ts create mode 100644 src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQueryHandler.spec.ts create mode 100644 src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQueryHandler.ts create mode 100644 src/Domain/HumanResource/Leave/ILeaveRequestsOverview.ts create mode 100644 src/Infrastructure/HumanResource/Leave/DTO/ListLeaveRequestsControllerDTO.ts create mode 100644 src/Infrastructure/HumanResource/Leave/Table/LeaveRequestOverviewTableFactory.ts create mode 100644 src/Infrastructure/Tables/HtmlColumn.ts create mode 100644 src/templates/pages/leave_requests/overview/days_remaining_cell.njk create mode 100644 src/templates/pages/leave_requests/overview/days_remaining_column_header.njk create mode 100644 src/templates/pages/leave_requests/overview/user_cell.njk diff --git a/src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQuery.ts b/src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQuery.ts new file mode 100644 index 00000000..50716e5c --- /dev/null +++ b/src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQuery.ts @@ -0,0 +1,5 @@ +import { IQuery } from 'src/Application/IQuery'; + +export class GetLeaveRequestsOverviewQuery implements IQuery { + constructor(public readonly date: Date, public readonly userId: string) {} +} diff --git a/src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQueryHandler.spec.ts b/src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQueryHandler.spec.ts new file mode 100644 index 00000000..e374d3f7 --- /dev/null +++ b/src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQueryHandler.spec.ts @@ -0,0 +1,55 @@ +import { mock, instance, when, verify } from 'ts-mockito'; +import { DateUtilsAdapter } from 'src/Infrastructure/Adapter/DateUtilsAdapter'; +import { LeaveRepository } from 'src/Infrastructure/HumanResource/Leave/Repository/LeaveRepository'; +import { GetLeaveRequestsOverviewQueryHandler } from './GetLeaveRequestsOverviewQueryHandler'; +import { ILeaveRequestsOverview } from 'src/Domain/HumanResource/Leave/ILeaveRequestsOverview'; +import { GetLeaveRequestsOverviewQuery } from './GetLeaveRequestsOverviewQuery'; + +describe('GetLeaveRequestsOverviewQueryHandler', () => { + let leaveRepository: LeaveRepository; + let dateUtils: DateUtilsAdapter; + let queryHandler: GetLeaveRequestsOverviewQueryHandler; + + beforeEach(() => { + leaveRepository = mock(LeaveRepository); + dateUtils = mock(DateUtilsAdapter); + queryHandler = new GetLeaveRequestsOverviewQueryHandler( + instance(dateUtils), + instance(leaveRepository) + ); + }); + + it('testGetLeaveRequestsOverview', async () => { + const expectedResult: ILeaveRequestsOverview = { + daysPerYear: 35, + daysTaken: 6, + daysRemaining: 29 + }; + + const now = new Date('2024-07-05'); + const startDate = new Date('2024-06-01'); + const endDate = new Date('2025-05-31'); + const userId = '06691061-b62f-7499-8000-a65d631224f1'; + + when(dateUtils.getLeaveReferencePeriodDays(now)).thenReturn([ + startDate, + endDate + ]); + when(dateUtils.getWorkedDaysPerWeek()).thenReturn(5); + when(dateUtils.getNumberOfPaidLeaveWeeks()).thenReturn(7); + + when( + leaveRepository.getSumOfPaidLeaveDurationsBetween( + startDate, + endDate, + userId + ) + ).thenReturn(new Promise(resolve => resolve(6 * 420))); + + when(dateUtils.getLeaveDurationAsDays(6 * 420)).thenReturn(6); + + expect( + await queryHandler.execute(new GetLeaveRequestsOverviewQuery(now, userId)) + ).toMatchObject(expectedResult); + }); +}); diff --git a/src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQueryHandler.ts b/src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQueryHandler.ts new file mode 100644 index 00000000..c1943c01 --- /dev/null +++ b/src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQueryHandler.ts @@ -0,0 +1,41 @@ +import { Inject } from '@nestjs/common'; +import { QueryHandler } from '@nestjs/cqrs'; +import { GetLeaveRequestsOverviewQuery } from './GetLeaveRequestsOverviewQuery'; +import { ILeaveRequestsOverview } from 'src/Domain/HumanResource/Leave/ILeaveRequestsOverview'; +import { IDateUtils } from 'src/Application/IDateUtils'; +import { ILeaveRepository } from 'src/Domain/HumanResource/Leave/Repository/ILeaveRepository'; + +@QueryHandler(GetLeaveRequestsOverviewQuery) +export class GetLeaveRequestsOverviewQueryHandler { + constructor( + @Inject('IDateUtils') + private readonly dateUtils: IDateUtils, + @Inject('ILeaveRepository') + private readonly leaveRepository: ILeaveRepository + ) {} + + public async execute( + query: GetLeaveRequestsOverviewQuery + ): Promise { + const [startDate, endDate] = this.dateUtils.getLeaveReferencePeriodDays( + query.date + ); + + const numDaysPerWeek = this.dateUtils.getWorkedDaysPerWeek(); + const numWeeks = this.dateUtils.getNumberOfPaidLeaveWeeks(); + const daysPerYear = numWeeks * numDaysPerWeek; + const totalDurationTaken = await this.leaveRepository.getSumOfPaidLeaveDurationsBetween( + startDate, + endDate, + query.userId + ); + const daysTaken = this.dateUtils.getLeaveDurationAsDays(totalDurationTaken); + const daysRemaining = daysPerYear - daysTaken; + + return { + daysPerYear, + daysTaken, + daysRemaining + }; + } +} diff --git a/src/Application/IDateUtils.ts b/src/Application/IDateUtils.ts index 9d2ca39c..1a000305 100644 --- a/src/Application/IDateUtils.ts +++ b/src/Application/IDateUtils.ts @@ -10,6 +10,8 @@ export interface IDateUtils { getWorkedDaysDuringAPeriod(start: Date, end: Date): Date[]; isAWorkingDay(date: Date): boolean; getWorkedFreeDays(year: number): Date[]; + getWorkedDaysPerWeek(): number; + getNumberOfPaidLeaveWeeks(): number; getEasterDate(year: number): Date; getLeaveDuration( startDate: string, @@ -17,6 +19,8 @@ export interface IDateUtils { endDate: string, isEndsAllDay: boolean ): number; + getLeaveDurationAsDays(duration: number); + getLeaveReferencePeriodDays(date: Date): [Date, Date]; addDaysToDate(date: Date, days: number): Date; getYear(date: Date): number; getMonth(date: Date): MonthDate; diff --git a/src/Domain/HumanResource/Leave/ILeaveRequestsOverview.ts b/src/Domain/HumanResource/Leave/ILeaveRequestsOverview.ts new file mode 100644 index 00000000..3c0c29d1 --- /dev/null +++ b/src/Domain/HumanResource/Leave/ILeaveRequestsOverview.ts @@ -0,0 +1,5 @@ +export interface ILeaveRequestsOverview { + daysPerYear: number; + daysTaken: number; + daysRemaining: number; +} diff --git a/src/Domain/HumanResource/Leave/Repository/ILeaveRepository.ts b/src/Domain/HumanResource/Leave/Repository/ILeaveRepository.ts index 574c83d2..3b57cb64 100644 --- a/src/Domain/HumanResource/Leave/Repository/ILeaveRepository.ts +++ b/src/Domain/HumanResource/Leave/Repository/ILeaveRepository.ts @@ -10,4 +10,9 @@ export interface ILeaveRepository { endDate: string ): Promise; sumOfDurationLeaveByUserAndDate(user: User, date: string): Promise; + getSumOfPaidLeaveDurationsBetween( + startDate: Date, + endDate: Date, + userId: string + ): Promise; } diff --git a/src/Infrastructure/Adapter/DateUtilsAdapter.spec.ts b/src/Infrastructure/Adapter/DateUtilsAdapter.spec.ts index 54edba33..ae93b703 100644 --- a/src/Infrastructure/Adapter/DateUtilsAdapter.spec.ts +++ b/src/Infrastructure/Adapter/DateUtilsAdapter.spec.ts @@ -168,4 +168,34 @@ describe('DateUtilsAdapter', () => { const result = dateUtils.getMonth(date); expect(result).toStrictEqual(expectedResult); }); + + it('getLeaveDurationsAsDays', () => { + const dateUtils = new DateUtilsAdapter(); + expect(dateUtils.getLeaveDurationAsDays(0)).toBe(0); + expect(dateUtils.getLeaveDurationAsDays(420)).toBe(1); + expect(dateUtils.getLeaveDurationAsDays(420 / 2)).toBe(0.5); + }); + + it('getLeaveReferencePeriodDays', () => { + const dateUtils = new DateUtilsAdapter(); + expect( + dateUtils.getLeaveReferencePeriodDays(new Date('2024-01-01')) + ).toEqual([new Date('2023-06-01'), new Date('2024-05-31')]); + expect( + dateUtils.getLeaveReferencePeriodDays(new Date('2024-05-31')) + ).toEqual([new Date('2023-06-01'), new Date('2024-05-31')]); + expect( + dateUtils.getLeaveReferencePeriodDays(new Date('2024-06-01')) + ).toEqual([new Date('2024-06-01'), new Date('2025-05-31')]); + }); + + it('getWorkedDaysPerWeek', () => { + const dateUtils = new DateUtilsAdapter(); + expect(dateUtils.getWorkedDaysPerWeek()).toBe(5); + }); + + it('getNumberOfPaidLeaveWeeks', () => { + const dateUtils = new DateUtilsAdapter(); + expect(dateUtils.getNumberOfPaidLeaveWeeks()).toBe(7); + }); }); diff --git a/src/Infrastructure/Adapter/DateUtilsAdapter.ts b/src/Infrastructure/Adapter/DateUtilsAdapter.ts index 5bfb5225..807b6e2a 100644 --- a/src/Infrastructure/Adapter/DateUtilsAdapter.ts +++ b/src/Infrastructure/Adapter/DateUtilsAdapter.ts @@ -207,4 +207,38 @@ export class DateUtilsAdapter implements IDateUtils { return duration; } + + /** + * @param duration Duration in minutes + * @returns Duration in days + */ + public getLeaveDurationAsDays(duration: number) { + return duration / 420; + } + + getLeaveReferencePeriodDays(date: Date): [Date, Date] { + // Reference period is between June 1st and May 31st. + + let startDate = this.makeDate(this.getYear(date), 6, 1); + + if (startDate > date) { + startDate = this.makeDate(this.getYear(date) - 1, 6, 1); + } + + let endDate = this.makeDate(this.getYear(date), 5, 31); + + if (endDate < date) { + endDate = this.makeDate(this.getYear(date) + 1, 5, 31); + } + + return [startDate, endDate]; + } + + getWorkedDaysPerWeek(): number { + return 5; + } + + getNumberOfPaidLeaveWeeks(): number { + return 7; + } } diff --git a/src/Infrastructure/HumanResource/Leave/Controller/ListLeaveRequestsController.ts b/src/Infrastructure/HumanResource/Leave/Controller/ListLeaveRequestsController.ts index f851f615..36fc147c 100644 --- a/src/Infrastructure/HumanResource/Leave/Controller/ListLeaveRequestsController.ts +++ b/src/Infrastructure/HumanResource/Leave/Controller/ListLeaveRequestsController.ts @@ -11,11 +11,15 @@ import { User } from 'src/Domain/HumanResource/User/User.entity'; import { GetLeaveRequestsQuery } from 'src/Application/HumanResource/Leave/Query/GetLeaveRequestsQuery'; import { IsAuthenticatedGuard } from 'src/Infrastructure/HumanResource/User/Security/IsAuthenticatedGuard'; import { WithName } from 'src/Infrastructure/Common/ExtendedRouting/WithName'; -import { PaginationDTO } from 'src/Infrastructure/Common/DTO/PaginationDTO'; import { Pagination } from 'src/Application/Common/Pagination'; import { LeaveRequestView } from 'src/Application/HumanResource/Leave/View/LeaveRequestView'; import { LoggedUser } from '../../User/Decorator/LoggedUser'; import { LeaveRequestTableFactory } from '../Table/LeaveRequestTableFactory'; +import { LeaveRequestsOverviewTableFactory } from '../Table/LeaveRequestOverviewTableFactory'; +import { GetLeaveRequestsOverviewQuery } from 'src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQuery'; +import { UserView } from 'src/Application/HumanResource/User/View/UserView'; +import { GetUsersQuery } from 'src/Application/HumanResource/User/Query/GetUsersQuery'; +import { ListLeaveRequestsControllerDTO } from '../DTO/ListLeaveRequestsControllerDTO'; @Controller('app/people/leave_requests') @UseGuards(IsAuthenticatedGuard) @@ -23,22 +27,38 @@ export class ListLeaveRequestsController { constructor( @Inject('IQueryBus') private readonly queryBus: IQueryBus, - private readonly tableFactory: LeaveRequestTableFactory + private readonly tableFactory: LeaveRequestTableFactory, + private readonly overviewTableFactory: LeaveRequestsOverviewTableFactory ) {} @Get() @WithName('people_leave_requests_list') @Render('pages/leave_requests/list.njk') public async get( - @Query() paginationDto: PaginationDTO, + @Query() dto: ListLeaveRequestsControllerDTO, @LoggedUser() user: User ) { const pagination: Pagination = await this.queryBus.execute( - new GetLeaveRequestsQuery(user.getId(), paginationDto.page) + new GetLeaveRequestsQuery(user.getId(), dto.page) ); const table = this.tableFactory.create(pagination.items, user.getId()); - return { table, pagination, currentPage: paginationDto.page }; + const userId = dto.userId ? dto.userId : user['id']; + + const overview = await this.queryBus.execute( + new GetLeaveRequestsOverviewQuery(new Date(), userId) + ); + const overviewTable = await this.overviewTableFactory.create( + overview, + userId + ); + + return { + table, + overviewTable, + pagination, + currentPage: dto.page + }; } } diff --git a/src/Infrastructure/HumanResource/Leave/DTO/ListLeaveRequestsControllerDTO.ts b/src/Infrastructure/HumanResource/Leave/DTO/ListLeaveRequestsControllerDTO.ts new file mode 100644 index 00000000..d09f69ad --- /dev/null +++ b/src/Infrastructure/HumanResource/Leave/DTO/ListLeaveRequestsControllerDTO.ts @@ -0,0 +1,8 @@ +import { IsUUID, IsOptional } from 'class-validator'; +import { PaginationDTO } from 'src/Infrastructure/Common/DTO/PaginationDTO'; + +export class ListLeaveRequestsControllerDTO extends PaginationDTO { + @IsUUID() + @IsOptional() + public userId?: string; +} diff --git a/src/Infrastructure/HumanResource/Leave/Repository/LeaveRepository.ts b/src/Infrastructure/HumanResource/Leave/Repository/LeaveRepository.ts index 367d13ac..7e64fcb7 100644 --- a/src/Infrastructure/HumanResource/Leave/Repository/LeaveRepository.ts +++ b/src/Infrastructure/HumanResource/Leave/Repository/LeaveRepository.ts @@ -5,6 +5,7 @@ import { ILeaveRepository } from 'src/Domain/HumanResource/Leave/Repository/ILea import { Leave } from 'src/Domain/HumanResource/Leave/Leave.entity'; import { User, UserRole } from 'src/Domain/HumanResource/User/User.entity'; import { IDateUtils } from 'src/Application/IDateUtils'; +import { Type } from 'src/Domain/HumanResource/Leave/LeaveRequest.entity'; export class LeaveRepository implements ILeaveRepository { constructor( @@ -68,4 +69,23 @@ export class LeaveRepository implements ILeaveRepository { return Number(result.time) || 0; } + + public async getSumOfPaidLeaveDurationsBetween( + startDate: Date, + endDate: Date, + userId: string + ): Promise { + const result = await this.repository + .createQueryBuilder('leave') + .select('SUM(leave.time) as time') + .innerJoin('leave.leaveRequest', 'leaveRequest') + .innerJoin('leaveRequest.user', 'user') + .where('leave.date >= :startDate', { startDate }) + .andWhere('leave.date < :endDate', { endDate }) + .andWhere('leaveRequest.type = :type', { type: Type.PAID }) + .andWhere('user.id = :userId', { userId: userId }) + .getRawOne(); + + return Number(result.time) || 0; + } } diff --git a/src/Infrastructure/HumanResource/Leave/Table/LeaveRequestOverviewTableFactory.ts b/src/Infrastructure/HumanResource/Leave/Table/LeaveRequestOverviewTableFactory.ts new file mode 100644 index 00000000..2f4cff89 --- /dev/null +++ b/src/Infrastructure/HumanResource/Leave/Table/LeaveRequestOverviewTableFactory.ts @@ -0,0 +1,55 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { IQueryBus } from '@nestjs/cqrs'; +import { GetUsersQuery } from 'src/Application/HumanResource/User/Query/GetUsersQuery'; +import { UserView } from 'src/Application/HumanResource/User/View/UserView'; +import { ILeaveRequestsOverview } from 'src/Domain/HumanResource/Leave/ILeaveRequestsOverview'; +import { Table } from 'src/Infrastructure/Tables'; +import { HtmlColumn } from 'src/Infrastructure/Tables/HtmlColumn'; +import { RowFactory } from 'src/Infrastructure/Tables/RowFactory'; +import { ITemplates } from 'src/Infrastructure/Templates/ITemplates'; + +@Injectable() +export class LeaveRequestsOverviewTableFactory { + constructor( + @Inject('IQueryBus') + private queryBus: IQueryBus, + private rowFactory: RowFactory, + @Inject('ITemplates') + private templates: ITemplates + ) {} + + public async create( + overview: ILeaveRequestsOverview, + userId: string + ): Promise { + const users: UserView[] = await this.queryBus.execute( + new GetUsersQuery(false, true) + ); + + const columns = [ + 'leaves-user', + new HtmlColumn( + 'leaves-overview-daysRemaining', + this.templates.render( + 'pages/leave_requests/overview/days_remaining_column_header.njk', + { + daysPerYear: overview.daysPerYear + } + ) + ) + ]; + + const row = this.rowFactory + .createBuilder() + .template('pages/leave_requests/overview/user_cell.njk', { + users, + userId + }) + .template('pages/leave_requests/overview/days_remaining_cell.njk', { + daysRemaining: overview.daysRemaining + }) + .build(); + + return new Table(columns, [row]); + } +} diff --git a/src/Infrastructure/HumanResource/humanResource.module.ts b/src/Infrastructure/HumanResource/humanResource.module.ts index 6470a0f1..66a2c20c 100644 --- a/src/Infrastructure/HumanResource/humanResource.module.ts +++ b/src/Infrastructure/HumanResource/humanResource.module.ts @@ -80,6 +80,9 @@ import { ListMealTicketsController } from './MealTicket/Controller/ListMealTicke import { MealTicketTableFactory } from './MealTicket/Table/MealTicketTableFactory'; import { AddMealTicketRemovalController } from './MealTicket/Controller/AddMealTicketRemovalController'; import { ExportLeavesCalendarController } from './Leave/Controller/ExportLeavesCalendarController'; +import { LeaveRequestsOverviewTableFactory } from './Leave/Table/LeaveRequestOverviewTableFactory'; +import { GetLeaveRequestsOverviewQueryHandler } from 'src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQueryHandler'; +import { TemplatesModule } from '../Templates/templates.module'; @Module({ imports: [ @@ -100,6 +103,7 @@ import { ExportLeavesCalendarController } from './Leave/Controller/ExportLeavesC UserSavingsRecord, InterestRate ]), + TemplatesModule, ExtendedRoutingModule, TablesModule ], @@ -183,6 +187,8 @@ import { ExportLeavesCalendarController } from './Leave/Controller/ExportLeavesC IncreaseUserSavingsRecordCommandHandler, UserTableFactory, LeaveRequestTableFactory, + GetLeaveRequestsOverviewQueryHandler, + LeaveRequestsOverviewTableFactory, PayrollElementsTableFactory, MealTicketTableFactory ] diff --git a/src/Infrastructure/Tables/HtmlColumn.ts b/src/Infrastructure/Tables/HtmlColumn.ts new file mode 100644 index 00000000..537b852e --- /dev/null +++ b/src/Infrastructure/Tables/HtmlColumn.ts @@ -0,0 +1,13 @@ +export class HtmlColumn { + public readonly isRaw = true; + + constructor(private readonly text: string, private readonly html: string) {} + + public getRaw(): string { + return this.html; + } + + public toString() { + return this.text; + } +} diff --git a/src/Infrastructure/Tables/TableCsvFactory.ts b/src/Infrastructure/Tables/TableCsvFactory.ts index 7acdabbd..11d39809 100644 --- a/src/Infrastructure/Tables/TableCsvFactory.ts +++ b/src/Infrastructure/Tables/TableCsvFactory.ts @@ -10,7 +10,9 @@ export class TableCsvFactory { ) {} public toCsv(table: Table): string { - const header = table.columns.map(name => this.translator.translate(name)); + const header = table.columns.map(column => + this.translator.translate(column.toString()) + ); const rows = table.rows.map(row => row .map(cell => { diff --git a/src/Infrastructure/Tables/index.ts b/src/Infrastructure/Tables/index.ts index 31cc0c33..aa39d070 100644 --- a/src/Infrastructure/Tables/index.ts +++ b/src/Infrastructure/Tables/index.ts @@ -1,3 +1,7 @@ +import { HtmlColumn } from './HtmlColumn'; + +type IColumn = string | HtmlColumn; + export interface ICell { name: string; renderHtml(): string; @@ -7,9 +11,12 @@ export interface ICell { export type Row = ICell[]; export class Table { - constructor(public readonly columns: string[], public readonly rows: Row[]) {} + constructor( + public readonly columns: IColumn[], + public readonly rows: Row[] + ) {} } export class Inline { - constructor(public readonly columns: string[], public readonly row: Row) {} + constructor(public readonly columns: IColumn[], public readonly row: Row) {} } diff --git a/src/assets/styles/components/button.css b/src/assets/styles/components/button.css index c714374d..ebb09c2a 100644 --- a/src/assets/styles/components/button.css +++ b/src/assets/styles/components/button.css @@ -35,7 +35,7 @@ padding: 0; width: var(--button-size, calc(4 * var(--w))); height: var(--button-size, calc(4 * var(--w))); - display: grid; + display: inline-grid; place-items: center; } diff --git a/src/assets/styles/defaults.css b/src/assets/styles/defaults.css index 90e478c6..15834d88 100644 --- a/src/assets/styles/defaults.css +++ b/src/assets/styles/defaults.css @@ -36,3 +36,20 @@ fieldset { dialog { margin: auto; } + +:popover-open { + position: absolute; + inset: unset; + margin: var(--w); + white-space: wrap; + border: 1px solid var(--border-default); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-default); + padding: var(--w); + font-weight: normal; + text-transform: none; +} + +[popovertarget]:focus { + outline: solid 2px; +} diff --git a/src/assets/styles/utilities/placement.css b/src/assets/styles/utilities/placement.css index 003954a0..23bceedc 100644 --- a/src/assets/styles/utilities/placement.css +++ b/src/assets/styles/utilities/placement.css @@ -2,3 +2,7 @@ display: grid; place-items: center; } + +.pc-relative { + position: relative; +} diff --git a/src/assets/styles/variables.css b/src/assets/styles/variables.css index 2d7713a1..2294e264 100644 --- a/src/assets/styles/variables.css +++ b/src/assets/styles/variables.css @@ -63,6 +63,9 @@ --event-other-border: rgb(244, 245, 247); --event-other-text: rgb(36, 38, 45); --event-today: #fcf8e3; + --leave-daysRemaining-background: rgb(222, 247, 236); + --leave-daysRemaining-border: rgb(222, 247, 236); + --leave-daysRemaining-text: rgb(4, 108, 78); --header-height-desktop: 56px; } diff --git a/src/templates/pages/leave_requests/list.njk b/src/templates/pages/leave_requests/list.njk index 9cf80fcc..41e7e67a 100644 --- a/src/templates/pages/leave_requests/list.njk +++ b/src/templates/pages/leave_requests/list.njk @@ -15,9 +15,21 @@ {{ links.add(path('people_leave_requests_add'), text='leave-requests-add-title'|trans) }} - {% table table %} +
+
+

{{ 'leaves-summary-title'|trans }}

- {% set paginationUrl = path('people_leave_requests_list') %} - {% include 'includes/pagination.njk' %} + {% table overviewTable, { attr: {class: 'pc-table--center pc-table--no-shadow'} } %} +
+ +
+

{{ 'leaves-list-title'|trans }}

+ + {% table table %} + + {% set paginationUrl = path('people_leave_requests_list') %} + {% include 'includes/pagination.njk' %} +
+
{% endblock main %} diff --git a/src/templates/pages/leave_requests/overview/days_remaining_cell.njk b/src/templates/pages/leave_requests/overview/days_remaining_cell.njk new file mode 100644 index 00000000..239ba265 --- /dev/null +++ b/src/templates/pages/leave_requests/overview/days_remaining_cell.njk @@ -0,0 +1,3 @@ + + {{ 'leaves-overview-daysRemaining-value'|trans({ daysRemaining: daysRemaining }) }} + diff --git a/src/templates/pages/leave_requests/overview/days_remaining_column_header.njk b/src/templates/pages/leave_requests/overview/days_remaining_column_header.njk new file mode 100644 index 00000000..462831e6 --- /dev/null +++ b/src/templates/pages/leave_requests/overview/days_remaining_column_header.njk @@ -0,0 +1,16 @@ + + {{ 'leaves-overview-daysRemaining'|trans }} + + + + + +
+ {{ 'leaves-overview-daysRemaining-explanation'|trans({ daysPerYear: daysPerYear }) }} +
+
diff --git a/src/templates/pages/leave_requests/overview/user_cell.njk b/src/templates/pages/leave_requests/overview/user_cell.njk new file mode 100644 index 00000000..e7d4982a --- /dev/null +++ b/src/templates/pages/leave_requests/overview/user_cell.njk @@ -0,0 +1,22 @@ + +
+
+ + +
+ + +
diff --git a/src/templates/tables/table.njk b/src/templates/tables/table.njk index 1dafc15a..43c00a41 100644 --- a/src/templates/tables/table.njk +++ b/src/templates/tables/table.njk @@ -7,7 +7,13 @@ {% for column in table.columns %} - + {% endfor %} diff --git a/src/translations/fr-FR.ftl b/src/translations/fr-FR.ftl index 55973e41..49294bdd 100644 --- a/src/translations/fr-FR.ftl +++ b/src/translations/fr-FR.ftl @@ -121,6 +121,8 @@ profile-password = Mot de passe people-title = FairRH leaves-title = Congés +leaves-summary-title = Synthèse +leaves-list-title = Liste des congés leaves-user = Coopérateur·ice - salarié·e leaves-period = Période leaves-type = Type de congé @@ -179,6 +181,23 @@ leave-requests-create-notification-message = Salut 👋, {$userFirstName} a fait leave-requests-approve-notification-emoji-name = white_check_mark leave-requests-approve-notification-message = Demande de congé approuvée par {$moderatorFirstName} leaves-calendar-url-title = Lien d'abonnement au calendrier +-leaves-overview-days-value = {$days -> + [0] 0 + [1] 1 jour + *[other] {$days} jours +} +leaves-overview-daysRemaining = Congés payés restants +leaves-overview-daysRemaining-showExplanation = Voir l'explication +leaves-overview-daysRemaining-value = {$daysRemaining -> + [0] 0 + [1] 1 jour + *[other] {$daysRemaining} jours +} +leaves-overview-daysRemaining-explanation = Estimation sur une base de {$daysPerYear -> + [0] 0 + [1] 1 jour + *[other] {$daysPerYear} jours +} par an, hors congés exceptionnels payroll-elements-title = Éléments de paie payroll-elements-page-title = Éléments de paie {DATETIME($date, month: "long", year: "numeric")}
{{ column|trans }} + {% if column.isRaw %} + {{ column.getRaw()|safe }} + {% else %} + {{ column|trans }} + {% endif %} +