import { Component, OnInit } from '@angular/core'; import { GC_Feature_Flags } from '@core/services/feature-flags.service'; import { SpinnerService } from '@core/services/spinner.service'; import { ReferenceFieldAPI } from '@core/typings/api/reference-fields.typing'; import { ReferenceFieldsUI } from '@core/typings/ui/reference-fields.typing'; import { ClientSettingsService } from '@features/client-settings/client-settings.service'; import { FormAudience } from '@features/configure-forms/form.typing'; import { FormsService } from '@features/configure-forms/services/forms/forms.service'; import { CustomDataTablesService } from '@features/custom-data-tables/custom-data-table.service'; import { StandardProductConfigurationService } from '@features/platform-admin/standard-product-configuration/standard-product-configuration.service'; import { StandardProductTypes } from '@features/platform-admin/standard-product-configuration/standard-product-configuration.typing'; import { BulkAction, DebounceFactory, FilterModalTypes, PaginationOptions, SimpleStringMap, TableDataFactory, TopLevelFilter, TypeaheadSelectOption } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { ConfirmationModalComponent, ModalFactory } from '@yourcause/common/modals'; import { ConvertFieldTypeModalComponent } from '../convert-field-type-modal/convert-field-type-modal.component'; import { CreateEditReferenceFieldModalComponent } from '../create-edit-reference-field-modal/create-edit-reference-field-modal.component'; import { ManageCategoryModalComponent } from '../manage-category-modal/manage-category-modal.component'; import { MergeFieldsModalComponent } from '../merge-fields-modal/merge-fields-modal.component'; import { MergeWithStandardFieldModalComponent } from '../merge-with-standard-field-modal/merge-with-standard-field-modal.component'; import { ReferenceFieldsService } from '../services/reference-fields.service'; @Component({ selector: 'gc-reference-fields-page', templateUrl: './reference-fields-page.component.html', styleUrls: ['./reference-fields-page.component.scss'] }) export class ReferenceFieldsPageComponent implements OnInit { GC_Feature_Flags = GC_Feature_Flags; tableDataFactory: TableDataFactory; topLevelFilters: TopLevelFilter[]; typeOptions = this.referenceFieldsService.getReferenceFieldTypeOptions(); ReferenceFieldTypes = ReferenceFieldsUI.ReferenceFieldTypes; audienceOptions: TypeaheadSelectOption[] = [{ label: this.i18n.translate( 'common:lblApplicant', {}, 'Applicant' ), value: FormAudience.APPLICANT }, { label: this.i18n.translate( 'GLOBAL:textGrantManager', {}, 'Grant manager' ), value: FormAudience.MANAGER }]; FormAudience = FormAudience; bulkActions: BulkAction[]; guidToNameMap = this.customDataTableService.guidToNameMap; idToNameMap: Record = this.referenceFieldsService.getIdNameMap(); drilldownMap: SimpleStringMap = {}; RefTypes = ReferenceFieldsUI.ReferenceFieldTypes; // Hidden Filter to filter out any table aggregate fields (not editable or visible) hiddenFilters = [ new TopLevelFilter( 'typeaheadSingleEquals', 'aggregateTableReferenceFieldId', 0, '', { selectOptions: [{ label: '', value: 0, filterTypeOverride: FilterModalTypes.isBlank }] } ) ]; ConversionTypes = ReferenceFieldsUI.RefFieldConversionTypes; constructor ( private i18n: I18nService, private referenceFieldsService: ReferenceFieldsService, private modalFactory: ModalFactory, private spinnerService: SpinnerService, private customDataTableService: CustomDataTablesService, private formService: FormsService, private standardProductService: StandardProductConfigurationService, private clientSettingsService: ClientSettingsService ) { } get categoryNameMap () { return this.referenceFieldsService.categoryNameMap; } get isRootZone () { return this.clientSettingsService.clientSettings.isRootClient; } ngOnInit () { this.setTableDataFactory(); this.setTopLevelFilters(); this.bulkActions = [{ disabled: () => false, label: this.i18n.translate( 'common:textExport', {}, 'Export' ), exec: (fields: ReferenceFieldAPI.ReferenceFieldDisplayModel[]) => { return this.exportFields(fields); } }, { disabled: (fields: ReferenceFieldAPI.ReferenceFieldDisplayModel[]) => { const isAggregateOrTable = fields.some((field) => { return field.type === ReferenceFieldsUI.ReferenceFieldTypes.Table || field.aggregateType; }); // Only 2 fields allowed for merge and no aggregates or tables if (isAggregateOrTable || fields.length !== 2) { return true; } const sameAttributes = this.referenceFieldsService.areAttributesTheSame( fields, [ 'type', 'formAudience', 'customDataTableGuid', 'supportsMultiple', 'parentReferenceFieldId', 'isSingleResponse', 'isEncrypted', 'isMasked', 'isTableField', 'formatType' ] ); return !sameAttributes; }, label: this.i18n.translate( 'common:textMerge', {}, 'Merge' ), exec: (fields: ReferenceFieldAPI.ReferenceFieldDisplayModel[]) => { return this.mergeFields(fields); } }, { disabled: () => false, label: this.i18n.translate( 'common:textManageCategory', {}, 'Manage category' ), exec: (fields: ReferenceFieldAPI.ReferenceFieldDisplayModel[]) => { return this.manageCategory(fields); } }, this.isRootZone ? { disabled: (fields: ReferenceFieldAPI.ReferenceFieldDisplayModel[]) => { return fields.some((field) => field.standardComponentIsPublished); }, label: this.i18n.translate( 'GLOBAL:textPublish', {}, 'Publish' ), exec: (fields: ReferenceFieldAPI.ReferenceFieldDisplayModel[]) => { return this.publishOrUnpublish(fields); } } : undefined].filter((item) => !!item); } async setTableDataFactory () { this.tableDataFactory = DebounceFactory.createSimple( async ( options: PaginationOptions ) => { return this.referenceFieldsService.getReferenceFieldsPaginated( options ); } ); } setTopLevelFilters () { const typeOptions = this.referenceFieldsService.getTypeSelectOptionsForFilters( true, !this.isRootZone ); this.topLevelFilters = [ this.referenceFieldsService.getFilterForNameOrKey(), new TopLevelFilter( 'checkboxDropdown', 'type', [], this.i18n.translate( 'common:textSearchByFormFieldType', {}, 'Search by form field type' ), { selectOptions: typeOptions, filterObjectName: this.i18n.translate( 'GLOBAL:textFormFieldType', {}, 'Form field type' ).toLowerCase(), filterObjectNamePlural: this.i18n.translate( 'GLOBAL:textFormFieldTypes', {}, 'Form Field types' ).toLowerCase() }, this.i18n.translate( 'GLOBAL:textFormFieldType', {}, 'Form field type' ), undefined, true ), this.referenceFieldsService.getCategoryDropdownFilter(), new TopLevelFilter( 'checkboxDropdown', 'formIds', [], this.i18n.translate( 'FORMS:textSearchByForm', {}, 'Search by form' ), { selectOptions: this.formService.allPublishedFormOptions, filterObjectName: this.i18n.translate( 'GLOBAL:textForm', {} ).toLowerCase(), filterObjectNamePlural: this.i18n.translate( 'common:textForms', {} ).toLowerCase() }, undefined, undefined, true ) ].filter((item) => !!item); } async exportFields (fields: ReferenceFieldAPI.ReferenceFieldDisplayModel[]) { this.spinnerService.startSpinner(); await this.referenceFieldsService.exportReferenceFields( fields.map((field) => field.referenceFieldId) ); this.spinnerService.stopSpinner(); } async manageCategory (fields: ReferenceFieldAPI.ReferenceFieldDisplayModel[]) { const categoryId = await this.modalFactory.open( ManageCategoryModalComponent, { fields } ); if (categoryId) { this.spinnerService.startSpinner(); await this.referenceFieldsService.bulkUpdateCategory( fields.map((field) => field.referenceFieldId), categoryId ); this.setTopLevelFilters(); this.spinnerService.stopSpinner(); } } async mergeFields (fieldsToMerge: ReferenceFieldAPI.ReferenceFieldDisplayModel[]) { this.spinnerService.startSpinner(); const fieldDetails = await this.referenceFieldsService.getDetailForFields( fieldsToMerge ); const invalidMergeMessage = this.referenceFieldsService.getMergeInvalidAlert( fieldDetails ); this.spinnerService.stopSpinner(); if (!invalidMergeMessage) { const result = await this.modalFactory.open( MergeFieldsModalComponent, { fieldsToMerge, fieldDetails } ); if (result) { this.spinnerService.startSpinner(); await this.referenceFieldsService.handleMergeFields( result ); this.setTopLevelFilters(); this.spinnerService.stopSpinner(); } } else { this.modalFactory.open( ConfirmationModalComponent, { modalHeader: this.i18n.translate( 'common:hdrMergeFormFields', {}, 'Merge Form Fields' ), modalSubHeader: fieldsToMerge[0].name + ', ' + fieldsToMerge[1].name, confirmButtonText: this.i18n.translate( 'common:textClose', {}, 'Close' ), confirmText: invalidMergeMessage } ); } } async updateReferenceField ( existingReferenceField: ReferenceFieldAPI.ReferenceFieldDisplayModel ) { const isViewOnly = this.isRootZone && existingReferenceField.standardComponentIsPublished; const result = await this.modalFactory.open( CreateEditReferenceFieldModalComponent, { existingReferenceField, isViewOnly } ); if (result && !isViewOnly) { const fieldToSave = { ...existingReferenceField, ...result.field }; this.spinnerService.startSpinner(); await this.referenceFieldsService.handleCreateOrUpdateField( existingReferenceField.referenceFieldId, fieldToSave, result.tableFields ); this.setTopLevelFilters(); this.spinnerService.stopSpinner(); } } async removeReferenceField ( existingReferenceField: ReferenceFieldAPI.ReferenceFieldDisplayModel ) { const cantDeleteTooltip = this.referenceFieldsService.getTooltipForDeleteRefField( existingReferenceField.formCount, existingReferenceField.usedOnReports, existingReferenceField.referenceFieldId, existingReferenceField.standardComponentIsPublished, this.isRootZone ); if (!cantDeleteTooltip) { const confirmed = await this.modalFactory.open( ConfirmationModalComponent, { confirmText: this.i18n.translate( 'FORMS:textAreYouSureDeleteFormField', {}, 'Are you sure you want to delete this form field?' ), modalHeader: this.i18n.translate( 'FORMS:hdrDeleteFormField', {}, 'Delete Form Field' ), confirmButtonText: this.i18n.translate('common:btnDelete', {}, 'Delete') }); if (confirmed) { this.spinnerService.startSpinner(); await this.referenceFieldsService.removeField( existingReferenceField.referenceFieldId ); this.spinnerService.stopSpinner(); } } } async copyReferenceField ( existingReferenceField: ReferenceFieldAPI.ReferenceFieldDisplayModel ) { const confirmed = await this.modalFactory.open( ConfirmationModalComponent, { confirmText: this.i18n.translate( 'FORMS:textAreYouSureCopyFormField', {}, 'Are you sure you want to copy this form field?' ), modalHeader: this.i18n.translate( 'FORMS:hdrCopyFormField', {}, 'Copy Form Field' ), confirmButtonText: this.i18n.translate('common:textCopy', {}, 'Copy') }); if (confirmed) { this.spinnerService.startSpinner(); await this.referenceFieldsService.copyField(existingReferenceField); this.spinnerService.stopSpinner(); } } async onDrilldown (row: ReferenceFieldAPI.ReferenceFieldDisplayModel) { if (!this.drilldownMap[row.key]) { this.spinnerService.startSpinner(); const detail = await this.referenceFieldsService.getDetailForFields([row]); this.drilldownMap = { ...this.drilldownMap, [row.key]: detail[0] }; this.spinnerService.stopSpinner(); } } async publishOrUnpublish (rows: ReferenceFieldAPI.ReferenceFieldDisplayModel[]) { const toUnpublish = rows.every((row) => row.standardComponentIsPublished); const actionText = this.i18n.translate( toUnpublish ? 'GLOBAL:textUnpublish' : 'GLOBAL:textPublish', {}, toUnpublish ? 'Unpublish' : 'Publish' ); let confirmText = this.i18n.translate( toUnpublish ? 'common:textConfirmUnpublishFormField' : 'common:textConfirmPublishFormField', {}, toUnpublish ? 'Are you sure you want to unpublish this form field?' : 'Are you sure you want to publish this form field?' ); if (rows.length > 1) { confirmText = this.i18n.translate( 'common:textConfirmPublishFormFields', {}, 'Are you sure you want to publish the form fields?' ); } const response = await this.modalFactory.open( ConfirmationModalComponent, { modalHeader: actionText, modalSubHeader: rows.length === 1 ? rows[0].name : '', confirmText, confirmButtonText: actionText } ); if (response) { this.spinnerService.startSpinner(); if (toUnpublish) { await this.standardProductService.handleUnpublishStandardComponents( rows[0].referenceFieldId, StandardProductTypes.FORM_FIELDS ); } else { await this.standardProductService.handlePublishStandardComponents( rows.map((row) => row.referenceFieldId), StandardProductTypes.FORM_FIELDS ); } this.spinnerService.stopSpinner(); } } async mergeWithStandardField ( field: ReferenceFieldAPI.ReferenceFieldDisplayModel ) { const idsToMerge = await this.modalFactory.open( MergeWithStandardFieldModalComponent, { field } ); if (idsToMerge) { this.spinnerService.startSpinner(); await this.referenceFieldsService.handleMergeWithStandardField( field.referenceFieldId, idsToMerge ); this.spinnerService.stopSpinner(); } } async convertReferenceFieldType ( row: ReferenceFieldAPI.ReferenceFieldDisplayModel, conversionType: ReferenceFieldsUI.RefFieldConversionTypes ) { this.spinnerService.startSpinner(); const validityInfo = await this.referenceFieldsService.validateFieldTypeConversion( row.referenceFieldId, conversionType ); this.spinnerService.stopSpinner(); const response = await this.modalFactory.open( ConvertFieldTypeModalComponent, { validityInfo, conversionType, fieldName: row.name } ); if (response) { this.spinnerService.startSpinner(); await this.referenceFieldsService.handleFieldTypeConversion( row.referenceFieldId, conversionType ); this.spinnerService.stopSpinner(); } } }