Skip to content

Commit 99c86c9

Browse files
rahul-rocketgreptile-apps[bot]coderabbitai[bot]
authored
fix(ui): daily timesheet layout selector & actions buttons UI (#9593)
* fix(ui): daily timesheet layout selector & actions buttons UI * Update packages/ui-core/shared/src/lib/components/layout-selector/layout-selector.component.html Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update packages/ui-core/shared/src/lib/gauzy-button-action/gauzy-button-action.component.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Apply suggestion from @coderabbitai[bot] * Update layout-selector\layout-selector.component.html * Update packages/ui-core/shared/src/lib/gauzy-button-action/gauzy-button-action.component.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Apply suggestion from @coderabbitai[bot] * Apply suggestion from @coderabbitai[bot] Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Apply suggestion from @coderabbitai[bot] * Update packages/ui-core/shared/src/lib/components/layout-selector/layout-selector.component.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Apply suggestion from @coderabbitai[bot] * Apply suggestion from @coderabbitai[bot] * Apply suggestion from @coderabbitai[bot] --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent fa469b2 commit 99c86c9

File tree

9 files changed

+110
-118
lines changed

9 files changed

+110
-118
lines changed

apps/gauzy/src/app/pages/employees/timesheet/daily/daily/daily.component.ts

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
// tslint:disable: nx-enforce-module-boundaries
2-
import { Component, OnInit, OnDestroy, AfterViewInit, ViewChild } from '@angular/core';
2+
import { Component, OnInit, OnDestroy, AfterViewInit, ViewChild, inject } from '@angular/core';
33
import { ActivatedRoute, ParamMap } from '@angular/router';
4-
import { distinctUntilChange, isEmpty } from '@gauzy/ui-core/common';
54
import { NbDialogService, NbMenuItem, NbMenuService } from '@nebular/theme';
6-
import { filter, map, debounceTime, tap } from 'rxjs/operators';
75
import { BehaviorSubject, Observable, catchError, finalize, firstValueFrom, from, of, switchMap } from 'rxjs';
6+
import { filter, map, debounceTime, tap } from 'rxjs/operators';
87
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
98
import { TranslateService } from '@ngx-translate/core';
109
import { pick } from 'underscore';
1110
import * as moment from 'moment';
1211
import { IGetTimeLogInput, ITimeLog, PermissionsEnum, ITimeLogFilters, TimeLogSourceEnum } from '@gauzy/contracts';
12+
import { distinctUntilChange, isEmpty } from '@gauzy/ui-core/common';
1313
import {
1414
DateRangePickerBuilderService,
1515
ErrorHandlingService,
@@ -30,12 +30,21 @@ import {
3030

3131
@UntilDestroy({ checkProperties: true })
3232
@Component({
33-
selector: 'ngx-daily-timesheet',
34-
templateUrl: './daily.component.html',
35-
styleUrls: ['./daily.component.scss'],
36-
standalone: false
33+
selector: 'ngx-daily-timesheet',
34+
templateUrl: './daily.component.html',
35+
styleUrls: ['./daily.component.scss'],
36+
standalone: false
3737
})
3838
export class DailyComponent extends BaseSelectorFilterComponent implements AfterViewInit, OnInit, OnDestroy {
39+
private readonly _timesheetService = inject(TimesheetService);
40+
private readonly _timeTrackerService = inject(TimeTrackerService);
41+
private readonly _dialogService = inject(NbDialogService);
42+
private readonly _nbMenuService = inject(NbMenuService);
43+
private readonly _timesheetFilterService = inject(TimesheetFilterService);
44+
private readonly _route = inject(ActivatedRoute);
45+
private readonly _toastrService = inject(ToastrService);
46+
private readonly _errorHandlingService = inject(ErrorHandlingService);
47+
3948
public PermissionsEnum = PermissionsEnum; // Enum for permissions.
4049
public logs$: Observable<ITimeLog[]>; // Observable for an array of Time Logs.
4150
public logs: ITimeLog[] = []; // Array of organization time logs.
@@ -64,35 +73,21 @@ export class DailyComponent extends BaseSelectorFilterComponent implements After
6473
};
6574

6675
constructor(
67-
public readonly translateService: TranslateService,
68-
private readonly _timesheetService: TimesheetService,
69-
private readonly _timeTrackerService: TimeTrackerService,
70-
private readonly _dialogService: NbDialogService,
71-
private readonly _nbMenuService: NbMenuService,
72-
private readonly _timesheetFilterService: TimesheetFilterService,
73-
private readonly _route: ActivatedRoute,
74-
private readonly _toastrService: ToastrService,
75-
private readonly _errorHandlingService: ErrorHandlingService,
76-
protected readonly store: Store,
77-
protected readonly dateRangePickerBuilderService: DateRangePickerBuilderService,
78-
protected readonly timeZoneService: TimeZoneService
76+
translateService: TranslateService,
77+
store: Store,
78+
dateRangePickerBuilderService: DateRangePickerBuilderService,
79+
timeZoneService: TimeZoneService
7980
) {
8081
super(store, translateService, dateRangePickerBuilderService, timeZoneService);
8182
}
8283

83-
/**
84-
*
85-
*/
8684
ngOnInit() {
8785
this._handleSubjectOperationsSubscriber();
8886
this._handleUpdateLogSubscriber();
8987
this._handleRefreshDailyLogs();
9088
this._getDailyTimesheetLogs();
9189
}
9290

93-
/**
94-
*
95-
*/
9691
ngAfterViewInit() {
9792
this._createContextMenus();
9893
this._applyTranslationOnContextMenu();
@@ -215,16 +210,10 @@ export class DailyComponent extends BaseSelectorFilterComponent implements After
215210
// Get the current payloads value
216211
const payloads = this.payloads$.getValue();
217212

218-
// Invoke the service to fetch time logs with given payloads
219-
const api$ = this._timesheetService.getTimeLogs(payloads, [
220-
'project',
221-
'task',
222-
'organizationContact',
223-
'employee.user'
224-
]);
225-
226213
// Convert the promise-based API call to an observable
227-
return from(api$).pipe(
214+
return from(
215+
this._timesheetService.getTimeLogs(payloads, ['project', 'task', 'organizationContact', 'employee.user'])
216+
).pipe(
228217
// Handle API call errors and log them
229218
catchError((error) => {
230219
console.error('Error while retrieving daily time logs entries', error);
@@ -531,7 +520,6 @@ export class DailyComponent extends BaseSelectorFilterComponent implements After
531520
*/
532521
private _createContextMenus(): void {
533522
const deletePermission = this.store.hasAnyPermission(PermissionsEnum.ALLOW_DELETE_TIME);
534-
535523
this.contextMenus = deletePermission
536524
? [
537525
{
@@ -540,7 +528,7 @@ export class DailyComponent extends BaseSelectorFilterComponent implements After
540528
action: 'DELETE'
541529
}
542530
}
543-
]
531+
]
544532
: [];
545533
}
546534

packages/core/src/lib/estimate-email/dto/find-estimate-email-query.dto.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { IEstimateEmailFindInput } from '@gauzy/contracts';
22
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
3-
import { Transform, TransformFnParams } from 'class-transformer';
3+
import { Transform } from 'class-transformer';
44
import { IsEmail, IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator';
5+
import { parseRelationsString } from './../../shared/dto';
56

67
/**
78
* Allowed relations for the estimate-email validation endpoint.
@@ -31,7 +32,7 @@ export class FindEstimateEmailQueryDTO implements IEstimateEmailFindInput {
3132

3233
@ApiPropertyOptional({ type: () => String, enum: EstimateEmailRelationEnum })
3334
@IsOptional()
34-
@Transform(({ value }: TransformFnParams) => (value ? value.map((element: string) => element.trim()) : []))
35+
@Transform(parseRelationsString)
3536
@IsEnum(EstimateEmailRelationEnum, { each: true })
3637
readonly relations: string[] = [];
37-
}
38+
}

packages/core/src/lib/public-share/invoice/dto/public-invoice-query.dto.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ApiPropertyOptional } from '@nestjs/swagger';
2-
import { Transform, TransformFnParams } from 'class-transformer';
2+
import { Transform } from 'class-transformer';
33
import { IsEnum, IsOptional } from 'class-validator';
4+
import { parseRelationsString } from './../../../shared/dto';
45

56
/**
67
* Allowed relations for the public invoice endpoint.
@@ -29,7 +30,7 @@ export enum PublicInvoiceRelationEnum {
2930
export class PublicInvoiceQueryDTO {
3031
@ApiPropertyOptional({ type: () => String, enum: PublicInvoiceRelationEnum })
3132
@IsOptional()
32-
@Transform(({ value }: TransformFnParams) => (value ? value.map((element: string) => element.trim()) : []))
33+
@Transform(parseRelationsString)
3334
@IsEnum(PublicInvoiceRelationEnum, { each: true })
3435
readonly relations: string[] = [];
35-
}
36+
}

packages/core/src/lib/shared/dto/relations-query.dto.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,37 @@ import { Transform, TransformFnParams } from 'class-transformer';
33
import { IsArray, IsOptional } from 'class-validator';
44
import { IBaseRelationsEntityModel } from '@gauzy/contracts';
55

6+
/**
7+
* Parses a comma-separated relations string (or string array) into a
8+
* trimmed, non-empty string array. Re-exported so that DTOs with
9+
* stricter enum constraints can reuse the same transform logic.
10+
*/
11+
export function parseRelationsString({ value }: TransformFnParams): string[] {
12+
if (value === undefined || value === null) {
13+
return [];
14+
}
15+
if (typeof value === 'string') {
16+
return value
17+
.split(',')
18+
.map((v) => v.trim())
19+
.filter((v) => v.length > 0);
20+
}
21+
if (Array.isArray(value)) {
22+
return value
23+
.filter((v) => typeof v === 'string')
24+
.map((v) => v.trim())
25+
.filter((v) => v.length > 0);
26+
}
27+
return [];
28+
}
29+
630
/**
731
* Validates and transforms 'relations' query parameter.
832
*/
933
export class RelationsQueryDTO implements IBaseRelationsEntityModel {
1034
@ApiPropertyOptional({ type: () => [String], isArray: true })
1135
@IsOptional()
1236
@IsArray()
13-
@Transform(({ value }: TransformFnParams) => {
14-
if (value === undefined || value === null) {
15-
return [];
16-
}
17-
if (typeof value === 'string') {
18-
return value.split(',').map((v) => v.trim()).filter((v) => v.length > 0);
19-
}
20-
if (Array.isArray(value)) {
21-
return value
22-
.filter((v) => typeof v === 'string')
23-
.map((v) => v.trim())
24-
.filter((v) => v.length > 0);
25-
}
26-
return [];
27-
})
37+
@Transform(parseRelationsString)
2838
readonly relations: string[] = [];
2939
}

packages/ui-core/shared/src/lib/components/layout-selector/layout-selector.component.html

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
11
<div class="layout-switch">
22
<button
3-
(click)="changeLayout(layoutStyles.CARDS_GRID)"
4-
status="basic"
5-
[class.primary]="componentLayoutStyle === layoutStyles.CARDS_GRID"
6-
[class.basic]="!(componentLayoutStyle === layoutStyles.CARDS_GRID)"
7-
class="switch-button ml-1 mr-1"
3+
type="button"
84
nbButton
95
size="small"
6+
status="basic"
7+
class="switch-button ml-1 mr-1"
8+
[class.primary]="componentLayoutStyle() === layoutStyles.CARDS_GRID"
9+
[class.basic]="componentLayoutStyle() !== layoutStyles.CARDS_GRID"
1010
[nbTooltip]="'SETTINGS_MENU.CARDS_GRID' | translate"
11+
(click)="changeLayout(layoutStyles.CARDS_GRID)"
1112
>
1213
<nb-icon icon="grid-outline"></nb-icon>
1314
</button>
1415
<button
15-
(click)="changeLayout(layoutStyles.TABLE)"
16-
status="basic"
17-
[class.primary]="componentLayoutStyle === layoutStyles.TABLE"
18-
[class.basic]="!(componentLayoutStyle === layoutStyles.TABLE)"
19-
class="switch-button ml-1 mr-1"
16+
type="button"
2017
nbButton
2118
size="small"
19+
status="basic"
20+
class="switch-button ml-1 mr-1"
21+
[class.primary]="componentLayoutStyle() === layoutStyles.TABLE"
22+
[class.basic]="componentLayoutStyle() !== layoutStyles.TABLE"
2223
[nbTooltip]="'SETTINGS_MENU.TABLE' | translate"
24+
(click)="changeLayout(layoutStyles.TABLE)"
2325
>
2426
<nb-icon icon="list-outline"></nb-icon>
2527
</button>
Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,35 @@
1-
import { Component, OnDestroy, OnInit, Input } from '@angular/core';
1+
import { ChangeDetectionStrategy, Component, OnInit, inject, input, signal } from '@angular/core';
22
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
33
import { ComponentLayoutStyleEnum } from '@gauzy/contracts';
44
import { ComponentEnum } from '@gauzy/ui-core/common';
55
import { Store } from '@gauzy/ui-core/core';
66

7-
@UntilDestroy({ checkProperties: true })
7+
@UntilDestroy()
88
@Component({
9-
selector: 'ga-layout-selector',
10-
templateUrl: './layout-selector.component.html',
11-
styleUrls: ['./layout-selector.component.scss'],
12-
standalone: false
9+
selector: 'ga-layout-selector',
10+
templateUrl: './layout-selector.component.html',
11+
styleUrls: ['./layout-selector.component.scss'],
12+
standalone: false,
13+
changeDetection: ChangeDetectionStrategy.OnPush
1314
})
14-
export class LayoutSelectorComponent implements OnInit, OnDestroy {
15-
public layoutStyles = ComponentLayoutStyleEnum;
15+
export class LayoutSelectorComponent implements OnInit {
16+
protected readonly store = inject(Store);
1617

17-
@Input() componentName: ComponentEnum;
18-
componentLayoutStyle: ComponentLayoutStyleEnum;
18+
protected readonly layoutStyles = ComponentLayoutStyleEnum;
19+
protected readonly componentName = input<ComponentEnum>();
1920

20-
constructor(private readonly store: Store) {}
21+
public readonly componentLayoutStyle = signal<ComponentLayoutStyleEnum | undefined>(undefined);
2122

2223
ngOnInit() {
2324
this.store.componentLayoutMap$
2425
.pipe(untilDestroyed(this))
2526
.subscribe((componentLayoutMap: Map<string, ComponentLayoutStyleEnum>) => {
26-
const dataLayout = componentLayoutMap.get(this.componentName);
27-
this.componentLayoutStyle = dataLayout;
27+
const dataLayout = componentLayoutMap.get(this.componentName());
28+
this.componentLayoutStyle.set(dataLayout);
2829
});
2930
}
3031

31-
changeLayout(layout: ComponentLayoutStyleEnum) {
32-
this.store.setLayoutForComponent(this.componentName, layout);
32+
protected changeLayout(layout: ComponentLayoutStyleEnum) {
33+
this.store.setLayoutForComponent(this.componentName(), layout);
3334
}
34-
35-
ngOnDestroy() {}
3635
}
Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
11
<div class="actions-container">
22
<div class="transition-container">
3-
<div
4-
[class.transition]="!isDisable"
5-
[class.show]="!isDisable"
6-
[class.transition]="!!isDisable"
7-
[class.hide]="!!isDisable"
8-
>
9-
<span
10-
[class.transition]="!isDisable"
11-
[class.show]="!isDisable"
12-
[class.transition]="!!isDisable"
13-
[class.show-button]="!!isDisable"
14-
>
15-
<ng-content select="[buttonTemplateVisible]"></ng-content>
16-
<ng-container [ngTemplateOutlet]="buttonTemplateVisible"></ng-container>
3+
<div class="transition" [class.show]="!isDisable()" [class.hide]="isDisable()">
4+
<span class="transition" [class.show]="!isDisable()" [class.show-button]="isDisable()">
5+
<ng-content select="[buttonTemplateVisible]" />
6+
<ng-container [ngTemplateOutlet]="buttonTemplateVisible()" />
177
</span>
18-
<ng-container [ngTemplateOutlet]="buttonTemplate"></ng-container>
19-
<ng-content select="[buttonTemplate]"></ng-content>
8+
<ng-container [ngTemplateOutlet]="buttonTemplate()" />
9+
<ng-content select="[buttonTemplate]" />
2010
</div>
2111
</div>
22-
@if (hasLayoutSelector) {
23-
<ga-layout-selector [componentName]="componentName"></ga-layout-selector>
12+
@if (hasLayoutSelector()) {
13+
<ga-layout-selector [componentName]="componentName()" />
2414
}
2515
</div>

packages/ui-core/shared/src/lib/gauzy-button-action/gauzy-button-action.component.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212

1313
:host .transition-container {
1414
overflow-x: hidden;
15-
border-radius: nb-theme(button-rectangle-border-radius);
15+
border-radius: var(--border-radius);
1616
span {
1717
background: var(--gauzy-card-2);
18-
border-radius: nb-theme(button-rectangle-border-radius);
18+
border-radius: var(--border-radius);
1919
padding: 2px 4px;
2020
@include nb-ltr(margin-right, 10px);
2121
@include nb-rtl(margin-left, 10px);
Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1-
import { Component, OnInit, Input } from '@angular/core';
1+
import { ChangeDetectionStrategy, Component, input, TemplateRef } from '@angular/core';
22
import { ComponentEnum } from '@gauzy/ui-core/common';
33

44
@Component({
55
selector: 'ngx-gauzy-button-action',
66
templateUrl: './gauzy-button-action.component.html',
77
styleUrls: ['./gauzy-button-action.component.scss'],
8+
changeDetection: ChangeDetectionStrategy.OnPush,
89
standalone: false
910
})
10-
export class GauzyButtonActionComponent implements OnInit {
11-
@Input() isDisable: boolean = true;
12-
@Input() hasLayoutSelector: boolean = true;
13-
@Input() componentName: ComponentEnum;
11+
export class GauzyButtonActionComponent {
12+
/** Whether the action buttons are disabled / hidden. */
13+
readonly isDisable = input<boolean>(true);
1414

15-
/** Typed as any to accept TemplateRef from plugin packages that may resolve a different @angular/core instance. */
16-
@Input() buttonTemplate: any;
17-
/** Typed as any to accept TemplateRef from plugin packages that may resolve a different @angular/core instance. */
18-
@Input() buttonTemplateVisible: any;
15+
/** Whether the layout selector toggle is shown. */
16+
readonly hasLayoutSelector = input<boolean>(true);
1917

20-
constructor() {}
21-
/**
22-
* not implemented
23-
*/
24-
ngOnInit(): void {}
18+
/** The component name passed to the layout selector. */
19+
readonly componentName = input<ComponentEnum>();
20+
21+
/** Template reference for the primary action button. */
22+
readonly buttonTemplate = input<TemplateRef<unknown>>();
23+
24+
/** Template reference for the visible-state button. */
25+
readonly buttonTemplateVisible = input<TemplateRef<unknown>>();
2526
}

0 commit comments

Comments
 (0)