From 85b2ad2743d2ea75a570a93754769dc4046e7540 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Fri, 30 Aug 2024 16:48:33 +0530 Subject: [PATCH 1/3] feat: include code to the theme language dropdown --- .../theme-language-selector.component.html | 9 ++-- .../theme-settings.component.html | 49 ++++++++----------- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/components/theme-language-selector/theme-language-selector.component.html b/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/components/theme-language-selector/theme-language-selector.component.html index f96b28db758..1e52051896b 100644 --- a/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/components/theme-language-selector/theme-language-selector.component.html +++ b/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/components/theme-language-selector/theme-language-selector.component.html @@ -1,6 +1,6 @@
{{ 'SETTINGS_MENU.LANGUAGE' | translate }} -
+
- - {{ lang.name | translate }} + + {{ lang.value | uppercase }} ({{ lang.name | translate }}) diff --git a/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/theme-settings.component.html b/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/theme-settings.component.html index fa18e7d672a..059167e0216 100644 --- a/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/theme-settings.component.html +++ b/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/theme-settings.component.html @@ -1,31 +1,22 @@
-
- - - -

{{ 'SETTINGS_MENU.QUICK_SETTINGS' | translate }}

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

{{ 'SETTINGS_MENU.QUICK_SETTINGS' | translate }}

