diff --git a/apps/gauzy/src/app/@shared/pipes/time-format.pipe.ts b/apps/gauzy/src/app/@shared/pipes/time-format.pipe.ts
index 979eae53b86..8cc77113eb6 100644
--- a/apps/gauzy/src/app/@shared/pipes/time-format.pipe.ts
+++ b/apps/gauzy/src/app/@shared/pipes/time-format.pipe.ts
@@ -1,9 +1,9 @@
import { Pipe, PipeTransform, OnDestroy } from '@angular/core';
-import { Store } from '../../@core/services/store.service';
-import { IOrganization } from '@gauzy/contracts';
+import { filter, tap } from 'rxjs/operators';
import * as moment from 'moment';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
-import { filter } from 'rxjs/operators';
+import { IOrganization, TimeFormatEnum } from '@gauzy/contracts';
+import { Store } from '../../@core/services/store.service';
@UntilDestroy({ checkProperties: true })
@Pipe({
@@ -11,24 +11,34 @@ import { filter } from 'rxjs/operators';
pure: false
})
export class TimeFormatPipe implements PipeTransform, OnDestroy {
- private format: 12 | 24;
+ private format: TimeFormatEnum;
- constructor(private store: Store) {
+ constructor(private readonly store: Store) {
this.store.selectedOrganization$
.pipe(
filter((organization: IOrganization) => !!organization),
+ tap((organization: IOrganization) => {
+ this.format = organization?.timeFormat ?? TimeFormatEnum.FORMAT_12_HOURS;
+ }),
untilDestroyed(this)
)
- .subscribe((org: IOrganization) => {
- this.format = org ? org.timeFormat : 12;
- });
+ .subscribe();
}
- transform(value: any, seconds: boolean = false): any {
+ /**
+ * Transforms a given value into a formatted time string.
+ * @param value The value to transform into a time string. This can be a string, number, Date object, or any value parsable by moment.js.
+ * @param timeFormat The time format to use. If not provided, it defaults to `this.format`.
+ * @param seconds Optional. If true, include seconds in the formatted time string. Defaults to false.
+ * @returns A formatted time string based on the input value and format options.
+ */
+ transform(value: any, timeFormat: number = this.format, seconds: boolean = false): any {
let format = 'HH:mm' + (seconds ? ':ss' : '');
- if (this.format === 12) {
+
+ if (timeFormat === TimeFormatEnum.FORMAT_12_HOURS) {
format = 'hh:mm' + (seconds ? ':ss' : '') + ' A';
}
+
let date = moment(value);
if (!date.isValid()) {
date = moment.utc(value, 'HH:mm');
diff --git a/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/filters.component.scss b/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/filters.component.scss
index 785b4878525..c1a3d586647 100644
--- a/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/filters.component.scss
+++ b/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/filters.component.scss
@@ -29,7 +29,7 @@ $status: 'basic';
}
.filter-item-list {
.filter-item {
- max-width: 263px;
+ min-width: 220px;
}
.select-box,
nb-select {
@@ -51,23 +51,17 @@ $status: 'basic';
}
&:focus {
- background-color: nb-theme(
- select-outline-#{$status}-focus-background-color
- );
+ background-color: nb-theme(select-outline-#{$status}-focus-background-color);
border-color: nb-theme(select-outline-#{$status}-focus-border-color);
}
&:hover {
- background-color: nb-theme(
- select-outline-#{$status}-hover-background-color
- );
+ background-color: nb-theme(select-outline-#{$status}-hover-background-color);
border-color: nb-theme(select-outline-#{$status}-hover-border-color);
}
&[disabled] {
color: nb-theme(select-outline-#{$status}-disabled-text-color);
- background-color: nb-theme(
- select-outline-#{$status}-disabled-background-color
- );
+ background-color: nb-theme(select-outline-#{$status}-disabled-background-color);
border-color: nb-theme(select-outline-#{$status}-disabled-border-color);
nb-icon {
@@ -81,14 +75,10 @@ $status: 'basic';
}
&.top {
- border-top-color: nb-theme(
- select-outline-#{$status}-adjacent-border-color
- );
+ border-top-color: nb-theme(select-outline-#{$status}-adjacent-border-color);
}
&.bottom {
- border-bottom-color: nb-theme(
- select-outline-#{$status}-adjacent-border-color
- );
+ border-bottom-color: nb-theme(select-outline-#{$status}-adjacent-border-color);
}
}
}
diff --git a/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/gauzy-filters.component.html b/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/gauzy-filters.component.html
index b005187cd0b..8024c77b210 100644
--- a/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/gauzy-filters.component.html
+++ b/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/gauzy-filters.component.html
@@ -2,8 +2,12 @@
-
-
+
+
@@ -27,29 +31,22 @@
nbButton
status="basic"
outline
- [nbPopover]="templateRef"
nbPopoverPlacement="bottom"
+ [nbPopover]="activityLevelSliderTemplate"
+ nbPopoverTrigger="click"
>
- 0 || activityLevel?.end < 100; else selectLabel">
+ 0 || activityLevel?.end < 100; else selectActivityLevelLabel"
+ >
{{ 'TIMESHEET.ACTIVITY_LEVEL' | translate }} : {{ activityLevel?.start }}% -
{{ activityLevel?.end }}%
-
- {{ 'TIMESHEET.SELECT_ACTIVITY_LEVEL' | translate }}
-
-
-
-
-
-
+
+ {{ 'TIMESHEET.SELECT_ACTIVITY_LEVEL' | translate }}
+
@@ -75,3 +72,14 @@
+
+
+
+
+
+
diff --git a/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/gauzy-filters.component.ts b/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/gauzy-filters.component.ts
index 88997b9de7b..d89dae548ec 100644
--- a/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/gauzy-filters.component.ts
+++ b/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/gauzy-filters.component.ts
@@ -8,15 +8,15 @@ import {
OnInit,
Output
} from '@angular/core';
-import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
-import { Options, ChangeContext } from '@angular-slider/ngx-slider';
-import { ITimeLogFilters, PermissionsEnum, TimeLogSourceEnum, TimeLogType } from '@gauzy/contracts';
import { Subject } from 'rxjs';
import { debounceTime, take, tap } from 'rxjs/operators';
+import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { TranslateService } from '@ngx-translate/core';
import { pick } from 'underscore';
-import { ActivityLevel, TimesheetFilterService } from '../timesheet-filter.service';
+import { Options, ChangeContext } from '@angular-slider/ngx-slider';
+import { ITimeLogFilters, PermissionsEnum, TimeFormatEnum, TimeLogSourceEnum, TimeLogType } from '@gauzy/contracts';
import { TranslationBaseComponent } from '@gauzy/ui-sdk/shared';
+import { ActivityLevel, TimesheetFilterService } from '../timesheet-filter.service';
@UntilDestroy({ checkProperties: true })
@Component({
@@ -50,6 +50,7 @@ export class GauzyFiltersComponent extends TranslationBaseComponent implements A
*/
private filters$: Subject = new Subject();
private _filters: ITimeLogFilters = {
+ timeFormat: TimeFormatEnum.FORMAT_12_HOURS,
source: [],
logType: [],
activityLevel: ActivityLevel
@@ -67,6 +68,7 @@ export class GauzyFiltersComponent extends TranslationBaseComponent implements A
}
this.cd.detectChanges();
}
+ @Input() isTimeformat: boolean = false;
@Output() filtersChange: EventEmitter = new EventEmitter();
@@ -108,24 +110,38 @@ export class GauzyFiltersComponent extends TranslationBaseComponent implements A
this.cd.detectChanges();
}
- setActivityLevel($event: ChangeContext): void {
+ /**
+ *
+ * @param activity
+ */
+ setActivityLevel(activity: ChangeContext): void {
this.filters.activityLevel = {
- start: $event.value,
- end: $event.highValue
+ start: activity.value,
+ end: activity.highValue
};
this.activityLevel = this.filters.activityLevel;
this.triggerFilterChange();
}
+ /**
+ *
+ */
triggerFilterChange(): void {
this.filters$.next(true);
}
+ /**
+ *
+ */
clearFilters(): void {
this.filters = this.timesheetFilterService.clear();
this.triggerFilterChange();
}
+ /**
+ *
+ * @returns
+ */
hasFilter(): boolean {
return (
(this._filters.source && this._filters.source.length >= 1) ||
@@ -135,11 +151,35 @@ export class GauzyFiltersComponent extends TranslationBaseComponent implements A
);
}
+ /**
+ *
+ * @returns
+ */
arrangedFilters(): ITimeLogFilters {
Object.keys(this.filters).forEach((key) => (this.filters[key] === undefined ? delete this.filters[key] : {}));
return this.filters;
}
+ /**
+ * Handles the event when the time format is changed.
+ *
+ * @param timeformat The new time format.
+ */
+ timeFormatChanged(timeFormat: TimeFormatEnum): void {
+ this.filters.timeFormat = timeFormat;
+ this.triggerFilterChange();
+ }
+
+ /**
+ * Handles the event when the time zone is changed.
+ *
+ * @param timezone The new time zone.
+ */
+ timeZoneChanged(timeZone: string): void {
+ this.filters.timeZone = timeZone;
+ this.triggerFilterChange();
+ }
+
/**
* Generate Dynamic Timelog Source Selector
*/
diff --git a/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/gauzy-filters.module.ts b/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/gauzy-filters.module.ts
index 06fbfd428dc..09d9682e6c6 100644
--- a/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/gauzy-filters.module.ts
+++ b/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/gauzy-filters.module.ts
@@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
-import { NbButtonModule, NbIconModule, NbSelectModule } from '@nebular/theme';
+import { NbButtonModule, NbIconModule, NbPopoverModule, NbSelectModule } from '@nebular/theme';
import { NgxSliderModule } from '@angular-slider/ngx-slider';
import { TranslateModule } from '@gauzy/ui-sdk/i18n';
import { PipesModule } from '../../pipes/pipes.module';
@@ -16,6 +16,7 @@ import { TimezoneFilterModule } from './timezone-filter/timezone-filter.module';
FormsModule,
NbButtonModule,
NbIconModule,
+ NbPopoverModule,
NbSelectModule,
NgxSliderModule,
PipesModule,
diff --git a/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/timezone-filter/timezone-filter.component.html b/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/timezone-filter/timezone-filter.component.html
index 24e0bbf2bb1..4d95a187225 100644
--- a/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/timezone-filter/timezone-filter.component.html
+++ b/apps/gauzy/src/app/@shared/timesheet/gauzy-filters/timezone-filter/timezone-filter.component.html
@@ -1,19 +1,21 @@
-
-
-
+
+
Time Zone
@@ -33,7 +35,7 @@
-
Time Format
+
Time Format
();
@Output() timeFormatChange = new EventEmitter();
constructor(
private readonly _route: ActivatedRoute,
- private readonly _router: Router,
- private readonly _store: Store
+ private readonly _store: Store,
+ private readonly _navigationService: NavigationService
) {}
ngOnInit(): void {
@@ -87,8 +58,8 @@ export class TimezoneFilterComponent implements AfterViewInit, OnInit, OnDestroy
combineLatest([queryParams$, storeOrganization$])
.pipe(
tap(([queryParams, organization]) => {
- this.applyTimeFormat(queryParams, organization.timeFormat);
- this.applyTimeZone(queryParams, TimeZoneEnum.ORG_TIMEZONE);
+ if (this.isTimeformat) this.applyTimeFormat(queryParams, organization.timeFormat);
+ if (this.isTimezone) this.applyTimeZone(queryParams, TimeZoneEnum.ORG_TIMEZONE);
}),
// Handle component lifecycle to avoid memory leaks
untilDestroyed(this)
@@ -110,8 +81,8 @@ export class TimezoneFilterComponent implements AfterViewInit, OnInit, OnDestroy
.pipe(
distinctUntilChange(),
tap(([queryParams, user]) => {
- this.applyTimeFormat(queryParams, user.timeFormat);
- this.applyTimeZone(queryParams, TimeZoneEnum.MINE_TIMEZONE);
+ if (this.isTimeformat) this.applyTimeFormat(queryParams, user.timeFormat);
+ if (this.isTimezone) this.applyTimeZone(queryParams, TimeZoneEnum.MINE_TIMEZONE);
}),
// Handle component lifecycle to avoid memory leaks
untilDestroyed(this)
@@ -121,18 +92,18 @@ export class TimezoneFilterComponent implements AfterViewInit, OnInit, OnDestroy
/**
* Applies the appropriate time format based on query parameters, organization settings, and employee settings.
+ *
* @param queryParams The query parameters from the route.
* @param organization The organization details.
- * @param employee The selected employee details.
*/
private applyTimeFormat(queryParams: Params, timeFormat: number): void {
const { time_format } = queryParams;
// Apply query parameters first
if (time_format) {
- this.selectTimeFormat(time_format);
+ this.updateSelectedTimeFormat(parseInt(time_format, 10));
} else {
- this.selectTimeFormat(timeFormat);
+ this.updateSelectedTimeFormat(timeFormat);
}
}
@@ -146,9 +117,9 @@ export class TimezoneFilterComponent implements AfterViewInit, OnInit, OnDestroy
// Apply query parameters first
if (time_zone) {
- this.selectTimeZone(time_zone);
+ this.updateSelectedTimeZone(time_zone);
} else {
- this.selectTimeZone(timeZone);
+ this.updateSelectedTimeZone(timeZone);
}
}
@@ -190,14 +161,10 @@ export class TimezoneFilterComponent implements AfterViewInit, OnInit, OnDestroy
this.timeFormatChange.emit(timeFormat);
- if (this.navigate) {
- // Update query parameter 'time_format'
- await this._router.navigate([], {
- relativeTo: this._route,
- queryParams: { time_format: timeFormat.toString() },
- queryParamsHandling: 'merge'
- });
- }
+ // Updates the query parameters of the current route without navigating away.
+ await this._navigationService.updateQueryParams({
+ time_format: timeFormat.toString()
+ });
}
/**
@@ -211,14 +178,10 @@ export class TimezoneFilterComponent implements AfterViewInit, OnInit, OnDestroy
this.timeZoneChange.emit(this.getTimeZone(this.selectedTimeZone));
- if (this.navigate) {
- // Update query parameter 'time_zone'
- await this._router.navigate([], {
- relativeTo: this._route,
- queryParams: { time_zone: timeZone.toString() },
- queryParamsHandling: 'merge'
- });
- }
+ // Updates the query parameters of the current route without navigating away.
+ await this._navigationService.updateQueryParams({
+ time_zone: timeZone.toString()
+ });
}
/**
diff --git a/apps/gauzy/src/app/@shared/timesheet/screenshots/screenshots-item/screenshots-item.component.html b/apps/gauzy/src/app/@shared/timesheet/screenshots/screenshots-item/screenshots-item.component.html
index c75193353d4..5cbcc035d81 100644
--- a/apps/gauzy/src/app/@shared/timesheet/screenshots/screenshots-item/screenshots-item.component.html
+++ b/apps/gauzy/src/app/@shared/timesheet/screenshots/screenshots-item/screenshots-item.component.html
@@ -1,18 +1,12 @@
-
+
-
1"
- [ngIfElse]="lastScreenshotTemplate"
- >
+ 1" [ngIfElse]="lastScreenshotTemplate">
-
+
{{ employees.length }}
@@ -46,9 +40,7 @@
[message]="'ACTIVITY.DELETE_CONFIRM' | translate"
(confirm)="deleteSlot(timeSlot)"
>
-
+
-
+
{{ 'ACTIVITY.NO_SCREENSHOT' | translate }}
@@ -105,8 +93,8 @@
- {{ timeSlot.localStartedAt | timeFormat }} -
- {{ timeSlot.localStoppedAt | timeFormat }}
+ {{ timeSlot.localStartedAt | timeFormat : timeformat }} -
+ {{ timeSlot.localStoppedAt | timeFormat : timeformat }}
{{ timeSlot.localStartedAt | dateFormat }}
@@ -120,12 +108,7 @@
>
{{ timeSlot.percentage || 0 }}% of
- {{
- timeSlot.duration
- | amFromUnix
- | amFromUtc
- | amDateFormat: 'mm'
- }}
+ {{ timeSlot.duration | amFromUnix | amFromUtc | amDateFormat : 'mm' }}
{{ 'ACTIVITY.MINUTES' | translate }}
@@ -138,14 +121,8 @@
-
-
+
+
diff --git a/apps/gauzy/src/app/@shared/timesheet/screenshots/screenshots-item/screenshots-item.component.ts b/apps/gauzy/src/app/@shared/timesheet/screenshots/screenshots-item/screenshots-item.component.ts
index 4aa693446d8..7546ec1faf7 100644
--- a/apps/gauzy/src/app/@shared/timesheet/screenshots/screenshots-item/screenshots-item.component.ts
+++ b/apps/gauzy/src/app/@shared/timesheet/screenshots/screenshots-item/screenshots-item.component.ts
@@ -1,5 +1,5 @@
import { Component, OnInit, Input, OnDestroy, Output, EventEmitter } from '@angular/core';
-import { ITimeSlot, IScreenshot, ITimeLog, IOrganization, IEmployee } from '@gauzy/contracts';
+import { ITimeSlot, IScreenshot, ITimeLog, IOrganization, IEmployee, TimeFormatEnum } from '@gauzy/contracts';
import { NbDialogService } from '@nebular/theme';
import { filter, take, tap } from 'rxjs/operators';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
@@ -107,6 +107,8 @@ export class ScreenshotsItemComponent implements OnInit, OnDestroy {
this._lastScreenshot = screenshot;
}
+ @Input() timeformat = TimeFormatEnum.FORMAT_12_HOURS;
+
constructor(
private readonly nbDialogService: NbDialogService,
private readonly timesheetService: TimesheetService,
diff --git a/apps/gauzy/src/app/pages/dashboard/time-tracking/time-tracking.component.html b/apps/gauzy/src/app/pages/dashboard/time-tracking/time-tracking.component.html
index 1f1b7873159..7160cd5b2a5 100644
--- a/apps/gauzy/src/app/pages/dashboard/time-tracking/time-tracking.component.html
+++ b/apps/gauzy/src/app/pages/dashboard/time-tracking/time-tracking.component.html
@@ -279,6 +279,7 @@
= new BehaviorSubject(null);
+ public filters: ITimeLogFilters = { timeFormat: TimeFormatEnum.FORMAT_12_HOURS };
+ public payloads$: BehaviorSubject = new BehaviorSubject(null);
@ViewChildren('widget') listOfWidgets: QueryList>;
@ViewChildren('window') listOfWindows: QueryList>;
@@ -243,6 +244,10 @@ export class TimeTrackingComponent
this.changeRef.detectChanges();
}
+ /**
+ *
+ * @returns
+ */
async getStatistics() {
if (!this.organization) {
return;
@@ -279,7 +284,8 @@ export class TimeTrackingComponent
todayStart: toUTC(moment().startOf('day')).format('YYYY-MM-DD HH:mm:ss'),
todayEnd: toUTC(moment().endOf('day')).format('YYYY-MM-DD HH:mm:ss'),
startDate: toUTC(startDate).format('YYYY-MM-DD HH:mm:ss'),
- endDate: toUTC(endDate).format('YYYY-MM-DD HH:mm:ss')
+ endDate: toUTC(endDate).format('YYYY-MM-DD HH:mm:ss'),
+ timeZone: this.filters.timeZone
};
if (isNotEmpty(employeeIds)) {
@@ -295,6 +301,11 @@ export class TimeTrackingComponent
this.payloads$.next(request);
}
+ /**
+ * Sets the auto refresh functionality.
+ *
+ * @param value - Determines if auto refresh should be enabled.
+ */
setAutoRefresh(value: boolean) {
if (this.autoRefresh$) {
this.autoRefresh$.unsubscribe();
@@ -310,37 +321,55 @@ export class TimeTrackingComponent
}
}
- async getTimeSlots() {
+ /**
+ * Fetches the time slots statistics.
+ *
+ * @returns Promise
+ */
+ async getTimeSlots(): Promise {
if (this._isWindowHidden(Windows.RECENT_ACTIVITIES)) return;
- const request: IGetTimeSlotStatistics = this.payloads$.getValue();
+
try {
this.timeSlotLoading = true;
+ const request: IGetTimeSlotStatistics = this.payloads$.getValue();
this.timeSlotEmployees = await this.timesheetStatisticsService.getTimeSlots(request);
} catch (error) {
- this.toastrService.error(error);
+ this.toastrService.error(error.message || 'An error occurred while fetching time slots.');
} finally {
this.timeSlotLoading = false;
}
}
- async getCounts() {
+ /**
+ * Fetches the counts statistics.
+ *
+ * @returns Promise
+ */
+ async getCounts(): Promise {
if (this._isAllWidgetsHidden()) return;
- const request: IGetCountsStatistics = this.payloads$.getValue();
+
try {
this.countsLoading = true;
+ const request: IGetCountsStatistics = this.payloads$.getValue();
this.counts = await this.timesheetStatisticsService.getCounts(request);
} catch (error) {
- this.toastrService.error(error);
+ this.toastrService.error(error.message || 'An error occurred while fetching counts.');
} finally {
this.countsLoading = false;
}
}
- async getActivities() {
+ /**
+ * Fetches the activities statistics.
+ *
+ * @returns Promise
+ */
+ async getActivities(): Promise {
if (this._isWindowHidden(Windows.APPS_URLS)) return;
- const request: IGetActivitiesStatistics = this.payloads$.getValue();
+
try {
this.activitiesLoading = true;
+ const request: IGetActivitiesStatistics = this.payloads$.getValue();
const activities = await this.timesheetStatisticsService.getActivities(request);
const sum = reduce(activities, (memo, activity) => memo + parseInt(activity.duration + '', 10), 0);
this.activities = (activities || []).map((activity) => {
@@ -348,65 +377,89 @@ export class TimeTrackingComponent
return activity;
});
} catch (error) {
- this.toastrService.error(error);
+ this.toastrService.error(error.message || 'An error occurred while fetching activities.');
} finally {
this.activitiesLoading = false;
}
}
- async getProjects() {
+ /**
+ * Fetches the projects statistics.
+ *
+ * @returns Promise
+ */
+ async getProjects(): Promise {
if (this._isWindowHidden(Windows.PROJECTS)) return;
- const request: IGetProjectsStatistics = this.payloads$.getValue();
+
try {
this.projectsLoading = true;
+ const request: IGetProjectsStatistics = this.payloads$.getValue();
this.projects = await this.timesheetStatisticsService.getProjects(request);
} catch (error) {
- this.toastrService.error(error);
+ this.toastrService.error(error.message || 'An error occurred while fetching projects.');
} finally {
this.projectsLoading = false;
}
}
- async getTasks() {
+ /**
+ * Fetches the tasks statistics.
+ *
+ * @returns Promise
+ */
+ async getTasks(): Promise {
if (this._isWindowHidden(Windows.TASKS)) return;
- const request: IGetTasksStatistics = this.payloads$.getValue();
- const take = 5;
+
try {
this.tasksLoading = true;
- this.tasks = await this.timesheetStatisticsService.getTasks({
- ...request,
- take
- });
+
+ const request: IGetTasksStatistics = this.payloads$.getValue();
+ const take = 5;
+
+ this.tasks = await this.timesheetStatisticsService.getTasks({ ...request, take });
} catch (error) {
- this.toastrService.error(error);
+ this.toastrService.error(error.message || 'An error occurred while fetching tasks.');
} finally {
this.tasksLoading = false;
}
}
- async getManualTimes() {
+ /**
+ * Fetches the manual times statistics.
+ *
+ * @returns Promise
+ */
+ async getManualTimes(): Promise {
if (this._isWindowHidden(Windows.MANUAL_TIMES)) return;
- const request: IGetManualTimesStatistics = this.payloads$.getValue();
+
try {
this.manualTimeLoading = true;
+ const request: IGetManualTimesStatistics = this.payloads$.getValue();
this.manualTimes = await this.timesheetStatisticsService.getManualTimes(request);
} catch (error) {
- this.toastrService.error(error);
+ this.toastrService.error(error.message || 'An error occurred while fetching manual times.');
} finally {
this.manualTimeLoading = false;
}
}
- async getMembers() {
+ /**
+ * Fetches the members statistics.
+ *
+ * @returns Promise
+ */
+ async getMembers(): Promise {
if (
!(await this.ngxPermissionsService.hasPermission(PermissionsEnum.CHANGE_SELECTED_EMPLOYEE)) ||
this._isWindowHidden(Windows.MEMBERS)
) {
return;
}
- const request: IGetMembersStatistics = this.payloads$.getValue();
+
try {
this.memberLoading = true;
+
+ const request: IGetMembersStatistics = this.payloads$.getValue();
const members = await this.timesheetStatisticsService.getMembers(request);
this.members = (members || []).map((member) => {
@@ -423,7 +476,7 @@ export class TimeTrackingComponent
return member;
});
} catch (error) {
- this.toastrService.error(error);
+ this.toastrService.error(error.message || 'An error occurred while fetching members.');
} finally {
this.memberLoading = false;
}
@@ -437,6 +490,9 @@ export class TimeTrackingComponent
this.galleryService.clearGallery();
}
+ /**
+ *
+ */
get period() {
if (!this.selectedDateRange) {
return;
@@ -799,13 +855,19 @@ export class TimeTrackingComponent
/**
* Handles the event when the time format is changed.
- * @param timeformat The new time format.
+ *
+ * @param timeFormat The new time format.
*/
- timeFormatChanged(timeformat: TimeFormatEnum): void {}
+ timeFormatChanged(timeFormat: TimeFormatEnum): void {
+ this.filters.timeFormat = timeFormat;
+ }
/**
* Handles the event when the time zone is changed.
- * @param timezone The new time zone.
+ *
+ * @param timeZone The new time zone.
*/
- timeZoneChanged(timezone: string): void {}
+ timeZoneChanged(timeZone: string): void {
+ this.filters.timeZone = timeZone;
+ }
}
diff --git a/apps/gauzy/src/app/pages/employees/activity/screenshot/screenshot/screenshot.component.html b/apps/gauzy/src/app/pages/employees/activity/screenshot/screenshot/screenshot.component.html
index 9c12a8036e9..736c95bedd9 100644
--- a/apps/gauzy/src/app/pages/employees/activity/screenshot/screenshot/screenshot.component.html
+++ b/apps/gauzy/src/app/pages/employees/activity/screenshot/screenshot/screenshot.component.html
@@ -1,13 +1,11 @@
-
+
0" [ngIfElse]="notFound">
-
+
-
+
- {{ hourSlot.startTime | timeFormat }} -
- {{ hourSlot.endTime | timeFormat }}
+ {{ hourSlot.startTime | timeFormat : filters?.timeFormat }} -
+ {{ hourSlot.endTime | timeFormat : filters?.timeFormat }}
-
0"
@@ -49,23 +44,17 @@
-
+
-
+
0">
-
- {{ this.selectedIdsCount }} Screen selected
-
+
{{ this.selectedIdsCount }} Screen selected
@@ -41,15 +36,11 @@
-
+
-
+
diff --git a/apps/gauzy/src/app/pages/reports/expenses-report/expenses-report/expenses-report.component.scss b/apps/gauzy/src/app/pages/reports/expenses-report/expenses-report/expenses-report.component.scss
index 9a2c779e47d..097ba829b51 100644
--- a/apps/gauzy/src/app/pages/reports/expenses-report/expenses-report/expenses-report.component.scss
+++ b/apps/gauzy/src/app/pages/reports/expenses-report/expenses-report/expenses-report.component.scss
@@ -2,33 +2,28 @@
@import 'gauzy/_gauzy-overrides';
:host {
- ::ng-deep {
- .action-select button {
- background-color: nb-theme(gauzy-card-1) !important;
- border-radius: nb-theme(select-rectangle-border-radius);
- }
+ ::ng-deep {
+ .action-select button {
+ background-color: nb-theme(gauzy-card-1) !important;
+ border-radius: nb-theme(select-rectangle-border-radius);
}
+ }
}
:host {
.filters {
display: flex;
justify-content: flex-end;
- gap: 5px;
}
::ng-deep {
ng-select {
min-width: 10rem;
}
- @include ng-select-overrides(
- 2rem,
- $default-button-radius,
- $default-box-shadow
- );
+ @include ng-select-overrides(2rem, $default-button-radius, $default-box-shadow);
}
}
.report-body {
- height: calc(100% - 8rem) !important;
+ height: calc(100% - 8rem) !important;
}
diff --git a/apps/gauzy/src/app/pages/reports/manual-time/manual-time/manual-time.component.html b/apps/gauzy/src/app/pages/reports/manual-time/manual-time/manual-time.component.html
index 65ecc475548..d9b783c1175 100644
--- a/apps/gauzy/src/app/pages/reports/manual-time/manual-time/manual-time.component.html
+++ b/apps/gauzy/src/app/pages/reports/manual-time/manual-time/manual-time.component.html
@@ -1,13 +1,11 @@
-
-
-
- {{ 'REPORT_PAGE.MANUAL_TIME_EDIT_REPORT' | translate }}
-
+
+
+
+ {{ 'REPORT_PAGE.MANUAL_TIME_EDIT_REPORT' | translate }}
+
-
+
[hasActivityLevelFilter]="false"
(filtersChange)="filtersChange($event)"
>
-
+
+
+
@@ -49,75 +49,91 @@
-
+
-
+
+ employeeEl;
+ context: { $implicit: timeLogRow?.employee }
+ "
+ >
-
+
+ projectEl;
+ context: { $implicit: timeLogRow?.project }
+ "
+ >
-
+
+ taskEl;
+ context: { $implicit: timeLogRow?.task }
+ "
+ >
-
+
{{ timeLogRow.reason || '—' }}
-
+
{{ timeLogRow.createdAt | timeFormat }}
-
+
{{ timeLogRow.duration | durationFormat }}
-
+
{{ timeLogRow.editedAt | dateTimeFormat }}
+
-
+ class="responsive-table-content day-col"
+ [ngSwitch]="timeLogRow.isEdited"
+ >
-
-
-
- {{ 'REPORT_PAGE.NO_PROJECT' | translate }}
-
+
+
+ {{ 'REPORT_PAGE.NO_PROJECT' | translate }}
+
- {{ task?.title }}
-
- {{ 'REPORT_PAGE.NO_TASK' | translate }}
-
+ {{ task?.title }}
+
+ {{ 'REPORT_PAGE.NO_TASK' | translate }}
+
-
-
-
-
- {{ 'REPORT_PAGE.NO_EMPLOYEE' | translate }}
-
+
+
+
+
+ {{ 'REPORT_PAGE.NO_EMPLOYEE' | translate }}
+
- 0">
-
-
- {{ 'REPORT_PAGE.EMPLOYEE' | translate }}
-
-
- {{ 'REPORT_PAGE.PROJECT' | translate }}
-
-
- {{ 'REPORT_PAGE.TO_DO' | translate }}
-
-
- {{ 'REPORT_PAGE.REASON' | translate }}
-
-
- {{ 'REPORT_PAGE.FROM' | translate }}
-
-
- {{ 'REPORT_PAGE.TIME_SPAN' | translate }}
-
-
- {{ 'REPORT_PAGE.CHANGED_AT' | translate }}
-
-
- {{ 'REPORT_PAGE.ACTION' | translate }}
-
-
-
+ 0">
+
+
+ {{ 'REPORT_PAGE.EMPLOYEE' | translate }}
+
+
+ {{ 'REPORT_PAGE.PROJECT' | translate }}
+
+
+ {{ 'REPORT_PAGE.TO_DO' | translate }}
+
+
+ {{ 'REPORT_PAGE.REASON' | translate }}
+
+
+ {{ 'REPORT_PAGE.FROM' | translate }}
+
+
+ {{ 'REPORT_PAGE.TIME_SPAN' | translate }}
+
+
+ {{ 'REPORT_PAGE.CHANGED_AT' | translate }}
+
+
+ {{ 'REPORT_PAGE.ACTION' | translate }}
+
+
+
diff --git a/packages/common-angular/src/utils/shared-utils.ts b/packages/common-angular/src/utils/shared-utils.ts
index ef82e28376f..1d20e02fc37 100644
--- a/packages/common-angular/src/utils/shared-utils.ts
+++ b/packages/common-angular/src/utils/shared-utils.ts
@@ -24,6 +24,15 @@ export function isNotNullOrUndefined
(value: T | undefined | null): value is T
return value !== undefined && value !== null;
}
+/**
+ * Check if a value is null, undefined, or an empty string.
+ * @param value The value to check.
+ * @returns true if the value is null, undefined, or an empty string, false otherwise.
+ */
+export function isNotNullOrUndefinedOrEmpty(value: T | undefined | null): boolean {
+ return isNotNullOrUndefined(value) && value !== '';
+}
+
// It will use for pass nested object or array in query params in get method.
export function toParams(query) {
let params: HttpParams = new HttpParams();
diff --git a/packages/contracts/src/timesheet.model.ts b/packages/contracts/src/timesheet.model.ts
index 11d06f24db2..d0599f6ca8b 100644
--- a/packages/contracts/src/timesheet.model.ts
+++ b/packages/contracts/src/timesheet.model.ts
@@ -1,21 +1,11 @@
-import {
- IBasePerTenantAndOrganizationEntityModel,
- IBaseRelationsEntityModel,
-} from './base-entity.model';
-import {
- IOrganizationContact,
- OrganizationContactBudgetTypeEnum,
-} from './organization-contact.model';
+import { IBasePerTenantAndOrganizationEntityModel, IBaseRelationsEntityModel } from './base-entity.model';
+import { IOrganizationContact, OrganizationContactBudgetTypeEnum } from './organization-contact.model';
import {
IOrganizationProject,
IRelationalOrganizationProject,
- OrganizationProjectBudgetTypeEnum,
+ OrganizationProjectBudgetTypeEnum
} from './organization-projects.model';
-import {
- IEmployee,
- IEmployeeFindInput,
- IRelationalEmployee,
-} from './employee.model';
+import { IEmployee, IEmployeeFindInput, IRelationalEmployee } from './employee.model';
import { ITask } from './task.model';
import { ITag } from './tag.model';
import { IPaginationInput } from './core.model';
@@ -23,6 +13,7 @@ import { ReportGroupByFilter } from './report.model';
import { IUser } from './user.model';
import { IRelationalOrganizationTeam } from './organization-team.model';
import { IScreenshot } from './screenshot.model';
+import { TimeFormatEnum } from './organization.model';
export interface ITimesheet extends IBasePerTenantAndOrganizationEntityModel {
employee: IEmployee;
@@ -78,17 +69,15 @@ export enum TimesheetStatus {
PENDING = 'PENDING',
IN_REVIEW = 'IN REVIEW',
DENIED = 'DENIED',
- APPROVED = 'APPROVED',
+ APPROVED = 'APPROVED'
}
-export interface IUpdateTimesheetStatusInput
- extends IBasePerTenantAndOrganizationEntityModel {
+export interface IUpdateTimesheetStatusInput extends IBasePerTenantAndOrganizationEntityModel {
ids: string | string[];
status?: TimesheetStatus;
}
-export interface ISubmitTimesheetInput
- extends IBasePerTenantAndOrganizationEntityModel {
+export interface ISubmitTimesheetInput extends IBasePerTenantAndOrganizationEntityModel {
ids: string | string[];
status: 'submit' | 'unsubmit';
}
@@ -106,7 +95,10 @@ export interface IDateRange {
start: Date;
end: Date;
}
-export interface ITimeLog extends IBasePerTenantAndOrganizationEntityModel, IRelationalOrganizationProject, IRelationalOrganizationTeam {
+export interface ITimeLog
+ extends IBasePerTenantAndOrganizationEntityModel,
+ IRelationalOrganizationProject,
+ IRelationalOrganizationTeam {
employee: IEmployee;
employeeId: IEmployee['id'];
timesheet?: ITimesheet;
@@ -133,8 +125,7 @@ export interface ITimeLog extends IBasePerTenantAndOrganizationEntityModel, IRel
isEdited?: boolean;
}
-export interface ITimeLogCreateInput
- extends IBasePerTenantAndOrganizationEntityModel {
+export interface ITimeLogCreateInput extends IBasePerTenantAndOrganizationEntityModel {
employeeId: string;
timesheetId?: string;
taskId?: string;
@@ -148,8 +139,7 @@ export interface ITimeLogCreateInput
isBilled?: boolean;
}
-export interface ITimeSlotCreateInput
- extends IBasePerTenantAndOrganizationEntityModel {
+export interface ITimeSlotCreateInput extends IBasePerTenantAndOrganizationEntityModel {
employeeId: string;
duration: number;
keyboard: number;
@@ -163,12 +153,12 @@ export enum TimeLogType {
TRACKED = 'TRACKED',
MANUAL = 'MANUAL',
IDLE = 'IDLE',
- RESUMED = 'RESUMED',
+ RESUMED = 'RESUMED'
}
export enum ManualTimeLogAction {
ADDED = 'ADDED',
- EDITED = 'EDITED',
+ EDITED = 'EDITED'
}
export enum TimeLogSourceEnum {
@@ -178,11 +168,10 @@ export enum TimeLogSourceEnum {
BROWSER_EXTENSION = 'BROWSER_EXTENSION',
HUBSTAFF = 'HUBSTAFF',
UPWORK = 'UPWORK',
- TEAMS = 'TEAMS',
+ TEAMS = 'TEAMS'
}
-export interface ITimeLogFilters
- extends IBasePerTenantAndOrganizationEntityModel {
+export interface ITimeLogFilters extends IBasePerTenantAndOrganizationEntityModel {
date?: Date | string;
startDate?: Date | string;
endDate?: Date | string;
@@ -199,11 +188,12 @@ export interface ITimeLogFilters
taskIds?: string[];
defaultRange?: boolean;
unitOfTime?: any;
- categoryId?: string
+ categoryId?: string;
+ timeZone?: string;
+ timeFormat?: TimeFormatEnum;
}
-export interface ITimeLogTodayFilters
- extends IBasePerTenantAndOrganizationEntityModel {
+export interface ITimeLogTodayFilters extends IBasePerTenantAndOrganizationEntityModel {
todayStart?: Date | string;
todayEnd?: Date | string;
}
@@ -231,16 +221,14 @@ export interface ITimeSlot extends IBasePerTenantAndOrganizationEntityModel {
isAllowDelete?: boolean;
}
-export interface ITimeSlotTimeLogs
- extends IBasePerTenantAndOrganizationEntityModel {
+export interface ITimeSlotTimeLogs extends IBasePerTenantAndOrganizationEntityModel {
timeLogs: ITimeLog[];
timeSlots: ITimeSlot[];
timeLogId: string;
timeSlotId: string;
}
-export interface ITimeSlotMinute
- extends IBasePerTenantAndOrganizationEntityModel {
+export interface ITimeSlotMinute extends IBasePerTenantAndOrganizationEntityModel {
timeSlot?: ITimeSlot;
timeSlotId?: string;
keyboard?: number;
@@ -282,8 +270,7 @@ export interface IDailyActivity {
childItems?: IDailyActivity[];
}
-export interface ICreateActivityInput
- extends IBasePerTenantAndOrganizationEntityModel {
+export interface ICreateActivityInput extends IBasePerTenantAndOrganizationEntityModel {
employeeId?: string;
projectId?: string;
duration?: number;
@@ -300,7 +287,7 @@ export interface ICreateActivityInput
export enum ActivityType {
URL = 'URL',
- APP = 'APP',
+ APP = 'APP'
}
export interface IURLMetaData {
@@ -310,9 +297,11 @@ export interface IURLMetaData {
[x: string]: any;
}
-
-
-export interface ITimerStatusInput extends ITimeLogTodayFilters, IBaseRelationsEntityModel, IRelationalEmployee, IRelationalOrganizationTeam {
+export interface ITimerStatusInput
+ extends ITimeLogTodayFilters,
+ IBaseRelationsEntityModel,
+ IRelationalEmployee,
+ IRelationalOrganizationTeam {
source?: TimeLogSourceEnum;
employeeIds?: string[];
}
@@ -340,7 +329,7 @@ export interface ITimerPosition {
export interface ITimerToggleInput
extends IBasePerTenantAndOrganizationEntityModel,
- Pick {
+ Pick {
projectId?: string;
taskId?: string;
organizationContactId?: string;
@@ -382,32 +371,24 @@ export interface IGetTimeLogReportInput extends IGetTimeLogInput {
isEdited?: boolean;
}
-export interface IGetTimeLogConflictInput
- extends IBasePerTenantAndOrganizationEntityModel,
- IBaseRelationsEntityModel {
+export interface IGetTimeLogConflictInput extends IBasePerTenantAndOrganizationEntityModel, IBaseRelationsEntityModel {
ignoreId?: string | string[];
startDate: string | Date;
endDate: string | Date;
employeeId: string;
}
-export interface IGetTimeSlotInput
- extends ITimeLogFilters,
- IBaseRelationsEntityModel {
+export interface IGetTimeSlotInput extends ITimeLogFilters, IBaseRelationsEntityModel {
[x: string]: any;
}
-export interface IGetActivitiesInput
- extends ITimeLogFilters,
- IPaginationInput,
- IBaseRelationsEntityModel {
+export interface IGetActivitiesInput extends ITimeLogFilters, IPaginationInput, IBaseRelationsEntityModel {
types?: string[];
titles?: string[];
groupBy?: string;
}
-export interface IBulkActivitiesInput
- extends IBasePerTenantAndOrganizationEntityModel {
+export interface IBulkActivitiesInput extends IBasePerTenantAndOrganizationEntityModel {
employeeId: string;
projectId?: string;
activities: IActivity[];
@@ -528,13 +509,11 @@ export interface IClientBudgetLimitReport {
remainingBudget?: number;
}
-export interface IDeleteTimeSlot
- extends IBasePerTenantAndOrganizationEntityModel {
+export interface IDeleteTimeSlot extends IBasePerTenantAndOrganizationEntityModel {
ids: string[];
}
-export interface IDeleteTimeLog
- extends IBasePerTenantAndOrganizationEntityModel {
+export interface IDeleteTimeLog extends IBasePerTenantAndOrganizationEntityModel {
logIds: string[];
forceDelete: boolean;
}
diff --git a/packages/ui-sdk/src/lib/common/src/utils/shared-utils.ts b/packages/ui-sdk/src/lib/common/src/utils/shared-utils.ts
index 465b63161df..52ba986b1e9 100644
--- a/packages/ui-sdk/src/lib/common/src/utils/shared-utils.ts
+++ b/packages/ui-sdk/src/lib/common/src/utils/shared-utils.ts
@@ -25,6 +25,15 @@ export function isNotNullOrUndefined(value: T | undefined | null): value is T
return value !== undefined && value !== null;
}
+/**
+ * Check if a value is null, undefined, or an empty string.
+ * @param value The value to check.
+ * @returns true if the value is null, undefined, or an empty string, false otherwise.
+ */
+export function isNotNullOrUndefinedOrEmpty(value: T | undefined | null): boolean {
+ return isNotNullOrUndefined(value) && value !== '';
+}
+
// It will use for pass nested object or array in query params in get method.
export function toParams(query: any) {
let params: HttpParams = new HttpParams();
diff --git a/packages/ui-sdk/src/lib/core/src/services/navigation/navigation.service.ts b/packages/ui-sdk/src/lib/core/src/services/navigation/navigation.service.ts
index e8197d75cf1..7300454a434 100644
--- a/packages/ui-sdk/src/lib/core/src/services/navigation/navigation.service.ts
+++ b/packages/ui-sdk/src/lib/core/src/services/navigation/navigation.service.ts
@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { ActivatedRoute, QueryParamsHandling, Router } from '@angular/router';
import { Location } from '@angular/common';
+import { isNotNullOrUndefinedOrEmpty } from '@gauzy/ui-sdk/common';
@Injectable({
providedIn: 'root'
@@ -70,7 +71,7 @@ export class NavigationService {
const uniqueQueryParams: { [key: string]: string | string[] | boolean } = {};
for (const key in finalQueryParams) {
const value = finalQueryParams[key];
- if (typeof value != 'undefined') {
+ if (isNotNullOrUndefinedOrEmpty(value)) {
if (Array.isArray(value)) {
uniqueQueryParams[key] = Array.from(new Set(value));
} else {