diff --git a/angular.json b/angular.json index 6e1d6383a9e..3fae3c6b3e3 100755 --- a/angular.json +++ b/angular.json @@ -218,14 +218,14 @@ "ui-config": { "projectType": "library", "root": "packages/ui-config", - "sourceRoot": "packages/ui-config", + "sourceRoot": "packages/ui-config/src", "prefix": "lib", "architect": { "build": { "builder": "@angular-devkit/build-angular:ng-packagr", "options": { - "project": "packages/ui-config/ng-package.json", - "tsConfig": "packages/ui-config/tsconfig.lib.json" + "tsConfig": "packages/ui-config/tsconfig.lib.json", + "project": "packages/ui-config/ng-package.json" }, "configurations": { "production": { @@ -240,6 +240,7 @@ "test": { "builder": "@angular-devkit/build-angular:karma", "options": { + "main": "packages/ui-config/src/test-setup.ts", "tsConfig": "packages/ui-config/tsconfig.spec.json", "polyfills": ["zone.js", "zone.js/testing"] } diff --git a/apps/gauzy/src/app/pages/dashboard/accounting/accounting.component.ts b/apps/gauzy/src/app/pages/dashboard/accounting/accounting.component.ts index eea039b4eea..7fa295109ff 100644 --- a/apps/gauzy/src/app/pages/dashboard/accounting/accounting.component.ts +++ b/apps/gauzy/src/app/pages/dashboard/accounting/accounting.component.ts @@ -231,6 +231,10 @@ export class AccountingComponent extends TranslationBaseComponent implements Aft * @param employee - The selected employee information. */ async selectEmployee(employee: ISelectedEmployee) { + if (!employee.id) { + return; + } + // Fetch detailed information about the selected employee from the employeesService const people = await firstValueFrom(this.employeesService.getEmployeeById(employee.id, ['user'])); diff --git a/apps/gauzy/src/app/pages/dashboard/time-tracking/time-tracking.component.ts b/apps/gauzy/src/app/pages/dashboard/time-tracking/time-tracking.component.ts index 0e729d3fa52..41d8204e571 100644 --- a/apps/gauzy/src/app/pages/dashboard/time-tracking/time-tracking.component.ts +++ b/apps/gauzy/src/app/pages/dashboard/time-tracking/time-tracking.component.ts @@ -418,16 +418,19 @@ export class TimeTrackingComponent async getTasks(): Promise { if (this._isWindowHidden(Windows.TASKS)) return; - try { - this.tasksLoading = true; + // Set loading state to true + this.tasksLoading = true; + try { const request: IGetTasksStatistics = this.payloads$.getValue(); const take = 5; - this.tasks = await this._timesheetStatisticsService.getTasks({ ...request, take }); + // Fetch tasks statistics + this.tasks = await this._timesheetStatisticsService.getTasksStatistics({ ...request, take }); } catch (error) { this._toastrService.error(error.message || 'An error occurred while fetching tasks.'); } finally { + // Set loading state to false this.tasksLoading = false; } } @@ -614,7 +617,10 @@ export class TimeTrackingComponent return; } try { - const people = await firstValueFrom(this._employeesService.getEmployeeById(employee.id, ['user'])); + const people: IEmployee = await firstValueFrom( + this._employeesService.getEmployeeById(employee.id, ['user']) + ); + this._store.selectedEmployee = employee.id ? ({ id: people.id, diff --git a/apps/gauzy/src/app/pages/employees/edit-employee/edit-employee-profile/edit-employee-profile.component.ts b/apps/gauzy/src/app/pages/employees/edit-employee/edit-employee-profile/edit-employee-profile.component.ts index ab700e9fb94..075f101e99b 100644 --- a/apps/gauzy/src/app/pages/employees/edit-employee/edit-employee-profile/edit-employee-profile.component.ts +++ b/apps/gauzy/src/app/pages/employees/edit-employee/edit-employee-profile/edit-employee-profile.component.ts @@ -222,6 +222,9 @@ export class EditEmployeeProfileComponent extends TranslationBaseComponent imple private async _getEmployeeProfile() { try { const { id } = this.routeParams; + if (!id) { + return; + } // Fetch employee data from the service const employee = await firstValueFrom( @@ -240,12 +243,11 @@ export class EditEmployeeProfileComponent extends TranslationBaseComponent imple this.employeeStore.selectedEmployee = this.selectedEmployee = employee; // Set the employee name for display - this.employeeName = employee?.user?.name || employee?.user?.username || 'Employee'; + this.employeeName = employee?.user?.name || employee?.user?.username || 'Unknown Employee'; } catch (error) { // Handle errors gracefully console.error('Error fetching employee profile:', error); // Optionally, navigate to a fallback route or show an error message - // this.router.navigate(['/error']); } } diff --git a/apps/gauzy/src/app/pages/employees/edit-employee/edit-employee.resolver.ts b/apps/gauzy/src/app/pages/employees/edit-employee/edit-employee.resolver.ts index ecd5dc696b8..6b94569b8f3 100644 --- a/apps/gauzy/src/app/pages/employees/edit-employee/edit-employee.resolver.ts +++ b/apps/gauzy/src/app/pages/employees/edit-employee/edit-employee.resolver.ts @@ -21,6 +21,10 @@ export class EditEmployeeResolver implements Resolve> { const employeeId = route.params.id; // Extract employee ID from route parameters const relations = ['user', 'user.image', 'organizationPosition']; // Define relations to include in the query + if (!employeeId) { + of(null); + } + // Call the employeeService to fetch employee data by ID with specified relations return this.employeeService.getEmployeeById(employeeId, relations).pipe( catchError((error) => { diff --git a/apps/gauzy/src/app/pages/employees/employees.component.html b/apps/gauzy/src/app/pages/employees/employees.component.html index 443d24a9213..abb30d708b3 100644 --- a/apps/gauzy/src/app/pages/employees/employees.component.html +++ b/apps/gauzy/src/app/pages/employees/employees.component.html @@ -44,18 +44,28 @@

- + + + + +
+ +
+
+
- - + + + + -
+ @@ -151,6 +162,7 @@

+
+
>
+

*ngIf="!clientSecretForm.get('authorization_code').value; else clientSecretTemplate" >
- + {{ 'INTEGRATIONS.HUBSTAFF_PAGE.NAME' | translate }}

{{ 'INTEGRATIONS.HUBSTAFF_PAGE.GRANT_PERMISSION' | translate }} - @@ -58,12 +51,7 @@
{{ 'INTEGRATIONS.HUBSTAFF_PAGE.NAME' | translate }}
{{ 'INTEGRATIONS.HUBSTAFF_PAGE.ENTER_CLIENT_SECRET' | translate }} - diff --git a/apps/gauzy/src/app/pages/hubstaff/components/hubstaff/hubstaff.component.html b/apps/gauzy/src/app/pages/hubstaff/components/hubstaff/hubstaff.component.html index 6a37d90f04c..eb9917681e5 100644 --- a/apps/gauzy/src/app/pages/hubstaff/components/hubstaff/hubstaff.component.html +++ b/apps/gauzy/src/app/pages/hubstaff/components/hubstaff/hubstaff.component.html @@ -1,16 +1,30 @@ - - - - -
{{ 'INTEGRATIONS.HUBSTAFF_PAGE.NAME' | translate }}
- -
-
+ +
+
+ + {{ 'INTEGRATIONS.HUBSTAFF_PAGE.TITLE' | translate }} +
+
+
+
+ + + + +
+
diff --git a/apps/gauzy/src/app/pages/hubstaff/components/hubstaff/hubstaff.component.ts b/apps/gauzy/src/app/pages/hubstaff/components/hubstaff/hubstaff.component.ts index 930b19b0904..ffc66a09197 100644 --- a/apps/gauzy/src/app/pages/hubstaff/components/hubstaff/hubstaff.component.ts +++ b/apps/gauzy/src/app/pages/hubstaff/components/hubstaff/hubstaff.component.ts @@ -1,13 +1,13 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { TitleCasePipe } from '@angular/common'; -import { TranslateService } from '@ngx-translate/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { switchMap, tap, catchError, finalize, map } from 'rxjs/operators'; -import { ID, IHubstaffOrganization, IHubstaffProject, IOrganization } from '@gauzy/contracts'; +import { switchMap, tap, catchError, finalize } from 'rxjs/operators'; import { Observable, of, firstValueFrom } from 'rxjs'; import { filter } from 'rxjs/operators'; -import { NbDialogService, NbMenuItem, NbMenuService } from '@nebular/theme'; +import { NbDialogService } from '@nebular/theme'; +import { TranslateService } from '@ngx-translate/core'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { ID, IHubstaffOrganization, IHubstaffProject, IOrganization } from '@gauzy/contracts'; import { TranslationBaseComponent } from '@gauzy/ui-core/i18n'; import { ErrorHandlingService, HubstaffService, ToastrService } from '@gauzy/ui-core/core'; import { Store } from '@gauzy/ui-core/common'; @@ -21,16 +21,15 @@ import { SettingsDialogComponent } from '../settings-dialog/settings-dialog.comp providers: [TitleCasePipe] }) export class HubstaffComponent extends TranslationBaseComponent implements OnInit, OnDestroy { - settingsSmartTable: object; - organizations$: Observable; - projects$: Observable; - organizations: IHubstaffOrganization[] = []; - projects: IHubstaffProject[] = []; - organization: IOrganization; - selectedProjects: IHubstaffProject[] = []; - loading: boolean; - integrationId: ID; - menus: NbMenuItem[] = []; + public settingsSmartTable: object; + public organizations$: Observable; + public organizations: IHubstaffOrganization[] = []; + public projects$: Observable; + public projects: IHubstaffProject[] = []; + public organization: IOrganization; + public selectedProjects: IHubstaffProject[] = []; + public loading: boolean; + public integrationId: ID; constructor( private readonly _router: Router, @@ -41,15 +40,13 @@ export class HubstaffComponent extends TranslationBaseComponent implements OnIni private readonly _toastrService: ToastrService, private readonly _dialogService: NbDialogService, private readonly _store: Store, - private readonly _titlecasePipe: TitleCasePipe, - private readonly _nbMenuService: NbMenuService + private readonly _titlecasePipe: TitleCasePipe ) { super(_translateService); } ngOnInit() { this._loadSettingsSmartTable(); - this._loadMenus(); this._applyTranslationOnSmartTable(); this._setTokenAndLoadOrganizations(); @@ -60,28 +57,18 @@ export class HubstaffComponent extends TranslationBaseComponent implements OnIni untilDestroyed(this) ) .subscribe(); - this._nbMenuService - .onItemClick() - .pipe( - map(({ item: { icon } }) => icon), - untilDestroyed(this) - ) - .subscribe((icon) => { - if (icon === 'settings-2-outline') { - this.setSettings(); - } - }); } ngOnDestroy(): void {} /** - * + * Fetches and sets the Hubstaff integration data from the ActivatedRoute data. */ private _setTokenAndLoadOrganizations() { this.integrationId = this._activatedRoute.snapshot.params.id; this._hubstaffService.getIntegration(this.integrationId).pipe(untilDestroyed(this)).subscribe(); + // Fetch organizations for the given integration this.organizations$ = this._hubstaffService.getToken(this.integrationId).pipe( tap(() => (this.loading = true)), switchMap(() => this._hubstaffService.getOrganizations(this.integrationId)), @@ -95,7 +82,7 @@ export class HubstaffComponent extends TranslationBaseComponent implements OnIni } /** - * + * Load Smart Table settings to configure the component. */ private _loadSettingsSmartTable() { this.settingsSmartTable = { @@ -136,14 +123,17 @@ export class HubstaffComponent extends TranslationBaseComponent implements OnIni } /** - * + * Fetches projects for a given organization. * @param organization * @returns */ private _fetchProjects(organization) { this.loading = true; + // Fetch projects for the given organization return this._hubstaffService.getProjects(organization.id, this.integrationId).pipe( + // Update component state with fetched projects tap((projects) => (this.projects = projects)), + // Handle errors catchError((error) => { this._errorHandlingService.handleError(error); return of([]); @@ -153,7 +143,7 @@ export class HubstaffComponent extends TranslationBaseComponent implements OnIni } /** - * + * Select a Hubstaff project. * @param param0 */ selectProject({ selected }) { @@ -161,7 +151,7 @@ export class HubstaffComponent extends TranslationBaseComponent implements OnIni } /** - * + * Initiates a manual synchronization process for Hubstaff projects. * @returns */ syncProjects() { @@ -188,7 +178,7 @@ export class HubstaffComponent extends TranslationBaseComponent implements OnIni } /** - * + * Initiates a manual synchronization process for Hubstaff projects. * @returns */ autoSync() { @@ -204,7 +194,7 @@ export class HubstaffComponent extends TranslationBaseComponent implements OnIni organizationId }) .pipe( - tap((res) => { + tap((es) => { this._toastrService.success( this.getTranslation('INTEGRATIONS.HUBSTAFF_PAGE.SYNCED_ENTITIES'), this.getTranslation('TOASTR.TITLE.SUCCESS') @@ -221,22 +211,21 @@ export class HubstaffComponent extends TranslationBaseComponent implements OnIni } /** - * + * Opens a modal popover for integration settings if the 'integration' object is defined. * @returns */ - async setSettings() { - const dialog = this._dialogService.open(SettingsDialogComponent, { - context: {} - }); - + async openSettingModal() { + const dialog = this._dialogService.open(SettingsDialogComponent); const data = await firstValueFrom(dialog.onClose); + if (!data) { this._hubstaffService.resetSettings(); return; } - this._hubstaffService - .updateSettings(this.integrationId) + // Update integration settings + const settings$ = this._hubstaffService.updateSettings(this.integrationId); + settings$ .pipe( tap(() => { this._toastrService.success( @@ -244,37 +233,23 @@ export class HubstaffComponent extends TranslationBaseComponent implements OnIni this.getTranslation('TOASTR.TITLE.SUCCESS') ); }), + catchError((error) => { + this._errorHandlingService.handleError(error); + return of(null); + }), untilDestroyed(this) ) .subscribe(); } /** - * - */ - private _loadMenus() { - this.menus = [ - { - title: this.getTranslation('INTEGRATIONS.RE_INTEGRATE'), - icon: 'text-outline', - link: `pages/integrations/hubstaff/regenerate` - }, - { - title: this.getTranslation('INTEGRATIONS.SETTINGS'), - icon: 'settings-2-outline' - } - ]; - } - - /** - * + * Apply translations to a Smart Table component when the language changes. */ private _applyTranslationOnSmartTable() { this.translateService.onLangChange .pipe( tap(() => { this._loadSettingsSmartTable(); - this._loadMenus(); }), untilDestroyed(this) ) @@ -287,4 +262,11 @@ export class HubstaffComponent extends TranslationBaseComponent implements OnIni navigateToIntegrations(): void { this._router.navigate(['/pages/integrations']); } + + /** + * Navigates to the 'Reset Integration' route within the Hubstaff integration setup wizard. + */ + navigateToResetIntegration(): void { + this._router.navigate(['/pages/integrations/hubstaff/regenerate']); + } } diff --git a/apps/gauzy/src/app/pages/hubstaff/components/settings-dialog/settings-dialog.component.html b/apps/gauzy/src/app/pages/hubstaff/components/settings-dialog/settings-dialog.component.html index 1a6abd8d15b..a935df6235c 100644 --- a/apps/gauzy/src/app/pages/hubstaff/components/settings-dialog/settings-dialog.component.html +++ b/apps/gauzy/src/app/pages/hubstaff/components/settings-dialog/settings-dialog.component.html @@ -3,37 +3,21 @@ {{ 'INTEGRATIONS.SETTINGS' | translate }} -
+
- + {{ entity.entity }}
- +
@@ -42,10 +26,7 @@ @@ -66,20 +47,16 @@
- diff --git a/apps/gauzy/src/app/pages/hubstaff/components/settings-dialog/settings-dialog.component.ts b/apps/gauzy/src/app/pages/hubstaff/components/settings-dialog/settings-dialog.component.ts index 6e06053107c..d5a87010f58 100644 --- a/apps/gauzy/src/app/pages/hubstaff/components/settings-dialog/settings-dialog.component.ts +++ b/apps/gauzy/src/app/pages/hubstaff/components/settings-dialog/settings-dialog.component.ts @@ -19,24 +19,24 @@ export class SettingsDialogComponent implements OnInit, AfterViewInit { minDate: Date = new Date(moment().subtract(6, 'months').format('YYYY-MM-DD')); defaultRange$: Observable; - dispayDate: any; + displayDate: string; IntegrationEntity = IntegrationEntity; constructor( - private readonly _hubstaffService: HubstaffService, public readonly dialogRef: NbDialogRef, - private readonly cdRef: ChangeDetectorRef + private readonly cdRef: ChangeDetectorRef, + private readonly _hubstaffService: HubstaffService ) {} ngOnInit() { this.defaultRange$ = this._hubstaffService.dateRangeActivity$.pipe( - tap(() => (this.expandOptions = false)), - tap( - (displayDate) => - (this.dispayDate = `${moment(displayDate.start).format('MMM D, YYYY')} - ${moment( - displayDate.end - ).format('MMM D, YYYY')}`) - ) + tap(() => { + this.expandOptions = false; + }), + tap((displayDate: IDateRangeActivityFilter) => { + const { start, end } = displayDate; + this.displayDate = `${moment(start).format('MMM D, YYYY')} - ${moment(end).format('MMM D, YYYY')}`; + }) ); } @@ -44,9 +44,11 @@ export class SettingsDialogComponent implements OnInit, AfterViewInit { this.cdRef.detectChanges(); } - getDateDisplay() {} - - onDateChange(dateRange) { + /** + * Set activity date range + * @param dateRange + */ + onDateChange(dateRange: IDateRangeActivityFilter) { this._hubstaffService.setActivityDateRange(dateRange); } } diff --git a/apps/gauzy/src/app/pages/hubstaff/hubstaff.module.ts b/apps/gauzy/src/app/pages/hubstaff/hubstaff.module.ts index d9eb53974b5..ef78b64f322 100644 --- a/apps/gauzy/src/app/pages/hubstaff/hubstaff.module.ts +++ b/apps/gauzy/src/app/pages/hubstaff/hubstaff.module.ts @@ -1,6 +1,5 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { HubstaffAuthorizeComponent } from './components/hubstaff-authorize/hubstaff-authorize.component'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { NbCardModule, @@ -17,13 +16,15 @@ import { NbActionsModule, NbContextMenuModule } from '@nebular/theme'; -import { HubstaffRoutingModule } from './hubstaff-routing.module'; -import { HubstaffComponent } from './components/hubstaff/hubstaff.component'; import { Angular2SmartTableModule } from 'angular2-smart-table'; +import { NgxPermissionsModule } from 'ngx-permissions'; import { NgSelectModule } from '@ng-select/ng-select'; -import { SettingsDialogComponent } from './components/settings-dialog/settings-dialog.component'; import { I18nTranslateModule } from '@gauzy/ui-core/i18n'; import { SharedModule } from '@gauzy/ui-core/shared'; +import { HubstaffRoutingModule } from './hubstaff-routing.module'; +import { HubstaffComponent } from './components/hubstaff/hubstaff.component'; +import { HubstaffAuthorizeComponent } from './components/hubstaff-authorize/hubstaff-authorize.component'; +import { SettingsDialogComponent } from './components/settings-dialog/settings-dialog.component'; @NgModule({ declarations: [HubstaffAuthorizeComponent, HubstaffComponent, SettingsDialogComponent], @@ -45,6 +46,7 @@ import { SharedModule } from '@gauzy/ui-core/shared'; NbToggleModule, NbActionsModule, NbContextMenuModule, + NgxPermissionsModule.forChild(), SharedModule, NbDatepickerModule, NbDialogModule.forChild(), diff --git a/apps/gauzy/src/app/pages/integrations/components/integration-list/list.component.html b/apps/gauzy/src/app/pages/integrations/components/integration-list/list.component.html index 29587319b5a..5ac9a3c99c1 100644 --- a/apps/gauzy/src/app/pages/integrations/components/integration-list/list.component.html +++ b/apps/gauzy/src/app/pages/integrations/components/integration-list/list.component.html @@ -1,9 +1,4 @@ - +

@@ -18,108 +13,128 @@

[hasLayoutSelector]="false" >

-
-
- {{ 'SM_TABLE.NAME' | translate }} -
-
- {{ 'SM_TABLE.LAST_SYNC_DATE' | translate }} -
-
- {{ 'SM_TABLE.ENABLE_DISABLE_INTEGRATION' | translate }} -
-
- {{ 'SM_TABLE.STATUS' | translate }} -
-
+ + +
+
+ {{ 'SM_TABLE.NAME' | translate }} +
+
+ {{ 'SM_TABLE.LAST_SYNC_DATE' | translate }} +
+
+ {{ 'SM_TABLE.ENABLE_DISABLE_INTEGRATION' | translate }} +
+
+ {{ 'SM_TABLE.STATUS' | translate }} +
+
+
+
- - - - - -
- -
-
-
- {{ integration?.name | replace : '_' : ' ' }} -
-
- {{ getProviderDescription(integration?.integration) }} -
-
-
- {{ (integration?.lastSyncedAt || integration?.updatedAt) | dateTimeFormat }} -
-
- -
-
-
- {{ (integration?.isActive ? 'INTEGRATIONS.ENABLED' : 'INTEGRATIONS.DISABLED') | translate }} -
-
-
- - - - -
-
-
+ + + + + + + +
+ +
+
+
+ {{ integration?.name | replace : '_' : ' ' }} +
+
+ {{ getProviderDescription(integration?.integration) }} +
+
+
+ {{ integration?.lastSyncedAt || integration?.updatedAt | dateTimeFormat }} +
+
+ +
+
+
+ {{ + (integration?.isActive + ? 'INTEGRATIONS.ENABLED' + : 'INTEGRATIONS.DISABLED' + ) | translate + }} +
+
+
+ + + + + + +
+
+
+
+
+ +
+ +
+
- -
- -
-
-
+
+ +
+ +
+
+ + - - diff --git a/apps/gauzy/src/app/pages/integrations/gauzy-ai/components/integration-setting-card/integration-setting-card.component.html b/apps/gauzy/src/app/pages/integrations/gauzy-ai/components/integration-setting-card/integration-setting-card.component.html index be15d6e7d88..0fce4a5fc43 100644 --- a/apps/gauzy/src/app/pages/integrations/gauzy-ai/components/integration-setting-card/integration-setting-card.component.html +++ b/apps/gauzy/src/app/pages/integrations/gauzy-ai/components/integration-setting-card/integration-setting-card.component.html @@ -1,90 +1,98 @@ - - -
-
-
-
-
{{ title }}
-
-
- - - - - - - -
-
-
-
-
- - -
-
-
-
-
+ + +
+
+
+
+
{{ title }}
+
+
+ + + + + + + + + +
+
+
+
+
+ + +
+
+
+
+
-
-
-
- {{ getTitleForSetting(setting) }} - + +
+
+
+ {{ getTitleForSetting(setting) }} + +
+
+ {{ 'INTEGRATIONS.GAUZY_AI_PAGE.GENERATED' | translate }} {{ setting.createdAt | dateFormat }} +
-
- {{ 'INTEGRATIONS.GAUZY_AI_PAGE.GENERATED' | translate }} {{ setting.createdAt | dateFormat }} +
+ +
+ +
+
+ + {{ setting.settingsValue }} +
-
- -
- -
-
- - {{ setting.settingsValue }} - -
-
+
diff --git a/apps/gauzy/src/app/pages/integrations/gauzy-ai/components/view/view.component.html b/apps/gauzy/src/app/pages/integrations/gauzy-ai/components/view/view.component.html index 7f73b34a409..d0f76192eae 100644 --- a/apps/gauzy/src/app/pages/integrations/gauzy-ai/components/view/view.component.html +++ b/apps/gauzy/src/app/pages/integrations/gauzy-ai/components/view/view.component.html @@ -12,35 +12,34 @@
- + + +
- - + + - + > - + - -
-
-
-
-
- - - {{ 'FORM.PLACEHOLDERS.ENABLE_JOBS_SEARCH_MATCHING_ANALYSIS' | translate }} - - + {{ + 'FORM.PLACEHOLDERS.ENABLE_JOBS_SEARCH_MATCHING_ANALYSIS' + | translate + }} + + +
-
-
-
-
- - - {{ 'FORM.PLACEHOLDERS.ENABLE_EMPLOYEE_PERFORMANCE_ANALYSIS' | translate }} - - + {{ + 'FORM.PLACEHOLDERS.ENABLE_EMPLOYEE_PERFORMANCE_ANALYSIS' + | translate + }} + + +
-
- - + + +
diff --git a/apps/gauzy/src/app/pages/integrations/gauzy-ai/gauzy-ai-routing.module.ts b/apps/gauzy/src/app/pages/integrations/gauzy-ai/gauzy-ai-routing.module.ts index 1c76bfca183..06c11655c3b 100644 --- a/apps/gauzy/src/app/pages/integrations/gauzy-ai/gauzy-ai-routing.module.ts +++ b/apps/gauzy/src/app/pages/integrations/gauzy-ai/gauzy-ai-routing.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { IntegrationEnum } from '@gauzy/contracts'; -import { IntegrationResolver } from '../integration.resolver'; +import { IntegrationEnum, PermissionsEnum } from '@gauzy/contracts'; +import { IntegrationResolver, PermissionsGuard } from '@gauzy/ui-core/core'; import { GauzyAIAuthorizationComponent } from './components/authorization/authorization.component'; import { GauzyAILayoutComponent } from './gauzy-ai.layout.component'; import { GauzyAIViewComponent } from './components/view/view.component'; @@ -24,7 +24,12 @@ const routes: Routes = [ { path: '', // Child route for the default page component: GauzyAIAuthorizationComponent, // Component for the default page + canActivate: [PermissionsGuard], data: { + permissions: { + only: [PermissionsEnum.INTEGRATION_ADD], + redirectTo: '/pages/dashboard' + }, integration: IntegrationEnum.GAUZY_AI // Custom data associated with this route }, resolve: { @@ -34,7 +39,12 @@ const routes: Routes = [ { path: 'reset', // Separate route for the reset page component: GauzyAIAuthorizationComponent, // Create a new component for the reset page + canActivate: [PermissionsGuard], data: { + permissions: { + only: [PermissionsEnum.INTEGRATION_EDIT], + redirectTo: '/pages/dashboard' + }, state: false, selectors: { project: false, diff --git a/apps/gauzy/src/app/pages/integrations/gauzy-ai/gauzy-ai.module.ts b/apps/gauzy/src/app/pages/integrations/gauzy-ai/gauzy-ai.module.ts index 8f3ae523efa..0e1fd099b8d 100644 --- a/apps/gauzy/src/app/pages/integrations/gauzy-ai/gauzy-ai.module.ts +++ b/apps/gauzy/src/app/pages/integrations/gauzy-ai/gauzy-ai.module.ts @@ -10,6 +10,7 @@ import { NbToggleModule, NbTooltipModule } from '@nebular/theme'; +import { NgxPermissionsModule } from 'ngx-permissions'; import { I18nTranslateModule } from '@gauzy/ui-core/i18n'; import { SharedModule } from '@gauzy/ui-core/shared'; import { WorkInProgressModule } from '../../work-in-progress/work-in-progress.module'; @@ -38,8 +39,9 @@ import { IntegrationSettingCardComponent } from './components/integration-settin NbToggleModule, NbToggleModule, NbTooltipModule, - GauzyAIRoutingModule, + NgxPermissionsModule.forChild(), I18nTranslateModule.forChild(), + GauzyAIRoutingModule, WorkInProgressModule, SharedModule ] diff --git a/apps/gauzy/src/app/pages/integrations/github/components/view/view.component.html b/apps/gauzy/src/app/pages/integrations/github/components/view/view.component.html index 43531fa8888..7b3f71e6b3d 100644 --- a/apps/gauzy/src/app/pages/integrations/github/components/view/view.component.html +++ b/apps/gauzy/src/app/pages/integrations/github/components/view/view.component.html @@ -12,12 +12,14 @@
- + + + +
+
+ +
-
-
-
- - - image not found - -
- - {{ 'INTEGRATIONS.COMING_SOON' | translate }} - -
-
-
-
+ + + + +
+
diff --git a/apps/gauzy/src/app/pages/integrations/integrations.module.ts b/apps/gauzy/src/app/pages/integrations/integrations.module.ts index c286d2991de..4a92c340764 100644 --- a/apps/gauzy/src/app/pages/integrations/integrations.module.ts +++ b/apps/gauzy/src/app/pages/integrations/integrations.module.ts @@ -33,9 +33,9 @@ import { IntegrationListComponent } from './components/integration-list/list.com NbTooltipModule, IntegrationsRoutingModule, SharedModule, - I18nTranslateModule.forChild(), NbIconModule, NgxPermissionsModule.forChild(), + I18nTranslateModule.forChild(), GauzyButtonActionModule, TableComponentsModule, NoDataMessageModule diff --git a/apps/gauzy/src/app/pages/integrations/layout/layout.component.ts b/apps/gauzy/src/app/pages/integrations/layout/layout.component.ts index 884a2cf4248..dfe2eedf4a6 100644 --- a/apps/gauzy/src/app/pages/integrations/layout/layout.component.ts +++ b/apps/gauzy/src/app/pages/integrations/layout/layout.component.ts @@ -7,8 +7,7 @@ import { Component, OnInit } from '@angular/core'; providers: [] }) export class IntegrationLayoutComponent implements OnInit { + constructor() {} - constructor() { } - - ngOnInit(): void { } + ngOnInit(): void {} } diff --git a/apps/gauzy/src/app/pages/jobs/employees/employees/employees.component.html b/apps/gauzy/src/app/pages/jobs/employees/employees/employees.component.html index 1ab4ae2bb7f..aef44b99e69 100644 --- a/apps/gauzy/src/app/pages/jobs/employees/employees/employees.component.html +++ b/apps/gauzy/src/app/pages/jobs/employees/employees/employees.component.html @@ -31,7 +31,7 @@

- +
+ +
+ +
+
+
diff --git a/apps/gauzy/src/app/pages/jobs/employees/employees/employees.component.ts b/apps/gauzy/src/app/pages/jobs/employees/employees/employees.component.ts index a3a34e4596e..b9c9ccc8b11 100644 --- a/apps/gauzy/src/app/pages/jobs/employees/employees/employees.component.ts +++ b/apps/gauzy/src/app/pages/jobs/employees/employees/employees.component.ts @@ -8,7 +8,7 @@ import { NbTabComponent } from '@nebular/theme'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { TranslateService } from '@ngx-translate/core'; import { Cell } from 'angular2-smart-table'; -import { IEmployee, IEmployeeJobsStatisticsResponse, IOrganization, ISelectedEmployee } from '@gauzy/contracts'; +import { ID, IEmployee, IEmployeeJobsStatisticsResponse, IOrganization, PermissionsEnum } from '@gauzy/contracts'; import { EmployeesService, JobService, ServerDataSource, ToastrService } from '@gauzy/ui-core/core'; import { API_PREFIX, Store, distinctUntilChange } from '@gauzy/ui-core/common'; import { @@ -37,15 +37,15 @@ export class EmployeesComponent extends PaginationFilterBaseComponent implements public loading: boolean = false; public settingsSmartTable: any; public employees$: Subject = new Subject(); + public nbTab$: Subject = new BehaviorSubject(JobSearchTabsEnum.BROWSE); public smartTableSource: ServerDataSource; - public selectedEmployeeId: ISelectedEmployee['id']; public organization: IOrganization; - public nbTab$: Subject = new BehaviorSubject(JobSearchTabsEnum.BROWSE); + public selectedEmployeeId: ID; public selectedEmployee: IEmployee; public disableButton: boolean = true; constructor( - public readonly translateService: TranslateService, + translateService: TranslateService, private readonly _http: HttpClient, private readonly _router: Router, private readonly _store: Store, @@ -124,33 +124,48 @@ export class EmployeesComponent extends PaginationFilterBaseComponent implements // Set loading state to true while fetching data this.loading = true; - // Destructure properties for clarity - const { tenantId } = this._store.user; - const { id: organizationId } = this.organization; + try { + // Destructure properties for clarity + const { id: organizationId, tenantId } = this.organization; - // Create a new ServerDataSource for Smart Table - this.smartTableSource = new ServerDataSource(this._http, { - endPoint: `${API_PREFIX}/employee-job/statistics`, - relations: ['user'], - // Define query parameters for the API request - where: { + const whereClause = { tenantId, organizationId, isActive: true, + isArchived: false, ...(this.selectedEmployeeId ? { id: this.selectedEmployeeId } : {}), ...(this.filters.where ? this.filters.where : {}) - }, - // Finalize callback to handle post-processing - finalize: () => { - // Update pagination based on the count of items in the source - this.setPagination({ - ...this.getPagination(), - totalItems: this.smartTableSource.count() - }); - // Set loading state to false once data fetching is complete - this.loading = false; + }; + + // Filter by current employee ID if the permission is not present + if (!this._store.hasPermission(PermissionsEnum.CHANGE_SELECTED_EMPLOYEE)) { + // Filter by current employee ID if the permission is not present + const employeeId = this._store.user?.employee?.id; + whereClause.id = employeeId; } - }); + + // Create a new ServerDataSource for Smart Table + this.smartTableSource = new ServerDataSource(this._http, { + endPoint: `${API_PREFIX}/employee-job/statistics`, + relations: ['user'], + // Define query parameters for the API request + where: { ...whereClause }, + // Finalize callback to handle post-processing + finalize: () => { + // Update pagination based on the count of items in the source + this.setPagination({ + ...this.getPagination(), + totalItems: this.smartTableSource.count() + }); + } + }); + } catch (error) { + // Display an error toastr notification in case of any exceptions. + this._toastrService.danger(error); + } finally { + // Set loading state to false once data fetching is complete + this.loading = false; + } } /** @@ -179,6 +194,9 @@ export class EmployeesComponent extends PaginationFilterBaseComponent implements } } + /** + * Loads the Smart Table settings. + */ private _loadSmartTableSettings(): void { // Retrieve pagination settings const pagination: IPaginationBase = this.getPagination(); @@ -188,9 +206,10 @@ export class EmployeesComponent extends PaginationFilterBaseComponent implements selectedRowIndex: -1, hideSubHeader: true, noDataMessage: this.getTranslation('SM_TABLE.NO_DATA.EMPLOYEE'), - editable: true, + isEditable: true, actions: { - delete: false + delete: false, + add: true }, pager: { display: false, @@ -207,23 +226,10 @@ export class EmployeesComponent extends PaginationFilterBaseComponent implements title: this.getTranslation('JOB_EMPLOYEE.EMPLOYEE'), width: '30%', type: 'custom', - sort: false, - editable: false, + isSortable: false, + isEditable: false, renderComponent: EmployeeLinksComponent, - valuePrepareFunction: ( - _: any, - cell: Cell - ): { name: string | null; imageUrl: string | null; id: string | null } => { - const employee: IEmployee | undefined = cell.getRow().getData(); - if (employee) { - const { user, id } = employee; - const name = user?.name || null; - const imageUrl = user?.imageUrl || null; - - return { name, imageUrl, id }; - } - return { name: null, imageUrl: null, id: null }; - }, + valuePrepareFunction: (_: any, cell: Cell) => this.prepareEmployeeValue(_, cell), componentInitFunction: (instance: EmployeeLinksComponent, cell: Cell) => { instance.rowData = cell.getRow().getData(); instance.value = cell.getValue(); @@ -233,24 +239,24 @@ export class EmployeesComponent extends PaginationFilterBaseComponent implements title: this.getTranslation('JOB_EMPLOYEE.AVAILABLE_JOBS'), type: 'text', width: '10%', - sort: false, - editable: false, + isSortable: false, + isEditable: false, valuePrepareFunction: (value: IEmployeeJobsStatisticsResponse['availableJobs']) => value || 0 }, appliedJobs: { title: this.getTranslation('JOB_EMPLOYEE.APPLIED_JOBS'), type: 'text', width: '10%', - sort: false, - editable: false, + isSortable: false, + isEditable: false, valuePrepareFunction: (value: IEmployeeJobsStatisticsResponse['appliedJobs']) => value || 0 }, billRateValue: { title: this.getTranslation('JOB_EMPLOYEE.BILLING_RATE'), type: 'text', width: '10%', - sort: false, - editable: true, + isSortable: false, + isEditable: true, editor: { type: 'custom', component: NumberEditorComponent @@ -264,8 +270,8 @@ export class EmployeesComponent extends PaginationFilterBaseComponent implements title: this.getTranslation('JOB_EMPLOYEE.MINIMUM_BILLING_RATE'), type: 'text', width: '20%', - sort: false, - editable: true, + isSortable: false, + isEditable: true, editor: { type: 'custom', component: NumberEditorComponent @@ -279,23 +285,53 @@ export class EmployeesComponent extends PaginationFilterBaseComponent implements title: this.getTranslation('JOB_EMPLOYEE.JOB_SEARCH_STATUS'), type: 'custom', width: '20%', - editable: false, + isSortable: false, + isEditable: false, renderComponent: SmartTableToggleComponent, - valuePrepareFunction: (_: any, cell: Cell) => { - const employee: IEmployee = cell.getRow().getData(); - return { - checked: employee.isJobSearchActive, - onChange: (toggle: boolean) => this.updateJobSearchAvailability(employee, toggle) - }; - }, componentInitFunction: (instance: SmartTableToggleComponent, cell: Cell) => { - instance.value = cell.getValue(); + // Get the employee data from the cell + const employee: IEmployee = cell.getRow().getData(); + + // Set the initial value of the toggle + instance.value = employee.isJobSearchActive; + + // Subscribe to the toggleChange event + instance.toggleChange.pipe(untilDestroyed(this)).subscribe((toggle: boolean) => { + this.updateJobSearchAvailability(employee, toggle); + }); } } } }; } + /** + * Prepares the value for the employee cell. + * @param _ The row data. + * @param cell The cell to prepare the value for. + * @returns The prepared value. + */ + private prepareEmployeeValue( + _: any, + cell: Cell + ): { name: string | null; imageUrl: string | null; id: string | null } { + // Get the employee data from the cell + const employee: IEmployee | undefined = cell.getRow().getData(); + + // Prepare the value for the cell + if (employee) { + const { user, id } = employee; + return { + name: user?.name ?? null, + imageUrl: user?.imageUrl ?? null, + id: id ?? null + }; + } + + // Return default values if the employee is undefined + return { name: null, imageUrl: null, id: null }; + } + /** * Handles the event for confirming the edit of an editable field. * @@ -359,7 +395,7 @@ export class EmployeesComponent extends PaginationFilterBaseComponent implements ? 'TOASTR.MESSAGE.EMPLOYEE_JOB_STATUS_ACTIVE' : 'TOASTR.MESSAGE.EMPLOYEE_JOB_STATUS_INACTIVE'; - const fullName = employee.fullName.trim(); + const fullName = employee.fullName.trim() || 'Unknown Employee'; this._toastrService.success(toastrMessageKey, { name: fullName }); } catch (error) { // Display an error toastr notification in case of any exceptions. diff --git a/apps/gauzy/src/app/pages/jobs/jobs-routing.module.ts b/apps/gauzy/src/app/pages/jobs/jobs-routing.module.ts index 3cf7ec60102..efcb00facd4 100644 --- a/apps/gauzy/src/app/pages/jobs/jobs-routing.module.ts +++ b/apps/gauzy/src/app/pages/jobs/jobs-routing.module.ts @@ -19,7 +19,8 @@ const routes: Routes = [ selectors: { date: true, employee: true, - project: false + project: false, + team: false } } }, @@ -30,7 +31,8 @@ const routes: Routes = [ selectors: { date: true, employee: true, - project: false + project: false, + team: false } } }, @@ -46,7 +48,8 @@ const routes: Routes = [ selectors: { date: true, employee: true, - project: false + project: false, + team: false } } } diff --git a/apps/gauzy/src/app/pages/jobs/proposal-template/proposal-template/proposal-template.component.html b/apps/gauzy/src/app/pages/jobs/proposal-template/proposal-template/proposal-template.component.html index 593b9aa667c..e99f358c575 100644 --- a/apps/gauzy/src/app/pages/jobs/proposal-template/proposal-template/proposal-template.component.html +++ b/apps/gauzy/src/app/pages/jobs/proposal-template/proposal-template/proposal-template.component.html @@ -1,8 +1,4 @@ - +
@@ -23,18 +19,12 @@

>

- + - + @@ -42,11 +32,7 @@

- +
- + + + + + +
- @@ -132,9 +122,7 @@

- +
diff --git a/apps/gauzy/src/app/pages/jobs/proposal-template/proposal-template/proposal-template.component.ts b/apps/gauzy/src/app/pages/jobs/proposal-template/proposal-template/proposal-template.component.ts index 8270cf5ced1..ba11c3e43cf 100644 --- a/apps/gauzy/src/app/pages/jobs/proposal-template/proposal-template/proposal-template.component.ts +++ b/apps/gauzy/src/app/pages/jobs/proposal-template/proposal-template/proposal-template.component.ts @@ -10,6 +10,7 @@ import { Cell } from 'angular2-smart-table'; import { IEmployee, IEmployeeProposalTemplate, + IEmployeeProposalTemplateMakeDefaultInput, IOrganization, ISelectedEmployee, PermissionsEnum @@ -44,7 +45,6 @@ export class ProposalTemplateComponent extends PaginationFilterBaseComponent imp public smartTableSource: ServerDataSource; public selectedEmployee: ISelectedEmployee; public selectedItem: any; - public isDefault: boolean = false; public proposalTemplateTabsEnum = ProposalTemplateTabsEnum; public templates$: Subject = new Subject(); public organization: IOrganization; @@ -241,9 +241,6 @@ export class ProposalTemplateComponent extends PaginationFilterBaseComponent imp // Update the selectedItem property based on the isSelected value this.selectedItem = isSelected ? data : null; - - // Update the isDefault property based on the isDefault value of the selectedItem - this.isDefault = this.selectedItem?.isDefault; } /** @@ -324,9 +321,7 @@ export class ProposalTemplateComponent extends PaginationFilterBaseComponent imp async createProposalTemplate(): Promise { // Open a dialog for adding/editing a proposal template const dialog = this.dialogService.open(AddEditProposalTemplateComponent, { - context: { - selectedEmployee: this.selectedEmployee - } + context: { selectedEmployee: this.selectedEmployee } }); // Wait for the dialog to close and get the result @@ -374,9 +369,7 @@ export class ProposalTemplateComponent extends PaginationFilterBaseComponent imp // Open the dialog for user confirmation const dialogRef = this.dialogService.open(DeleteConfirmationComponent, { - context: { - recordType: 'Proposal' - } + context: { recordType: 'Proposal' } }); // Wait for the dialog to close and get the result @@ -411,34 +404,28 @@ export class ProposalTemplateComponent extends PaginationFilterBaseComponent imp /** * Updates the default status of the selected proposal template. */ - async makeDefaultTemplate(selectedItem?: IEmployeeProposalTemplate): Promise { - // If a proposal template item is selected, mark it as selected - if (selectedItem) { - this.selectProposalTemplate({ - isSelected: true, - data: selectedItem - }); - } - + async makeDefaultTemplate(input: IEmployeeProposalTemplateMakeDefaultInput): Promise { try { if (!this.selectedItem) { return; } - const { id: proposalTemplateId } = this.selectedItem; + const { id: proposalTemplateId, organizationId, tenantId } = this.selectedItem; // Call the makeDefault method of the proposalTemplateService to update the default status - const data = await this.proposalTemplateService.makeDefault(proposalTemplateId); + const result = await this.proposalTemplateService.makeDefault(proposalTemplateId, { + isDefault: input.isDefault, + organizationId, + tenantId + }); // Determine the success message based on whether the template is set as default or not - const successMessage = data.isDefault + const successMessage = result.isDefault ? 'PROPOSAL_TEMPLATE.PROPOSAL_MAKE_DEFAULT_MESSAGE' : 'PROPOSAL_TEMPLATE.PROPOSAL_REMOVE_DEFAULT_MESSAGE'; // Display a success message using the toastrService - this.toastrService.success(successMessage, { - name: this.selectedItem.name - }); + this.toastrService.success(successMessage, { name: this.selectedItem.name }); } catch (error) { // Handle errors during the process this.errorHandler.handleError(error); diff --git a/apps/gauzy/src/app/pages/jobs/search/search-routing.module.ts b/apps/gauzy/src/app/pages/jobs/search/search-routing.module.ts index ceb82843bf7..e1bcbff248d 100644 --- a/apps/gauzy/src/app/pages/jobs/search/search-routing.module.ts +++ b/apps/gauzy/src/app/pages/jobs/search/search-routing.module.ts @@ -1,20 +1,25 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import { IntegrationEnum } from '@gauzy/contracts'; +import { IntegrationEnum, PermissionsEnum } from '@gauzy/contracts'; +import { IntegrationResolver, PermissionsGuard } from '@gauzy/ui-core/core'; import { SearchComponent } from './search/search.component'; -import { IntegrationResolver } from '../../integrations/integration.resolver'; const routes: Routes = [ { path: '', component: SearchComponent, + canActivate: [PermissionsGuard], data: { + permissions: { + only: [PermissionsEnum.ORG_JOB_SEARCH], + redirectTo: '/pages/jobs/search' + }, integration: IntegrationEnum.GAUZY_AI, // Custom data associated with this route relations: ['integration', 'entitySettings'] }, resolve: { - integration: IntegrationResolver, // Resolver to fetch data before activating the route - }, + integration: IntegrationResolver // Resolver to fetch data before activating the route + } } ]; @@ -22,4 +27,4 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) -export class SearchRoutingModule { } +export class SearchRoutingModule {} diff --git a/apps/gauzy/src/app/pages/jobs/search/search/search.component.html b/apps/gauzy/src/app/pages/jobs/search/search/search.component.html index 4edcac80166..776b0e68239 100644 --- a/apps/gauzy/src/app/pages/jobs/search/search/search.component.html +++ b/apps/gauzy/src/app/pages/jobs/search/search/search.component.html @@ -223,80 +223,98 @@

+ + -
- - - -
-
- - - -
+ +
+ + + +
+
+ + + +
+
+ +
+ +
+
+ +
- - - - - + + + + + + + + + + +
+ - +