+
+ + + + + + + + + + + + + +
From e3ecc474f0ce8503cda9ec67b9aabd3015079163 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Fri, 30 Aug 2024 16:51:03 +0530 Subject: [PATCH 2/3] feat: static tabs load template or component dynamically --- .../pages/dashboard/dashboard.component.ts | 20 ++--- .../job-employee/job-employee.component.html | 45 +++++----- .../job-employee/job-employee.component.ts | 87 +++++++++++++++---- .../src/lib/job-employee.module.ts | 5 +- .../src/lib/job-employee.routes.ts | 3 + .../page/page-tab-registry.service.ts | 22 +++-- .../services/page/page-tab-registry.types.ts | 44 ++++++---- .../dynamic-tabs/dynamic-tabs.component.html | 26 ++++-- .../dynamic-tabs/dynamic-tabs.component.ts | 69 +++++++++++---- 9 files changed, 221 insertions(+), 100 deletions(-) diff --git a/apps/gauzy/src/app/pages/dashboard/dashboard.component.ts b/apps/gauzy/src/app/pages/dashboard/dashboard.component.ts index 9f5023499ae..bad0c8bb140 100644 --- a/apps/gauzy/src/app/pages/dashboard/dashboard.component.ts +++ b/apps/gauzy/src/app/pages/dashboard/dashboard.component.ts @@ -72,11 +72,11 @@ export class DashboardComponent extends TranslationBaseComponent implements Afte if (this._store.hasAnyPermission(PermissionsEnum.ADMIN_DASHBOARD_VIEW, PermissionsEnum.TEAM_DASHBOARD)) { // Register the teams tab this._pageTabRegistryService.registerPageTab({ - tabsetId: 'dashboard', // The identifier for the tabset + tabsetId: this.tabsetId, // The identifier for the tabset tabId: 'teams', // The identifier for the tab tabsetType: 'route', // The type of tabset to use route: this.getRoute('teams'), // The route for the tab - tabTitle: () => this.getTranslation('ORGANIZATIONS_PAGE.TEAMS'), // The title for the tab + tabTitle: (_i18n) => _i18n.getTranslation('ORGANIZATIONS_PAGE.TEAMS'), // The title for the tab tabIcon: 'people-outline', // The icon for the tab responsive: true, // Whether the tab is responsive activeLinkOptions: { exact: false }, // The options for the active link @@ -93,11 +93,11 @@ export class DashboardComponent extends TranslationBaseComponent implements Afte ) { // Register the project management tab this._pageTabRegistryService.registerPageTab({ - tabsetId: 'dashboard', // The identifier for the tabset + tabsetId: this.tabsetId, // The identifier for the tabset tabId: 'project-management', // The identifier for the tab tabsetType: 'route', // The type of tabset to use route: this.getRoute('project-management'), // The route for the tab - tabTitle: () => this.getTranslation('DASHBOARD_PAGE.PROJECT_MANAGEMENT'), // The title for the tab + tabTitle: (_i18n) => _i18n.getTranslation('DASHBOARD_PAGE.PROJECT_MANAGEMENT'), // The title for the tab tabIcon: 'browser-outline', // The icon for the tab responsive: true, // Whether the tab is responsive activeLinkOptions: { exact: false }, // The options for the active link @@ -111,11 +111,11 @@ export class DashboardComponent extends TranslationBaseComponent implements Afte ) { // Register the time tracking tab this._pageTabRegistryService.registerPageTab({ - tabsetId: 'dashboard', // The identifier for the tabset + tabsetId: this.tabsetId, // The identifier for the tabset tabId: 'time-tracking', // The identifier for the tab tabsetType: 'route', // The type of tabset to use route: this.getRoute('time-tracking'), // The route for the tab - tabTitle: () => this.getTranslation('TIMESHEET.TIME_TRACKING'), // The title for the tab + tabTitle: (_i18n) => _i18n.getTranslation('TIMESHEET.TIME_TRACKING'), // The title for the tab tabIcon: 'clock-outline', // The icon for the tab responsive: true, // Whether the tab is responsive activeLinkOptions: { exact: false }, // The options for the active link @@ -138,11 +138,11 @@ export class DashboardComponent extends TranslationBaseComponent implements Afte if (!this.selectedEmployee || !this.selectedEmployee.id) { // Register the accounting tab this._pageTabRegistryService.registerPageTab({ - tabsetId: 'dashboard', // The identifier for the tabset + tabsetId: this.tabsetId, // The identifier for the tabset tabId: 'accounting', // The identifier for the tab tabsetType: 'route', // The type of tabset to use route: this.getRoute('accounting'), // The route for the tab - tabTitle: () => this.getTranslation('DASHBOARD_PAGE.ACCOUNTING'), // The title for the tab + tabTitle: (_i18n) => _i18n.getTranslation('DASHBOARD_PAGE.ACCOUNTING'), // The title for the tab tabIcon: 'credit-card-outline', // The icon for the tab responsive: true, // Whether the tab is responsive activeLinkOptions: { exact: false }, // The options for the active link @@ -161,11 +161,11 @@ export class DashboardComponent extends TranslationBaseComponent implements Afte if (this.selectedEmployee && this.selectedEmployee.id) { // Register the human resources tab this._pageTabRegistryService.registerPageTab({ - tabsetId: 'dashboard', // The identifier for the tabset + tabsetId: this.tabsetId, // The identifier for the tabset tabId: 'hr', // The identifier for the tab tabsetType: 'route', // The type of tabset to use route: this.getRoute('hr'), // The route for the tab - tabTitle: () => this.getTranslation('DASHBOARD_PAGE.HUMAN_RESOURCES'), // The title for the tab + tabTitle: (_i18n) => _i18n.getTranslation('DASHBOARD_PAGE.HUMAN_RESOURCES'), // The title for the tab tabIcon: 'person-outline', // The icon for the tab responsive: true, // Whether the tab is responsive activeLinkOptions: { exact: false }, // The options for the active link diff --git a/packages/plugins/job-employee-ui/src/lib/components/job-employee/job-employee.component.html b/packages/plugins/job-employee-ui/src/lib/components/job-employee/job-employee.component.html index 3ba920d4393..5578d94fe8c 100644 --- a/packages/plugins/job-employee-ui/src/lib/components/job-employee/job-employee.component.html +++ b/packages/plugins/job-employee-ui/src/lib/components/job-employee/job-employee.component.html @@ -15,24 +15,18 @@

[buttonTemplate]="actionButtons" >

- - - - - - - - - - - - - + + + + + +
+
+
+ +
- + +
+ + +
+
+ +
{{ 'COMING_SOON' | translate }}
+
+
+
+
@@ -107,12 +115,3 @@

- - -
-
- -
{{ 'COMING_SOON' | translate }}
-
-
-
diff --git a/packages/plugins/job-employee-ui/src/lib/components/job-employee/job-employee.component.ts b/packages/plugins/job-employee-ui/src/lib/components/job-employee/job-employee.component.ts index a36bbbe3509..a3a971dbbb6 100644 --- a/packages/plugins/job-employee-ui/src/lib/components/job-employee/job-employee.component.ts +++ b/packages/plugins/job-employee-ui/src/lib/components/job-employee/job-employee.component.ts @@ -1,7 +1,7 @@ -import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'; +import { AfterViewInit, Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { CurrencyPipe } from '@angular/common'; import { HttpClient } from '@angular/common/http'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { BehaviorSubject, combineLatest, merge, Subject } from 'rxjs'; import { debounceTime, filter, tap } from 'rxjs/operators'; import { NbTabComponent } from '@nebular/theme'; @@ -17,7 +17,9 @@ import { JobService, ServerDataSource, Store, - ToastrService + ToastrService, + PageTabsetRegistryId, + PageTabRegistryService } from '@gauzy/ui-core/core'; import { I18nService } from '@gauzy/ui-core/i18n'; import { @@ -45,6 +47,7 @@ export enum JobSearchTabsEnum { providers: [CurrencyPipe] }) export class JobEmployeeComponent extends PaginationFilterBaseComponent implements AfterViewInit, OnInit, OnDestroy { + public tabsetId: PageTabsetRegistryId = this._route.snapshot.data.tabsetId; // The identifier for the tabset public jobSearchTabsEnum = JobSearchTabsEnum; public loading: boolean = false; public settingsSmartTable: any; @@ -56,33 +59,34 @@ export class JobEmployeeComponent extends PaginationFilterBaseComponent implemen public selectedEmployee: IEmployee; public disableButton: boolean = true; + // Template References + @ViewChild('tableLayout', { static: true }) tableLayout: TemplateRef; // Template reference for the table layout tab + @ViewChild('comingSoon', { static: true }) comingSoon: TemplateRef; // Template reference for the coming soon tab + constructor( translateService: TranslateService, private readonly _http: HttpClient, + private readonly _route: ActivatedRoute, private readonly _router: Router, + private readonly _ngxPermissionsService: NgxPermissionsService, private readonly _store: Store, private readonly _employeesService: EmployeesService, private readonly _jobService: JobService, private readonly _toastrService: ToastrService, private readonly _currencyPipe: CurrencyPipe, - private readonly _ngxPermissionsService: NgxPermissionsService, private readonly _i18nService: I18nService, - readonly _pageDataTableRegistryService: PageDataTableRegistryService + readonly _pageDataTableRegistryService: PageDataTableRegistryService, + readonly _pageTabRegistryService: PageTabRegistryService ) { super(translateService); - - // Register data table columns - this.registerDataTableColumns(_pageDataTableRegistryService); } ngOnInit(): void { - this._applyTranslationOnSmartTable(); - this._loadSmartTableSettings(); - - // Initialize UI permissions - this.initializeUiPermissions(); - // Initialize UI languages and Update Locale - this.initializeUiLanguagesAndLocale(); + this._applyTranslationOnSmartTable(); // + this._loadSmartTableSettings(); // Load smart table settings + this.initializeUiPermissions(); // Initialize UI permissions + this.initializeUiLanguagesAndLocale(); // Initialize UI languages and Update Locale + this._initializePageElements(); // Register page elements } ngAfterViewInit(): void { @@ -135,6 +139,59 @@ export class JobEmployeeComponent extends PaginationFilterBaseComponent implemen .subscribe(); } + /** + * Initializes page elements by registering page tabs and data table columns. + * + * This method centralizes the logic for setting up page-related configurations, + * ensuring that the necessary page tabs and data table columns are registered + * upon initialization. + */ + private _initializePageElements(): void { + // Register page elements + this.registerPageTabs(this._pageTabRegistryService); // Register page tabs + this.registerDataTableColumns(this._pageDataTableRegistryService); // Register data table columns + } + + /** + * Register page tabs for the JobEmployee + * + * @param _pageTabRegistryService + */ + registerPageTabs(_pageTabRegistryService: PageTabRegistryService): void { + // Register the browse tab + _pageTabRegistryService.registerPageTab({ + tabsetId: this.tabsetId, // The identifier for the tabset + tabId: 'browse', // The identifier for the tab + tabsetType: 'standard', // The type of tabset to use + tabTitle: (_i18n) => _i18n.getTranslation('JOB_EMPLOYEE.BROWSE'), // The title for the tab + order: 1, // The order of the tab, + responsive: true, // Whether the tab is responsive, + template: this.tableLayout // The template to be rendered in the tab + }); + + // Register the search tab + _pageTabRegistryService.registerPageTab({ + tabsetId: this.tabsetId, // The identifier for the tabset + tabId: 'search', // The identifier for the tab + tabsetType: 'standard', // The type of tabset to use + tabTitle: (_i18n) => _i18n.getTranslation('JOB_EMPLOYEE.SEARCH'), // The title for the tab + order: 2, // The order of the tab, + responsive: true, // Whether the tab is responsive, + template: this.comingSoon // The template to be rendered in the tab + }); + + // Register the history tab + _pageTabRegistryService.registerPageTab({ + tabsetId: this.tabsetId, // The identifier for the tabset + tabId: 'history', // The identifier for the tab + tabsetType: 'standard', // The type of tabset to use + tabTitle: (_i18n) => _i18n.getTranslation('JOB_EMPLOYEE.HISTORY'), // The title for the tab + order: 3, // The order of the tab, + responsive: true, // Whether the tab is responsive, + template: this.comingSoon // The template to be rendered in the tab + }); + } + /** * Register data table columns for the JobEmployee * diff --git a/packages/plugins/job-employee-ui/src/lib/job-employee.module.ts b/packages/plugins/job-employee-ui/src/lib/job-employee.module.ts index 2e329b1c92b..12a9888d57c 100644 --- a/packages/plugins/job-employee-ui/src/lib/job-employee.module.ts +++ b/packages/plugins/job-employee-ui/src/lib/job-employee.module.ts @@ -14,7 +14,7 @@ import { NgxPermissionsModule } from 'ngx-permissions'; import { LanguagesEnum } from '@gauzy/contracts'; import { PageRouteRegistryService } from '@gauzy/ui-core/core'; import { HttpLoaderFactory } from '@gauzy/ui-core/i18n'; -import { SharedModule, SmartDataViewLayoutModule } from '@gauzy/ui-core/shared'; +import { DynamicTabsModule, SharedModule, SmartDataViewLayoutModule } from '@gauzy/ui-core/shared'; import { createJobEmployeeRoutes } from './job-employee.routes'; import { JobEmployeeComponent } from './components/job-employee/job-employee.component'; @@ -45,7 +45,8 @@ const THIRD_PARTY_MODULES = [ ...NB_MODULES, ...THIRD_PARTY_MODULES, SharedModule, - SmartDataViewLayoutModule + SmartDataViewLayoutModule, + DynamicTabsModule ], providers: [ { diff --git a/packages/plugins/job-employee-ui/src/lib/job-employee.routes.ts b/packages/plugins/job-employee-ui/src/lib/job-employee.routes.ts index 71de96fd9ed..06c0b0d69d0 100644 --- a/packages/plugins/job-employee-ui/src/lib/job-employee.routes.ts +++ b/packages/plugins/job-employee-ui/src/lib/job-employee.routes.ts @@ -15,6 +15,9 @@ export const createJobEmployeeRoutes = (_pageRouteRegistryService: PageRouteRegi component: JobEmployeeComponent, canActivate: [PermissionsGuard], data: { + // The tabset identifier for the route + tabsetId: 'job-employee', + // The permission required to access the route permissions: { only: [PermissionsEnum.ORG_JOB_EMPLOYEE_VIEW], redirectTo: '/pages/jobs/search' diff --git a/packages/ui-core/core/src/lib/services/page/page-tab-registry.service.ts b/packages/ui-core/core/src/lib/services/page/page-tab-registry.service.ts index 2d818d7bf3f..d8bd4aa4ac4 100644 --- a/packages/ui-core/core/src/lib/services/page/page-tab-registry.service.ts +++ b/packages/ui-core/core/src/lib/services/page/page-tab-registry.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Type } from '@angular/core'; +import { Injectable, TemplateRef, Type } from '@angular/core'; import { PageTabsetRegistryId } from '../../common/component-registry.types'; import { IPageTabRegistry, PageTabRegistryConfig } from './page-tab-registry.types'; @@ -66,6 +66,7 @@ export class PageTabRegistryService implements IPageTabRegistry { config.order = config.order ?? 0; // Set the default order to 0 if not provided config.hide = config.hide ?? false; // Set the default hide to false if not provided config.responsive = config.responsive ?? true; // Set the default responsive to true if not provided + config.active = config.active ?? false; // Set the default active to false if not provided // Find the index of an existing tab with the same tabId in the specified tab set const existing = tabs.findIndex((tab) => tab.tabId === config.tabId); @@ -199,17 +200,20 @@ export class PageTabRegistryService implements IPageTabRegistry { /** * @description - * Retrieves the component associated with a specific tab ID for a given tabset. + * Retrieves the component or template associated with a specific tab ID for a given tabset. * * This method looks up the tabset using the provided `tabsetId`, then searches for the tab - * with the specified `tabId` within that tabset. If the tab is found, it returns the associated - * component; otherwise, it returns `undefined`. + * with the specified `tabId` within that tabset. If the tab is found, it returns either the + * component or the template based on the tab's configuration; otherwise, it returns `undefined`. * * @param tabsetId The identifier of the tabset to retrieve tabs from. - * @param tabId The identifier of the tab whose component is to be retrieved. - * @returns The component associated with the specified tab ID, or `undefined` if the tab or component is not found. + * @param tabId The identifier of the tab whose component or template is to be retrieved. + * @returns The component or template associated with the specified tab ID, or `undefined` if neither is found. */ - public getComponentForTab(tabsetId: PageTabsetRegistryId, tabId: string): Type | undefined { + public getComponentOrTemplateForTab( + tabsetId: PageTabsetRegistryId, + tabId: string + ): Type | TemplateRef | undefined { // Retrieve the list of tabs for the specified tabsetId const tabs = this.getPageTabset(tabsetId); @@ -218,8 +222,8 @@ export class PageTabRegistryService implements IPageTabRegistry { // Find the tab with the specified tabId const tab = tabs.find((t) => t.tabId === tabId); - // Return the component associated with the tab ID or undefined if not found - return tab?.component; + // Return the component if it exists, otherwise return the template + return tab?.component || tab?.template; } // Return undefined if the tabs could not be retrieved or are not an array diff --git a/packages/ui-core/core/src/lib/services/page/page-tab-registry.types.ts b/packages/ui-core/core/src/lib/services/page/page-tab-registry.types.ts index a06a6ecc9ab..3bc98624a67 100644 --- a/packages/ui-core/core/src/lib/services/page/page-tab-registry.types.ts +++ b/packages/ui-core/core/src/lib/services/page/page-tab-registry.types.ts @@ -1,11 +1,36 @@ -import { Type } from '@angular/core'; +import { TemplateRef, Type } from '@angular/core'; import { NbRouteTab } from '@nebular/theme'; +import { I18nService } from '@gauzy/ui-core/i18n'; import { PageTabsetRegistryId } from '../../common/component-registry.types'; +/** + * Custom page tab configuration options. + */ +export interface CustomNbRouteTab extends NbRouteTab { + /** + * @description + * Specifies if the tab is active. + */ + active?: boolean; + + /** + * @description + * The component to be loaded in the tab. This can be a component reference or a string identifier + * for lazy loading the component. + */ + component?: Type; + + /** + * @description + * The template to be rendered in the tab. This is an alternative to the component. + */ + template?: TemplateRef; +} + /** * Page tab configuration options. */ -export interface PageTabRegistryConfig extends NbRouteTab { +export interface PageTabRegistryConfig extends CustomNbRouteTab { /** * @description * The tabset identifier for the page tabset. @@ -22,7 +47,7 @@ export interface PageTabRegistryConfig extends NbRouteTab { * @description * The translatable key for the tab title. */ - tabTitle: string | (() => string); + tabTitle: string | ((_: I18nService) => string); /** * @description @@ -30,13 +55,6 @@ export interface PageTabRegistryConfig extends NbRouteTab { */ tabIcon?: string; - /** - * @description - * The component to be loaded in the tab. This can be a component reference or a string identifier - * for lazy loading the component. - */ - component?: Type; - /** * @description * The order of the tab in the tabs bar. @@ -50,12 +68,6 @@ export interface PageTabRegistryConfig extends NbRouteTab { */ tabsetType: 'route' | 'standard'; - /** - * @description - * Specifies if the tab is active. - */ - active?: boolean; - /** * @description * Specifies if the tab is hidden for any reason. diff --git a/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.html b/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.html index 8e6cc5647dd..aade4e115a2 100644 --- a/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.html +++ b/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.html @@ -5,14 +5,26 @@ - {{ tabs | json }} - + [active]="tab.active" + [disabled]="tab.disabled" + > + + + + + + + + + + + + diff --git a/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.ts b/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.ts index 33106d93839..5a6c6f233ab 100644 --- a/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.ts +++ b/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.ts @@ -1,10 +1,25 @@ -import { Component, OnInit, Input, OnDestroy, ChangeDetectorRef } from '@angular/core'; +import { + Component, + OnInit, + Input, + OnDestroy, + ChangeDetectorRef, + ViewContainerRef, + ComponentFactoryResolver, + Type +} from '@angular/core'; import { NbRouteTab } from '@nebular/theme'; import { TranslateService } from '@ngx-translate/core'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { tap } from 'rxjs/operators'; import { Subject } from 'rxjs/internal/Subject'; -import { PageTabRegistryConfig, PageTabRegistryService, PageTabsetRegistryId } from '@gauzy/ui-core/core'; +import { + CustomNbRouteTab, + PageTabRegistryConfig, + PageTabRegistryService, + PageTabsetRegistryId +} from '@gauzy/ui-core/core'; +import { I18nService } from '@gauzy/ui-core/i18n'; @UntilDestroy() @Component({ @@ -38,8 +53,10 @@ export class DynamicTabsComponent implements OnInit, OnDestroy { constructor( private readonly _cdr: ChangeDetectorRef, + private readonly _componentFactoryResolver: ComponentFactoryResolver, + private readonly _translateService: TranslateService, private readonly _pageTabRegistryService: PageTabRegistryService, - private readonly _translateService: TranslateService + readonly _i18n: I18nService ) {} ngOnInit(): void { @@ -96,21 +113,34 @@ export class DynamicTabsComponent implements OnInit, OnDestroy { * @param tabsetId The identifier for the tabset. * @returns An array of NbRouteTab objects representing the registered tabs. */ - getRegisteredNbTabs(tabsetId: PageTabsetRegistryId): NbRouteTab[] { + getRegisteredNbTabs(tabsetId: PageTabsetRegistryId): CustomNbRouteTab[] { // Map each tab configuration to an NbRouteTab object return this.getRegisteredTabs(tabsetId).map((tab: PageTabRegistryConfig): NbRouteTab => { - // Create a new route object - const route: NbRouteTab = { + // Create a new route tab object + const route: CustomNbRouteTab = { ...(tab.tabTitle && { - title: typeof tab.tabTitle === 'function' ? tab.tabTitle() : tab.tabTitle + title: typeof tab.tabTitle === 'function' ? tab.tabTitle(this._i18n) : tab.tabTitle }), ...(tab.route && { route: tab.route }), ...(tab.tabIcon && { icon: tab.tabIcon }), ...(tab.responsive && { responsive: tab.responsive }), ...(tab.activeLinkOptions && { activeLinkOptions: tab.activeLinkOptions }), - disabled: !!tab.disabled + disabled: !!tab.disabled, + active: !!tab.active }; + // Check if the tabset is a router tabset + if (!this.isRouterTabset) { + // Check if the route configuration has a component or loadChildren property + if (tab.template) { + // Set the template property to the config object + route.template = tab.template; + } else if (tab.component) { + // Set the component property to the config object + route.component = tab.component; + } + } + // Return the route object return route; }); @@ -128,7 +158,7 @@ export class DynamicTabsComponent implements OnInit, OnDestroy { this._translateService.onLangChange .pipe( // Re-initialize the tabs when the language changes - tap(() => this._initializeTabs()), + tap(() => this.reload$.next(true)), // Automatically unsubscribe when the component is destroyed untilDestroyed(this) ) @@ -136,17 +166,20 @@ export class DynamicTabsComponent implements OnInit, OnDestroy { } /** - * Create dynamic components for the tabs + * Create and insert a dynamic component into the specified ViewContainerRef. + * + * @param component The component to be created dynamically. + * @param viewContainer The ViewContainerRef where the component should be inserted. */ - createDynamicComponents(): void { - // Add logic to create dynamic components for the tabset - } + createDynamicComponentForTab(component: Type, viewContainer: ViewContainerRef): void { + // Resolve the component factory for the given component type + const factory = this._componentFactoryResolver.resolveComponentFactory(component); - /** - * Create static tabs for the tabset - */ - createStaticTabs(): void { - // Add logic to create static tabs for NbTabsetComponent if necessary + // Clear any existing view in the container + viewContainer.clear(); + + // Create and insert the component into the view container + viewContainer.createComponent(factory); } ngOnDestroy(): void {} From 78709db138ed0ce49bd91070a618a437c0ea6ec2 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Fri, 30 Aug 2024 23:26:10 +0530 Subject: [PATCH 3/3] feat: static tabs load template or component dynamically --- .../pages/dashboard/dashboard.component.ts | 2 +- .../timesheet/layout/layout.component.ts | 10 +-- .../job-employee/job-employee.component.ts | 13 +++- .../src/lib/job-employee.routes.ts | 2 + .../page/page-data-table-registry.service.ts | 40 ++++++++++ .../page/page-route-registry.service.ts | 16 ++++ .../page/page-tab-registry.service.ts | 10 ++- .../services/page/page-tab-registry.types.ts | 12 +-- .../dynamic-tabs/dynamic-tabs.component.html | 25 +++--- .../dynamic-tabs/dynamic-tabs.component.ts | 77 ++++++++++++++++--- 10 files changed, 162 insertions(+), 45 deletions(-) diff --git a/apps/gauzy/src/app/pages/dashboard/dashboard.component.ts b/apps/gauzy/src/app/pages/dashboard/dashboard.component.ts index bad0c8bb140..b3aa6d19411 100644 --- a/apps/gauzy/src/app/pages/dashboard/dashboard.component.ts +++ b/apps/gauzy/src/app/pages/dashboard/dashboard.component.ts @@ -183,6 +183,6 @@ export class DashboardComponent extends TranslationBaseComponent implements Afte */ ngOnDestroy() { // Delete the dashboard tabset from the registry - this._pageTabRegistryService.deleteTabset('dashboard'); + this._pageTabRegistryService.deleteTabset(this.tabsetId); } } diff --git a/apps/gauzy/src/app/pages/employees/timesheet/layout/layout.component.ts b/apps/gauzy/src/app/pages/employees/timesheet/layout/layout.component.ts index 20183f89c25..25ed419d9ae 100644 --- a/apps/gauzy/src/app/pages/employees/timesheet/layout/layout.component.ts +++ b/apps/gauzy/src/app/pages/employees/timesheet/layout/layout.component.ts @@ -47,7 +47,7 @@ export class TimesheetLayoutComponent extends TranslationBaseComponent implement if (this._store.hasAnyPermission(...permissions)) { // Register the daily timesheet tab this._pageTabRegistryService.registerPageTab({ - tabsetId: 'timesheet', // The identifier for the tabset + tabsetId: this.tabsetId, // The identifier for the tabset tabId: 'daily', // The identifier for the tab tabsetType: 'route', // The type of tabset to use route: '/pages/employees/timesheets/daily', // The route for the tab @@ -59,7 +59,7 @@ export class TimesheetLayoutComponent extends TranslationBaseComponent implement // Register the weekly timesheet tab this._pageTabRegistryService.registerPageTab({ - tabsetId: 'timesheet', // The identifier for the tabset + tabsetId: this.tabsetId, // The identifier for the tabset tabId: 'weekly', // The identifier for the tab tabsetType: 'route', // The type of tabset to use route: '/pages/employees/timesheets/weekly', // The route for the tab @@ -71,7 +71,7 @@ export class TimesheetLayoutComponent extends TranslationBaseComponent implement // Register the calendar timesheet tab this._pageTabRegistryService.registerPageTab({ - tabsetId: 'timesheet', // The identifier for the tabset + tabsetId: this.tabsetId, // The identifier for the tabset tabId: 'calendar', // The identifier for the tab tabsetType: 'route', // The type of tabset to use route: '/pages/employees/timesheets/calendar', // The route for the tab @@ -86,7 +86,7 @@ export class TimesheetLayoutComponent extends TranslationBaseComponent implement if (this._store.hasPermission(PermissionsEnum.CAN_APPROVE_TIMESHEET)) { // Register the approvals tab this._pageTabRegistryService.registerPageTab({ - tabsetId: 'timesheet', // The identifier for the tabset + tabsetId: this.tabsetId, // The identifier for the tabset tabId: 'approvals', // The identifier for the tab tabsetType: 'route', // The type of tabset to use route: '/pages/employees/timesheets/approvals', // The route for the tab @@ -100,6 +100,6 @@ export class TimesheetLayoutComponent extends TranslationBaseComponent implement ngOnDestroy(): void { // Delete the timesheet tabset from the registry - this._pageTabRegistryService.deleteTabset('timesheet'); + this._pageTabRegistryService.deleteTabset(this.tabsetId); } } diff --git a/packages/plugins/job-employee-ui/src/lib/components/job-employee/job-employee.component.ts b/packages/plugins/job-employee-ui/src/lib/components/job-employee/job-employee.component.ts index a3a971dbbb6..08566c8b70e 100644 --- a/packages/plugins/job-employee-ui/src/lib/components/job-employee/job-employee.component.ts +++ b/packages/plugins/job-employee-ui/src/lib/components/job-employee/job-employee.component.ts @@ -19,7 +19,8 @@ import { Store, ToastrService, PageTabsetRegistryId, - PageTabRegistryService + PageTabRegistryService, + PageDataTableRegistryId } from '@gauzy/ui-core/core'; import { I18nService } from '@gauzy/ui-core/i18n'; import { @@ -48,6 +49,7 @@ export enum JobSearchTabsEnum { }) export class JobEmployeeComponent extends PaginationFilterBaseComponent implements AfterViewInit, OnInit, OnDestroy { public tabsetId: PageTabsetRegistryId = this._route.snapshot.data.tabsetId; // The identifier for the tabset + public dataTableId: PageDataTableRegistryId = this._route.snapshot.data.dataTableId; // The identifier for the data table public jobSearchTabsEnum = JobSearchTabsEnum; public loading: boolean = false; public settingsSmartTable: any; @@ -162,6 +164,7 @@ export class JobEmployeeComponent extends PaginationFilterBaseComponent implemen _pageTabRegistryService.registerPageTab({ tabsetId: this.tabsetId, // The identifier for the tabset tabId: 'browse', // The identifier for the tab + tabIcon: 'globe-2-outline', // The icon for the tab tabsetType: 'standard', // The type of tabset to use tabTitle: (_i18n) => _i18n.getTranslation('JOB_EMPLOYEE.BROWSE'), // The title for the tab order: 1, // The order of the tab, @@ -173,6 +176,7 @@ export class JobEmployeeComponent extends PaginationFilterBaseComponent implemen _pageTabRegistryService.registerPageTab({ tabsetId: this.tabsetId, // The identifier for the tabset tabId: 'search', // The identifier for the tab + tabIcon: 'search-outline', // The icon for the tab tabsetType: 'standard', // The type of tabset to use tabTitle: (_i18n) => _i18n.getTranslation('JOB_EMPLOYEE.SEARCH'), // The title for the tab order: 2, // The order of the tab, @@ -184,6 +188,7 @@ export class JobEmployeeComponent extends PaginationFilterBaseComponent implemen _pageTabRegistryService.registerPageTab({ tabsetId: this.tabsetId, // The identifier for the tabset tabId: 'history', // The identifier for the tab + tabIcon: 'clock-outline', // The icon for the tab tabsetType: 'standard', // The type of tabset to use tabTitle: (_i18n) => _i18n.getTranslation('JOB_EMPLOYEE.HISTORY'), // The title for the tab order: 3, // The order of the tab, @@ -623,5 +628,9 @@ export class JobEmployeeComponent extends PaginationFilterBaseComponent implemen } }; - ngOnDestroy(): void {} + ngOnDestroy(): void { + // Delete the dashboard tabset from the registry + this._pageTabRegistryService.deleteTabset(this.tabsetId); + this._pageDataTableRegistryService.deleteDataTable(this.dataTableId); + } } diff --git a/packages/plugins/job-employee-ui/src/lib/job-employee.routes.ts b/packages/plugins/job-employee-ui/src/lib/job-employee.routes.ts index 06c0b0d69d0..f0acdf2639d 100644 --- a/packages/plugins/job-employee-ui/src/lib/job-employee.routes.ts +++ b/packages/plugins/job-employee-ui/src/lib/job-employee.routes.ts @@ -17,6 +17,8 @@ export const createJobEmployeeRoutes = (_pageRouteRegistryService: PageRouteRegi data: { // The tabset identifier for the route tabsetId: 'job-employee', + // The data table identifier for the route + dataTableId: 'job-employee', // The permission required to access the route permissions: { only: [PermissionsEnum.ORG_JOB_EMPLOYEE_VIEW], diff --git a/packages/ui-core/core/src/lib/services/page/page-data-table-registry.service.ts b/packages/ui-core/core/src/lib/services/page/page-data-table-registry.service.ts index 3219f7a3b63..3c9d3f50a80 100644 --- a/packages/ui-core/core/src/lib/services/page/page-data-table-registry.service.ts +++ b/packages/ui-core/core/src/lib/services/page/page-data-table-registry.service.ts @@ -14,6 +14,21 @@ export class PageDataTableRegistryService implements IPageDataTableRegistry { */ private readonly registry = new Map(); + /** + * Retrieves a read-only copy of the data table registry. + * + * This method returns a new `Map` instance based on the current state of the registry. + * This approach ensures that the original `registry` is not directly modified by + * external code, preserving immutability and encapsulation. + * + * @returns A `ReadonlyMap` containing the current data table registry. This map + * cannot be modified, ensuring that the internal state remains unchanged. + */ + public getRegistry(): ReadonlyMap { + // Return a new Map to provide a snapshot of the current registry state + return new Map(this.registry); + } + /** * Register a column configurations. * @@ -148,4 +163,29 @@ export class PageDataTableRegistryService implements IPageDataTableRegistry { return acc; }, {}); } + + /** + * Deletes a data table from the registry. + * + * This method removes the specified data table from the registry. If the data table does not exist, + * it logs a warning message. Additionally, if the operation is successful, it logs an informational message. + * + * @param dataTableId The identifier of the data table to be removed. + * @returns void + */ + public deleteDataTable(dataTableId: PageDataTableRegistryId): void { + // Check if the data table exists in the registry + if (!this.registry.has(dataTableId)) { + console.warn(`Data table with id "${dataTableId}" does not exist in the registry.`); + return; + } + + try { + // Remove the data table from the registry + this.registry.delete(dataTableId); + console.log(`Data table with id "${dataTableId}" has been successfully removed from the registry.`); + } catch (error) { + console.error(`Failed to remove data table with id "${dataTableId}": ${error.message}`); + } + } } diff --git a/packages/ui-core/core/src/lib/services/page/page-route-registry.service.ts b/packages/ui-core/core/src/lib/services/page/page-route-registry.service.ts index 92dbe5589f3..8866bff4166 100644 --- a/packages/ui-core/core/src/lib/services/page/page-route-registry.service.ts +++ b/packages/ui-core/core/src/lib/services/page/page-route-registry.service.ts @@ -14,6 +14,22 @@ export class PageRouteRegistryService implements IPageRouteRegistry { */ private readonly registry = new Map(); + /** + * Retrieves a read-only snapshot of the page route registry. + * + * This method returns a new `Map` instance based on the current state of the `registry`. + * This approach ensures that the original `registry` remains unchanged and protected + * from direct modifications, preserving encapsulation and immutability. + * + * @returns A `ReadonlyMap` containing the current page route registry. This map + * provides a snapshot of the registry's state and cannot be modified, + * ensuring that internal data integrity is maintained. + */ + public getRegistry(): ReadonlyMap { + // Create and return a new Map to provide an immutable view of the current registry state + return new Map(this.registry); + } + /** * Register a single page route configuration. * diff --git a/packages/ui-core/core/src/lib/services/page/page-tab-registry.service.ts b/packages/ui-core/core/src/lib/services/page/page-tab-registry.service.ts index d8bd4aa4ac4..38ea31711c5 100644 --- a/packages/ui-core/core/src/lib/services/page/page-tab-registry.service.ts +++ b/packages/ui-core/core/src/lib/services/page/page-tab-registry.service.ts @@ -193,9 +193,13 @@ export class PageTabRegistryService implements IPageTabRegistry { return; } - // Remove the tabset from the registry - this.registry.delete(tabsetId); - console.log(`Tabset with id "${tabsetId}" has been successfully removed from the registry.`); + try { + // Remove the tabset from the registry + this.registry.delete(tabsetId); + console.log(`Tabset with id "${tabsetId}" has been successfully removed from the registry.`); + } catch (error) { + console.error(`Failed to remove tabset with id "${tabsetId}": ${error.message}`); + } } /** diff --git a/packages/ui-core/core/src/lib/services/page/page-tab-registry.types.ts b/packages/ui-core/core/src/lib/services/page/page-tab-registry.types.ts index 3bc98624a67..5099a3ef7fb 100644 --- a/packages/ui-core/core/src/lib/services/page/page-tab-registry.types.ts +++ b/packages/ui-core/core/src/lib/services/page/page-tab-registry.types.ts @@ -7,6 +7,12 @@ import { PageTabsetRegistryId } from '../../common/component-registry.types'; * Custom page tab configuration options. */ export interface CustomNbRouteTab extends NbRouteTab { + /** + * @description + * The identifier for the tab. + */ + tabId?: string; + /** * @description * Specifies if the tab is active. @@ -37,12 +43,6 @@ export interface PageTabRegistryConfig extends CustomNbRouteTab { */ tabsetId: PageTabsetRegistryId; - /** - * @description - * The identifier for the tab. - */ - tabId: string; - /** * @description * The translatable key for the tab title. diff --git a/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.html b/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.html index aade4e115a2..5b66f0f79ee 100644 --- a/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.html +++ b/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.html @@ -8,23 +8,16 @@ - - - - - - - - - - + + diff --git a/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.ts b/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.ts index 5a6c6f233ab..de79f415044 100644 --- a/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.ts +++ b/packages/ui-core/shared/src/lib/components/dynamic-tabs/dynamic-tabs.component.ts @@ -5,8 +5,10 @@ import { OnDestroy, ChangeDetectorRef, ViewContainerRef, - ComponentFactoryResolver, - Type + Type, + TemplateRef, + ViewChildren, + QueryList } from '@angular/core'; import { NbRouteTab } from '@nebular/theme'; import { TranslateService } from '@ngx-translate/core'; @@ -29,11 +31,15 @@ import { I18nService } from '@gauzy/ui-core/i18n'; providers: [] }) export class DynamicTabsComponent implements OnInit, OnDestroy { - public tabs: NbRouteTab[] = []; // Define the structure of tabs according to your needs + public tabs: CustomNbRouteTab[] = []; // Define the structure of tabs according to your needs public reload$ = new Subject(); // Subject to trigger reload of tabs + // Input to set the tabsetId @Input() tabsetId!: PageTabsetRegistryId; + // ViewChildren to access the tab content containers + @ViewChildren('tabContent', { read: ViewContainerRef }) tabContents!: QueryList; + /** * Determines if all tabs in the tabset have the tabsetType set to 'route'. * @@ -53,7 +59,6 @@ export class DynamicTabsComponent implements OnInit, OnDestroy { constructor( private readonly _cdr: ChangeDetectorRef, - private readonly _componentFactoryResolver: ComponentFactoryResolver, private readonly _translateService: TranslateService, private readonly _pageTabRegistryService: PageTabRegistryService, readonly _i18n: I18nService @@ -65,6 +70,10 @@ export class DynamicTabsComponent implements OnInit, OnDestroy { this._setupReloadTabsListener(); } + ngAfterViewInit(): void { + this._loadTabsContent(); // Load the tab content for each tab in the tabset + } + /** * Setup listener for reloading tabs. */ @@ -77,6 +86,30 @@ export class DynamicTabsComponent implements OnInit, OnDestroy { .subscribe(); } + /** + * Load the tab content for each tab in the tabset. + */ + private _loadTabsContent(): void { + // Load the tab content for each tab in the tabset + this.tabContents.forEach((container: ViewContainerRef, index: number) => { + // Get the tab configuration for the current index + const tab = this.tabs[index]; + + // Get the component or template for the tab + const content = this._pageTabRegistryService.getComponentOrTemplateForTab(this.tabsetId, tab.tabId); + + // Check if the content is a template or component + if (content instanceof TemplateRef) { + this.loadTemplateForTab(content, container); // Handle template loading + } else if (content instanceof Type) { + this.loadComponentForTab(content, container); // Handle component loading + } + }); + + // Detect changes after loading tab content + this._cdr.detectChanges(); + } + /** * Initializes the tabs for the component based on the provided tabset ID. * @@ -121,6 +154,7 @@ export class DynamicTabsComponent implements OnInit, OnDestroy { ...(tab.tabTitle && { title: typeof tab.tabTitle === 'function' ? tab.tabTitle(this._i18n) : tab.tabTitle }), + ...(tab.tabId && { tabId: tab.tabId }), ...(tab.route && { route: tab.route }), ...(tab.tabIcon && { icon: tab.tabIcon }), ...(tab.responsive && { responsive: tab.responsive }), @@ -165,21 +199,40 @@ export class DynamicTabsComponent implements OnInit, OnDestroy { .subscribe(); } + /** + * Loads a template into a specified container. + * + * This method clears any existing content in the provided container + * and then creates an embedded view for the given template, inserting + * it into the container. + * + * @param template The template to be loaded into the container. + * @param container The container where the template will be inserted. + */ + loadTemplateForTab(template: TemplateRef, container: ViewContainerRef): void { + // Clear any existing content in the container + container.clear(); + + // Create an embedded view for the provided template and insert it into the container + container.createEmbeddedView(template); + } + /** * Create and insert a dynamic component into the specified ViewContainerRef. * + * This method clears any existing content in the provided container, + * then creates a new instance of the specified component and inserts it + * into the container. + * * @param component The component to be created dynamically. - * @param viewContainer The ViewContainerRef where the component should be inserted. + * @param container The ViewContainerRef where the component should be inserted. */ - createDynamicComponentForTab(component: Type, viewContainer: ViewContainerRef): void { - // Resolve the component factory for the given component type - const factory = this._componentFactoryResolver.resolveComponentFactory(component); - - // Clear any existing view in the container - viewContainer.clear(); + loadComponentForTab(component: Type, container: ViewContainerRef): void { + // Ensure the container is available and clear any existing content + container.clear(); // Create and insert the component into the view container - viewContainer.createComponent(factory); + container.createComponent(component); } ngOnDestroy(): void {}