import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { Router } from '@angular/router'; import { LookupService } from '@core/services/lookup.service'; import { PortalDeterminationService } from '@core/services/portal-determination.service'; import { SpinnerService } from '@core/services/spinner.service'; import { LocationState } from '@core/states/location.state'; import { Country } from '@core/typings/location.typing'; import { ApplicantOrganization, OrgUnion, SelectedSearchType } from '@core/typings/organization.typing'; import { ProcessingTypes } from '@core/typings/payment.typing'; import { AddOrgUI } from '@core/typings/ui/add-org.typing'; import { AddOrganizationModalManagerService } from '@features/add-organization/add-organization-modal-manager.service'; import { AddOrganizationService } from '@features/add-organization/add-organization.service'; import { OrgNotEligibleModalComponent } from '@features/add-organization/org-not-eligible-modal/org-not-eligible-modal.component'; import { ClientSettingsService } from '@features/client-settings/client-settings.service'; import { NonprofitService } from '@features/nonprofit/nonprofit.service'; import { AutoTableRepository, AutoTableRepositoryFactory, BucketSearchResultObj, DebounceFactory, OrganizationEligibleForGivingStatus, OrganizationSearchResponse, PaginationOptions, SearchResult, TopLevelFilter, TypeaheadSelectOption, TypeSafeFormBuilder, TypeSafeFormGroup, ValueComparisonService } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { ModalFactory, YCModalComponent } from '@yourcause/common/modals'; import { NotifierService } from '@yourcause/common/notifier'; import { TabsetComponent } from 'ngx-bootstrap/tabs'; import { map, Observable, Subscription } from 'rxjs'; interface SearchAllOrgsFormGroup { term: string; countryCode: string; stateProvRegCode: string; } @Component({ templateUrl: './search-all-orgs-modal.component.html', selector: 'gc-search-all-orgs', styleUrls: ['./search-all-orgs-modal.component.scss'] }) export class SearchAllOrgsModalComponent extends YCModalComponent<{ selectedOrg: SearchResult; isPrivate: boolean; }> implements OnInit, OnDestroy { @Input() showPrivateOrgs = true; @Input() hideAddOrg = false; @Input() saveNewlyCreatedOrgs = true; @Input() orgSearchGuidelines: string; @Input() isNomination = false; @Input() exitRouterLink: string; // Pass in for action to exit modal and go to specified route @Input() exitLinkText: string; @Input() processorType: ProcessingTypes; @Input() favoriteOrgs: ApplicantOrganization[] = []; @Input() charityBucketId: string; @Input() clientId: number; @Input() forceOrgSelect: boolean; @ViewChild('staticTabs') staticTabs: TabsetComponent; cantAddOrgs: boolean; showFavoriteOrgs: boolean; showPanel: number; useBucketSearch = false; hasInternational = this.clientSettingsService.clientSettings.hasInternational; hide = false; formGroup: TypeSafeFormGroup; topLevelFilters: TopLevelFilter[] = [ new TopLevelFilter( 'text', 'term', '', '' ) ]; rowsPerPage = 4; resetRegions = false; country: Country; publicLoading = false; bucketLoading = false; privateLoading = false; selectedSearchType = SelectedSearchType.Public; SelectedSearchType = SelectedSearchType; publicRepository: AutoTableRepository; privateRepository: AutoTableRepository; bucketRepository: AutoTableRepository; subs = new Subscription(); initialSearch = true; isManager = this.portal.isManager; clientDefaultCountry = this.clientSettingsService.clientSettings.country || 'US'; countrySelects$: Observable[]> = this.locationState .changesTo$('countries').pipe( map((countries) => { countries = countries || []; if (!(countries.some((country) => country.code === ''))) { const allCountries = this.i18n.translate( 'common:textAllCountries', {}, 'All countries' ); countries.unshift({ hasState: false, id: null, code: '', name: '-- ' + allCountries + ' --' }); } return countries.map(country => ({ label: country.name, value: country.code })); })); regionSelects$: Observable[]> = this.locationState .changesTo$('regions').pipe( map(regionMap => { regionMap = regionMap || {}; const countries = this.locationState.countries; const countryCode = this.formGroup.value.countryCode; this.country = countries.find(({ code }) => code === countryCode); const regionsExist = !!regionMap[countryCode] || !this.country.hasState; if (!regionsExist) { this.lookupService.getStatesByCountryId(this.country.id) .then((result) => { this.locationState.set('regions', { ...regionMap, [countryCode]: result }); }); } if ( (countryCode !== '') && regionMap[countryCode] && !(regionMap[countryCode].some((region) => region.code === '')) ) { const allStatesProvsRegions = this.i18n.translate( 'common:textAllStatesProvsRegions', {}, 'All states, provinces, or regions' ); regionMap[countryCode].unshift({ name: '-- ' + allStatesProvsRegions + ' --', code: '' }); } return regionMap[countryCode] || []; }), map(regions => regions.map(region => ({ label: region.name, value: region.code })))); publicOrgsTableDataFactory = DebounceFactory.createSimple(async (options: PaginationOptions) => { return this.nonprofitService.doSearchPublic( options.pageNumber, this.searchTerm, this.rowsPerPage, this.stateProvRegCode, this.countryCode, this.country?.name, this.processorType ); }, 1000); bucketOrgsTableDataFactory = DebounceFactory.createSimple(async (options: PaginationOptions) => { return this.nonprofitService.doBucketSearch( options.pageNumber, this.searchTerm, this.stateProvRegCode, this.countryCode, this.rowsPerPage, this.clientId, this.charityBucketId ); }, 1000); privateOrgsTableDataFactory = DebounceFactory.createSimple(async (options: PaginationOptions) => { return this.nonprofitService.doSearchPrivate( options.pageNumber, this.searchTerm, this.rowsPerPage, this.processorType, this.stateProvRegCode, this.countryCode, this.clientId ); }, 1000); constructor ( private formBuilder: TypeSafeFormBuilder, private autoTableFactory: AutoTableRepositoryFactory, private notifierService: NotifierService, private valueComparisonService: ValueComparisonService, private locationState: LocationState, private lookupService: LookupService, private i18n: I18nService, private nonprofitService: NonprofitService, private spinnerService: SpinnerService, private addOrgManageModalService: AddOrganizationModalManagerService, private clientSettingsService: ClientSettingsService, private addOrganizationService: AddOrganizationService, private modalFactory: ModalFactory, private portal: PortalDeterminationService, private router: Router ) { super(); } get searchTerm () { return this.formGroup.value.term; } get countryCode () { return this.formGroup.value.countryCode; } get stateProvRegCode () { return this.formGroup.value.stateProvRegCode; } ngOnInit () { this.formGroup = this.formBuilder.group({ term: '', countryCode: this.hasInternational ? '' : this.clientDefaultCountry, stateProvRegCode: '' }); this.useBucketSearch = !!this.charityBucketId; this.cantAddOrgs = this.useBucketSearch || this.hideAddOrg || ( this.processorType === ProcessingTypes.YourCause && !this.hasInternational ); this.showFavoriteOrgs = this.favoriteOrgs.length > 0; this.setRepositories(); } setRepositories () { this.publicRepository = this.autoTableFactory.create({ key: 'PUBLIC_ORG_SEARCH', columns: [], notifier: this.notifierService, rowsPerPage: this.rowsPerPage, valueComparisonService: this.valueComparisonService, formGroup: this.formGroup, topLevelFilters: this.topLevelFilters, tableDataFactory: this.publicOrgsTableDataFactory, skipAddToState: true }); if (this.showPrivateOrgs) { this.privateRepository = this.autoTableFactory.create({ key: 'PRIVATE_ORG_SEARCH', columns: [], rowsPerPage: this.rowsPerPage, notifier: this.notifierService, valueComparisonService: this.valueComparisonService, formGroup: this.formGroup, topLevelFilters: this.topLevelFilters, tableDataFactory: this.privateOrgsTableDataFactory, skipAddToState: true }); this.subs.add(this.privateRepository.loading.subscribe((val) => { this.privateLoading = val; })); } if (this.useBucketSearch) { this.bucketRepository = this.autoTableFactory.create({ key: 'BUCKET_ORG_SEARCH', columns: [], rowsPerPage: this.rowsPerPage, notifier: this.notifierService, valueComparisonService: this.valueComparisonService, formGroup: this.formGroup, topLevelFilters: this.topLevelFilters, tableDataFactory: this.bucketOrgsTableDataFactory, skipAddToState: true }); this.subs.add(this.bucketRepository.loading.subscribe((val) => { this.bucketLoading = val; })); } this.subs.add(this.publicRepository.loading.subscribe((val) => { this.publicLoading = val; if (!val) { this.handleTabs(); } })); } async openAddOrgModal () { this.hide = true; this.addOrganizationService.setCurrentPage(AddOrgUI.AddOrgModalPages.ID_LOOKUP); this.addOrganizationService.setModalHeader(); this.addOrganizationService.setClientId(this.clientId); this.addOrganizationService.setProcessorType(this.processorType); const submission = await this.addOrgManageModalService.openAddOrgModal( this.processorType ); if (submission && submission.searchResult && !submission.isInvalidOrg) { this.initialSearch = false; const isPrivate = submission.selectionType === AddOrgUI.SelectionType.PRIVATE_ORG; const isNew = submission.selectionType === AddOrgUI.SelectionType.NEW_ORG; if ( isNew && !submission.searchResult.document.id && this.saveNewlyCreatedOrgs ) { await this.orgSelected(submission.searchResult, true); } else { await this.orgSelected(submission.searchResult, isPrivate || isNew); } } else { if (submission && submission.isInvalidOrg) { this.handleOrgNotEligibleToSelect(submission.searchResult); } else { setTimeout(() => { this.hide = false; }, 500); } } } async openAddOrgConfirmModal (adaptedOrg: SearchResult) { this.hide = true; this.addOrganizationService.setSearchResult(adaptedOrg); this.addOrganizationService.setCurrentPage(AddOrgUI.AddOrgModalPages.CONFIRMATION); if ( adaptedOrg.document.country.toLowerCase() === 'us' || adaptedOrg.document.country.toLowerCase() === 'united states' ) { this.addOrganizationService.setConfirmTextToDomesticOrg(); } else { this.addOrganizationService.setConfirmTextToVettingRequiredForNewOrg(); } this.addOrganizationService.setModalHeaderToVettingRequired(); this.addOrganizationService.setClientId(this.clientId); this.addOrganizationService.setProcessorType(this.processorType); this.addOrganizationService.setShowContactForm(true); this.addOrganizationService.setValidation(true); const submission = await this.addOrgManageModalService.openAddOrgModal(this.processorType); if (submission && submission.searchResult) { this.initialSearch = false; const isPrivate = submission.selectionType === AddOrgUI.SelectionType.PRIVATE_ORG; await this.orgSelected(submission.searchResult, isPrivate); } else { setTimeout(() => { this.hide = false; }, 500); } } setRegionsOnCountryChange () { this.locationState.triggerChange('regions'); this.resetRegions = true; if (this.publicRepository) { this.kickOffSearch(); } } async orgSelected ( org: OrgUnion, isPrivate = false, isFromBucket = false ) { this.spinnerService.startSpinner(); const { adaptedOrg, nonprofitDetail } = await this.nonprofitService.getAdaptedOrg(org, isPrivate, this.useBucketSearch); if ( isFromBucket && !this.isNomination && (this.processorType === ProcessingTypes.YourCause) && nonprofitDetail?.eligibleForGivingStatusId !== OrganizationEligibleForGivingStatus.ELIGIBLE ) { this.spinnerService.stopSpinner(); return this.handleOrgNotEligibleToSelect(adaptedOrg); } if ( !this.isNomination && (this.processorType === ProcessingTypes.YourCause) && this.initialSearch ) { if (adaptedOrg.document.eligibleForGivingStatusId !== OrganizationEligibleForGivingStatus.ELIGIBLE) { this.addOrganizationService.setSelectionType(AddOrgUI.SelectionType.UNVETTED_ORG); this.addOrganizationService.setOriginalSearchResponse(org); } if ( 'address' in org && adaptedOrg.document.eligibleForGivingStatusId !== OrganizationEligibleForGivingStatus.ELIGIBLE ) { this.addOrganizationService.setAddOrgAttr('address', org.address); this.addOrganizationService.setShowContactForm(true); this.addOrganizationService.setSelectionType(AddOrgUI.SelectionType.PRIVATE_ORG); } // we need to create a vetting request and update language in modal if (adaptedOrg.document.eligibleForGivingStatusId !== OrganizationEligibleForGivingStatus.ELIGIBLE) { this.spinnerService.stopSpinner(); this.openAddOrgConfirmModal(adaptedOrg); } else { // eligible this.addOrganizationService.setSelectionType(AddOrgUI.SelectionType.VETTED_ORG); this.resetRepos(); this.spinnerService.stopSpinner(); this.closeModal.emit({ selectedOrg: adaptedOrg, isPrivate }); } } else { if (isPrivate) { this.addOrganizationService.setSelectionType(AddOrgUI.SelectionType.NEW_ORG); } this.resetRepos(); this.spinnerService.stopSpinner(); this.closeModal.emit({ selectedOrg: adaptedOrg, isPrivate }); } } async handleOrgNotEligibleToSelect (selectedOrg: SearchResult) { this.hide = true; await this.modalFactory.open( OrgNotEligibleModalComponent, { selectedOrg } ); this.resetRepos(); this.formGroup.get('term').setValue(''); this.hide = false; } onTabChange (newTabType: SelectedSearchType) { this.selectedSearchType = newTabType; } kickOffSearch () { this.topLevelFilters[0].value = this.searchTerm; if (this.resetRegions) { this.formGroup.get('stateProvRegCode').setValue(''); this.resetRegions = false; } if (!this.useBucketSearch) { this.publicRepository.reset(); } if (!!this.clientId && this.privateRepository) { this.privateRepository.reset(); } if (this.useBucketSearch) { this.bucketRepository.reset(); } } handleTabs () { if (this.staticTabs && this.staticTabs.tabs) { const favoritesTab = this.staticTabs.tabs.find((tab) => { return tab.id === ('' + SelectedSearchType.Favorite); }); const organizationsTab = this.staticTabs.tabs.find((tab) => { return tab.id === ('' + SelectedSearchType.Public); }); if (favoritesTab && favoritesTab.active && this.searchTerm) { favoritesTab.active = false; organizationsTab.active = true; this.selectedSearchType = SelectedSearchType.Public; } } } resetRepos () { this.publicRepository.reset(); if (this.useBucketSearch) { this.bucketRepository.reset(); } if (this.privateRepository) { this.privateRepository.reset(); } } onCancel () { if (this.forceOrgSelect) { return; } else { this.resetAndCloseModal(); } } resetAndCloseModal () { this.formGroup.get('term').setValue(''); this.resetRepos(); this.closeModal.emit(); } exitModal () { this.resetAndCloseModal(); this.router.navigate([ this.exitRouterLink ]); } ngOnDestroy () { this.subs.unsubscribe(); } }