diff --git a/.env.example b/.env.example index 6119cc45..e2e99029 100644 --- a/.env.example +++ b/.env.example @@ -27,6 +27,10 @@ NG_USE_Q_AND_AS=true # Enable Search-feature for 'Q & A's (Possible value: `true` or leave empty for `false`) NG_USE_Q_AND_A_SEARCH=true +# (Optional) Server-side Search-feature via API (Requires NG_USE_Q_AND_A_SEARCH to be true!) +SEARCH_API= +SEARCH_API_KEY= + # Enable Feedback-prompt on Sub-Category and Offer pages (Possible value: `true` or leave empty for `false`) NG_USE_FEEDBACK_PROMPT=true diff --git a/src/app/components/feedback-link/feedback-link.component.ts b/src/app/components/feedback-link/feedback-link.component.ts index e0e2d6c8..7a8d68ee 100644 --- a/src/app/components/feedback-link/feedback-link.component.ts +++ b/src/app/components/feedback-link/feedback-link.component.ts @@ -115,6 +115,7 @@ export class FeedbackLinkComponent implements OnChanges, OnInit { LoggingEventCategory.ai, LoggingEvent.FeedbackAnswered, { + name: value, // Use "name"-property for Matomo answer: value, }, ); diff --git a/src/app/components/offer/offer.component.ts b/src/app/components/offer/offer.component.ts index 17718cc9..7e19dae4 100644 --- a/src/app/components/offer/offer.component.ts +++ b/src/app/components/offer/offer.component.ts @@ -1,6 +1,5 @@ import { NgFor, NgIf } from '@angular/common'; import { Component, Input } from '@angular/core'; -import { RouterLink } from '@angular/router'; import { IonImg } from '@ionic/angular/standalone'; import { MarkdownModule } from 'ngx-markdown'; import { @@ -17,7 +16,7 @@ import { formatPhoneNumberAsUrl } from 'src/app/shared/utils'; templateUrl: './offer.component.html', styleUrls: ['./offer.component.scss'], standalone: true, - imports: [MarkdownModule, IonImg, NgIf, NgFor, RouterLink], + imports: [MarkdownModule, IonImg, NgIf, NgFor], }) export class OfferComponent { @Input() diff --git a/src/app/components/q-a-set/q-a-set.component.ts b/src/app/components/q-a-set/q-a-set.component.ts index 4fca863b..7bfb38ef 100644 --- a/src/app/components/q-a-set/q-a-set.component.ts +++ b/src/app/components/q-a-set/q-a-set.component.ts @@ -53,6 +53,7 @@ export class QASetComponent { ? LoggingEvent.QuestionOpen : LoggingEvent.QuestionClose, { + name: slug, // Use "name"-property for Matomo questionSlug: slug, question: question.substring(0, 100), }, diff --git a/src/app/components/search-input/search-input.component.html b/src/app/components/search-input/search-input.component.html index 1d89082c..5e6dfb97 100644 --- a/src/app/components/search-input/search-input.component.html +++ b/src/app/components/search-input/search-input.component.html @@ -2,18 +2,31 @@ role="search" #ngForm (ngSubmit)="doSubmit()" + class="search-input" > - + + + + + × + + {{ actionLabel || 'Search' }} diff --git a/src/app/components/search-input/search-input.component.scss b/src/app/components/search-input/search-input.component.scss index 69f87550..c49b0076 100644 --- a/src/app/components/search-input/search-input.component.scss +++ b/src/app/components/search-input/search-input.component.scss @@ -7,7 +7,7 @@ --search-input--focus: var(--hia_level_text, black); } -form { +.search-input { padding-block-start: 1em; padding-block-end: 1em; @@ -15,11 +15,26 @@ form { display: flex; gap: 0.5em; - input[type='search'] { + .search-input--input { flex: 1 1 80%; + position: relative; } - button[type='submit'] { - flex: 0 0 auto; + .search-input--action { + flex: 0 0 20%; + word-wrap: normal; + } +} + +.input-field--clear, +.input-field--text { + border: 0; + border-radius: 0.25em; + + &:focus { + outline-color: var(--search-input--focus); + outline-offset: 0.125rem; + outline-width: 0.125rem; + outline-style: solid; } } @@ -30,11 +45,18 @@ form { border-radius: 0.25em; padding-block: 0.25em; padding-inline: 0.5em; + width: 100%; +} + +.input-field--clear { + position: absolute; + inset-block-start: 0.25rem; + inset-inline-end: 0.25rem; + display: inline-block; + padding: 0.25rem; + background: transparent; &:focus { - outline-color: var(--search-input--focus); - outline-offset: 2px; - outline-width: 2px; - outline-style: solid; + outline-color: gold; } } diff --git a/src/app/components/search-input/search-input.component.ts b/src/app/components/search-input/search-input.component.ts index 19cac8dc..2c451c66 100644 --- a/src/app/components/search-input/search-input.component.ts +++ b/src/app/components/search-input/search-input.component.ts @@ -1,3 +1,4 @@ +import { NgIf } from '@angular/common'; import type { OnInit } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core'; import { FormsModule } from '@angular/forms'; @@ -7,7 +8,7 @@ import { FormsModule } from '@angular/forms'; templateUrl: './search-input.component.html', styleUrls: ['./search-input.component.scss'], standalone: true, - imports: [FormsModule], + imports: [FormsModule, NgIf], }) export class SearchInputComponent implements OnInit { @Input() @@ -22,6 +23,9 @@ export class SearchInputComponent implements OnInit { @Input() public actionLabel: string; + @Input() + public clearLabel: string; + private previousQuery: string; constructor() {} diff --git a/src/app/pages/search/search.page.html b/src/app/pages/search/search.page.html index 275b6f8d..240152f2 100644 --- a/src/app/pages/search/search.page.html +++ b/src/app/pages/search/search.page.html @@ -1,12 +1,17 @@ - {{ regionData?.labelSearchPageTitle }} + + {{ regionData?.labelSearchPageTitle }} + + What are you looking for? +Search by typing a question or relevant keywords. + @@ -17,12 +22,38 @@ [tabindex]="-1" class="focus--minimal focus--fade-out" > - - {{ regionData?.labelSearchResultsCount }} - {{ searchResults?.length }} + + + + + + + + + + + For more detailed information, check out these FAQs: { + const safeQuery = this.searchService.sanitizeSearchQuery(query); - this.searchResults = this.searchService.query(safeQuery); + if (this.useSearchApi) { + this.loadingSearch = true; + const apiResponse: SearchApiResponse = + await this.fetchApiResults(safeQuery); + + if (apiResponse) { + this.loadingSearch = false; + } + if (apiResponse && apiResponse.references) { + this.searchResults = this.createReferences(apiResponse.references); + } + } else { + this.searchResults = this.searchService.query(safeQuery); + } if (this.searchResults.length > 1) { this.pageMeta.setTitle({ @@ -111,4 +146,48 @@ export default class SearchPageComponent implements OnInit { } } } + + private createReferences( + references: SearchApiResponse['references'], + ): QASet[] { + const results = references.map((reference) => { + const result = this.qaSets.find((qa) => { + return ( + qa.categoryID === Number(reference.category) && + qa.subCategoryID === Number(reference.subcategory) + ); + }); + + return result; + }); + return results; + } + + private async fetchApiResults(query: string): Promise { + const response = await window.fetch(environment.searchApi, { + method: 'POST', + credentials: 'omit', + mode: 'cors', + headers: { + Authorization: environment.searchApiKey, + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + question: query, + googleSheetId: this.configService.getRegionByRegionSlug(this.region) + .sheetId, + locale: this.regionData.localeLanguage, + }), + }); + + if (!response || !response.ok) { + console.warn('Something went wrong:', response); + return { + references: [], + }; + } + + return await response.json(); + } } diff --git a/src/app/referral/referral.page.ts b/src/app/referral/referral.page.ts index d3f32184..c1e5c8ce 100644 --- a/src/app/referral/referral.page.ts +++ b/src/app/referral/referral.page.ts @@ -9,7 +9,7 @@ import { RouterLink, RouterOutlet, } from '@angular/router'; -import { IonContent, IonFooter, IonHeader } from '@ionic/angular/standalone'; +import { IonContent, IonHeader } from '@ionic/angular/standalone'; import { MarkdownComponent } from 'ngx-markdown'; import { filter } from 'rxjs'; import { AppHeaderComponent } from 'src/app/components/header/header.component'; @@ -46,7 +46,6 @@ import { AppPath } from 'src/routes'; RouterOutlet, IonHeader, IonContent, - IonFooter, ], }) export class ReferralPageComponent implements OnInit { diff --git a/src/app/services/spreadsheet.service.ts b/src/app/services/spreadsheet.service.ts index cd106ffe..1c67fde7 100644 --- a/src/app/services/spreadsheet.service.ts +++ b/src/app/services/spreadsheet.service.ts @@ -747,30 +747,17 @@ export class SpreadsheetService { const parentRow = all.find((row) => row.slug === element.parentSlug); // When defined parentRow is missing, treat as a 'normal' question - if (!parentRow) { - this.loggingService.logEvent( - LoggingEventCategory.error, - LoggingEvent.NotFoundParentQuestion, - { - row: element.id, - slug: element.slug, - parentSlug: element.parentSlug, - }, - ); - return element; - } - - // When pointing to itself, treat as a 'normal' question - if (parentRow === element) { - this.loggingService.logEvent( - LoggingEventCategory.error, - LoggingEvent.NotFoundParentQuestionIsSelf, - { - row: element.id, - slug: element.slug, - parentSlug: element.parentSlug, - }, - ); + if (!parentRow || parentRow === element) { + const errorType = + parentRow === element + ? LoggingEvent.NotFoundParentQuestionIsSelf + : LoggingEvent.NotFoundParentQuestion; + this.loggingService.logEvent(LoggingEventCategory.error, errorType, { + name: `row=${element.id};slug=${element.slug};parent=${element.parentSlug}`, + row: element.id, + slug: element.slug, + parentSlug: element.parentSlug, + }); return element; } diff --git a/src/environments/environment.prod.ts.template.js b/src/environments/environment.prod.ts.template.js index 6db7381a..7df128bb 100644 --- a/src/environments/environment.prod.ts.template.js +++ b/src/environments/environment.prod.ts.template.js @@ -13,6 +13,9 @@ export const environment = { useRegionPerLocale: ${process.env.NG_USE_REGION_PER_LOCALE === 'true' || false}, useFeedbackPrompt: ${process.env.NG_USE_FEEDBACK_PROMPT === 'true' || false}, + searchApi: '${process.env.SEARCH_API || ''}', + searchApiKey: '${process.env.SEARCH_API_KEY || ''}', + // Configuration of content localeLanguage: '${process.env.NG_LOCALE_LANGUAGE || ''}', localeDir: '${process.env.NG_LOCALE_DIR || ''}', diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 5f7fbf51..6fc7c532 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -12,6 +12,9 @@ export const environment = { useRegionPerLocale: false, useFeedbackPrompt: true, + searchApi: '', + searchApiKey: '', + // Configuration of content localeLanguage: 'en', localeDir: 'ltr', diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 35e66554..9c2c3c05 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -14,6 +14,9 @@ export const environment = { useRegionPerLocale: true, // Enable language-switcher to switch between Regions useFeedbackPrompt: true, // Enable Feedback-prompt on Sub-Category and Offer pages + searchApi: '', // Server-side Search-feature via API + searchApiKey: '', // Server-side Search-API-Key + // Configuration of content localeLanguage: 'en', // Default language localeDir: 'ltr', // Default language direction: 'ltr' or 'rtl'
Search by typing a question or relevant keywords.
- {{ regionData?.labelSearchResultsCount }} - {{ searchResults?.length }} +
+ For more detailed information, check out these FAQs: