{"version":3,"sources":["webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/car-helper.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/request-errors.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/product-extra-helper.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/warehouse-display-name.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/connection-lost.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/tire-filter-helper.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/latest-request.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/utils.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/partner-cart-helper.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/mobile-cards.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/phone-href.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/sentry-helper.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/price-helper.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/subscriptions.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/search-route.ts","webpack:///src/var/www/virtualhost/client.tk.salome.ee/www/develop/src/utils/helpers/toast-helper.ts"],"names":["getLogger","isValidTireString","tire","test","decodeTireString","CarHelper","match","width","height","diameter","type","toUpperCase","carPlateRegex","standardCarPlateRegex","vinRegex","isAbortError","error","name","networkErrorPattern","isNetworkError","message","isRequestNoise","hasRefundableDeposit","source","fallbackSource","refundableDepositSource","this","getRefundableDepositSource","getRefundableDepositFromSource","getRefundableDeposit","getSelectedRefundableDepositSource","product","selectedStock","warehouse","hasRefundableDepositInfo","findMatchingStockWithRefundableDeposit","hasTkItem","id","Number","isFinite","isPartnerOnly","getExternalWarehouseLabel","trim","getMinimumQuantity","stockItem","minimumQuantity","toNumber","getValue","Math","floor","isValidMinimumQuantity","quantity","copyMinimumQuantityIfMissing","target","isRecord","existing","refundableDeposit","refundableDepositKey","legacyRefundableDepositKeys","key","value","copyRefundableDepositIfMissing","preserveExternalStockRefundableDeposit","newStock","oldStock","length","forEach","oldStockItem","find","oldItem","selectedWarehouse","selectedWarehouseId","selectedIsExternal","isExternal","stock","externalStock","normalizedKey","normalizeKey","matchingKey","Object","keys","sourceKey","undefined","replace","toLowerCase","numberValue","String","warehouseDisplayName","warehouseId","lists","loaded","list","connectionLost","cooldownMs","now","Date","lastShownAt","Infinity","record","seasonEntries","values","present","Set","map","rank","index","seasonOrder","indexOf","rows","Array","from","sort","a","b","localeCompare","has","push","nested","child","seasonChoice","selected","clicked","nowChecked","available","set","add","delete","filter","splitDisabled","response","filters","disabled","startsWith","disabledPrefix","substring","isArray","v","isOptionDisabled","chosen","text","some","includes","isOptionLocked","pending","pendingGroup","changedGroup","before","after","norm","JSON","stringify","changed","translateKey","categoryId","i18n","path","translated","tr","translateValue","codedKeys","translateValueList","split","part","join","translateInfoPair","pair","infoHref","sequence","controller","invalidate","abort","start","currentKey","send","Promise","mine","AbortController","signal","result","aborted","selectionKey","vehicleId","groups","group","log","getResponseErrorText","getErrorMessage","reportNetworkErrors","warn","status","loadSortable","$","bootstrapSortable","children","remove","append","on","each","icon","removeClass","hasClass","addClass","checkPasswordValidity","np","npa","setCustomValidity","showSuccessToast","Utils","showToast","showInfoToast","handleRequestNoise","showErrorToast","selector","attemptsLeft","document","querySelector","toast","getOrCreateInstance","show","setTimeout","unique","array","i","removeEmpty","entry","isArrayEmpty","isEmpty","isObjectEmpty","object","isPositive","round","num","chunkify","n","balanced","size","len","out","slice","ceil","setLocale","eventAggregator","locale","publish","localeChanged","_","location","reload","getLocaleClass","encode","string","optionalParameter","encodeURIComponent","encodeURI","showGallery","galleryImage","escapeRegex","nameMatches","item","deepFilterItems","items","filtered","hasChildren","newItem","assign","getFilterQuery","query","entries","isInStock","parseInt","partnerCartNotice","outcome","attempted","failed","kind","codes","failedCodes","labelTableCells","table","header","tHead","labels","cells","cell","textContent","labelled","tBodies","body","row","label","removeAttribute","getAttribute","setAttribute","toPhoneHref","trimmed","dialable","sentryEnvironments","sentryEnvironment","stamped","loadFailedMessagePattern","sentryBeforeSendFilter","event","hint","logentry","eventMessage","formatted","exception","requestUrl","request","url","responseStatusCode","contexts","status_code","debug","stacktrace","frames","frame","filename","loadFailedException","exceptionType","isNonActionableLoadFailed","event_id","level","logger","keyword","formatProductsPrices","products","settings","displayWholesale","formatPrices","prices","formatStockPrices","PriceHelper","displayPrice","getDisplayPrice","price","translatePriceName","displayAmount","getPriceVatFromSettings","Wholesale","Retail","Discount","AmountDiscount","TimeDiscount","UserGroupTimeDiscount","DiscountGroupUserGroupTimeDiscount","AmountDiscountList","amount","priceSetting","None","priceType","getPrice","withoutVat","vatIncluded","vatIncludedInRetail","vatIncludedInWholesale","withVat","subscriptions","disposeAll","dispose","needsDoubleEncoding","encodeSearchRouteQuery","decodeSearchRouteQuery","raw","decodeURIComponent","shouldNavigateForQuery","currentRoute","routeQuery","routeType","canReuseSearch","sameParameters","hasResult","requestInFlight","SEARCH_TIMEOUT_MS","fillAndShowToast","doc","element","innerHTML","classList","confirmPending","adding","requestConfirm","takeConfirm","cancelConfirm","begin","end"],"mappings":"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","file":"app~b799487f.8762972835aa3d495e3f.chunk.js","sourcesContent":["import { LogManager } from \"aurelia-framework\";\n\nexport const log = LogManager.getLogger(\"app.utils.helpers.car\");\n\nexport class CarHelper {\n    // store variables to be used in other components\n    public static carPlateRegex =\n        /^([\\d]{4}[A-Z]{2}|[\\d]{3}[A-Z]{3}|[\\d]{3}[A-Z]{1}|[\\d]{2}[A-Z]{2,3}|[A-Z]{1,8}[\\d]{1,8})$/i;\n    public static standardCarPlateRegex = /^([\\d]{3}[A-Z]{3})$/i;\n    public static vinRegex = /^([\\dA-Z]{17})$/i;\n\n  public static isValidTireString(tire: string): boolean {\n    // Optional trailing C = cargo/van (SAL-250 type code).\n    // Bare C only: end of string or non-alnum after; not CVI7 / other letter sequences.\n    return /^.{0,3}(\\d{3})\\/?(\\d{2})?\\D{0,3}(\\d{2})(?:C(?![A-Za-z0-9]))?(?:\\D|$)/i.test(tire);\n  }\n\n  /**\n   * Decode width/height/diameter and optional type \"C\" (cargo) from a tyre size string.\n   * Returns [width, height, diameter] or [width, height, diameter, \"C\"].\n   */\n  public static decodeTireString(tire: string): string[] {\n    if (!CarHelper.isValidTireString(tire)) return [];\n\n    const match = tire.match(/^.{0,3}(\\d{3})\\/?(\\d{2})?\\D{0,3}(\\d{2})(C(?![A-Za-z0-9]))?/i)!;\n\n    const width = match[1];\n    const height = match[2];\n    const diameter = match[3];\n    const type = match[4] ? match[4].toUpperCase() : '';\n\n    return type ? [width, height, diameter, type] : [width, height, diameter];\n  }\n}\n","/**\n * SAL-322: errors a fetch raises when it never got a response. They say nothing about the app\n * (the user typed on, navigated away, or lost the network), so they are not reported to Sentry.\n */\n\n/** The request was aborted with an AbortController (a newer search replaced it). */\nexport function isAbortError(error: unknown): boolean {\n  return !!error && typeof error === \"object\" && (error as { name?: string }).name === \"AbortError\";\n}\n\n// Chrome / Edge, Firefox, Safari. Sentry appends the host in brackets: \"Failed to fetch (api.tk.salome.ee)\".\nconst networkErrorPattern =\n  /^(?:Failed to fetch|NetworkError when attempting to fetch resource\\.?|Load failed|Network request failed)(?: \\(.+\\))?$/;\n\n/** The browser could not reach the API at all: a TypeError, not an HTTP error status. */\nexport function isNetworkError(error: unknown): boolean {\n  if (!error || typeof error !== \"object\") return false;\n  const { name, message } = error as { name?: string; message?: string };\n  return name === \"TypeError\" && typeof message === \"string\" && networkErrorPattern.test(message);\n}\n\n/** Aborted or never reached the API. */\nexport function isRequestNoise(error: unknown): boolean {\n  return isAbortError(error) || isNetworkError(error);\n}\n","import { IProduct } from \"domain/Product/IProduct\";\nimport { IStockItem } from \"domain/Product/IStockItem\";\nimport { IWarehouse } from \"domain/Client/IWarehouse\";\n\ntype RefundableDepositSource = (IProduct | IStockItem) & Record<string, unknown>;\n\nexport class ProductExtraHelper {\n  private static readonly refundableDepositKey = \"refundableDeposit\";\n  private static readonly legacyRefundableDepositKeys = [\"refundable_deposit\"];\n\n  static hasRefundableDeposit(\n    source?: IProduct | IStockItem | null,\n    fallbackSource?: IProduct | IStockItem | null,\n  ): boolean {\n    const refundableDepositSource = this.getRefundableDepositSource(source, fallbackSource);\n    if (!refundableDepositSource) return false;\n\n    return this.getRefundableDepositFromSource(refundableDepositSource) != null;\n  }\n\n  static getRefundableDeposit(\n    source?: IProduct | IStockItem | null,\n    fallbackSource?: IProduct | IStockItem | null,\n  ): number | null {\n    const refundableDepositSource = this.getRefundableDepositSource(source, fallbackSource);\n    if (!refundableDepositSource) return null;\n\n    return this.getRefundableDepositFromSource(refundableDepositSource);\n  }\n\n  static getSelectedRefundableDepositSource(\n    product?: IProduct | null,\n    selectedStock?: IStockItem | null,\n  ): IProduct | IStockItem | null {\n    if (!selectedStock?.warehouse) return null;\n    if (this.hasRefundableDepositInfo(selectedStock)) return selectedStock;\n\n    return this.findMatchingStockWithRefundableDeposit(product, selectedStock);\n  }\n\n  /**\n   * SAL-270: partner-only search rows (unmatched partner warehouse rows) have no TK item:\n   * the API sends id null (older Bridge: 0). Anything keyed by product id (external stock,\n   * images, info, description, documents, OE numbers, cars, cart) must be skipped for them.\n   */\n  static hasTkItem(product?: Pick<IProduct, \"id\"> | null): boolean {\n    const id = Number(product?.id);\n    return Number.isFinite(id) && id > 0;\n  }\n\n  static isPartnerOnly(product?: Pick<IProduct, \"id\" | \"foundBy\"> | null): boolean {\n    return !!product && !this.hasTkItem(product);\n  }\n\n  static getExternalWarehouseLabel(warehouse?: IWarehouse | null): string | null {\n    const name = warehouse?.name?.trim();\n    return name ? name : null;\n  }\n\n  static getMinimumQuantity(stockItem?: IStockItem | null): number {\n    if (!stockItem) return 1;\n    const minimumQuantity = this.toNumber(\n      this.getValue(stockItem as RefundableDepositSource, \"minimumQuantity\"),\n    );\n    return minimumQuantity != null && minimumQuantity > 1 ? Math.floor(minimumQuantity) : 1;\n  }\n\n  static isValidMinimumQuantity(quantity: number, stockItem?: IStockItem | null): boolean {\n    if (!Number.isFinite(quantity) || quantity < 1) return false;\n    const minimumQuantity = this.getMinimumQuantity(stockItem);\n    return quantity >= minimumQuantity && quantity % minimumQuantity === 0;\n  }\n\n  static copyMinimumQuantityIfMissing(target?: unknown, source?: unknown): void {\n    if (!this.isRecord(target) || !this.isRecord(source)) return;\n    const existing = this.toNumber(\n      this.getValue(target as RefundableDepositSource, \"minimumQuantity\"),\n    );\n    if (existing != null && existing > 1) return;\n\n    const minimumQuantity = this.toNumber(\n      this.getValue(source as RefundableDepositSource, \"minimumQuantity\"),\n    );\n    if (minimumQuantity != null && minimumQuantity > 1) {\n      target[\"minimumQuantity\"] = Math.floor(minimumQuantity);\n    }\n  }\n\n  private static getRefundableDepositSource(\n    source?: IProduct | IStockItem | null,\n    fallbackSource?: IProduct | IStockItem | null,\n  ): RefundableDepositSource | null {\n    if (this.hasRefundableDepositInfo(source)) return source as RefundableDepositSource;\n    if (this.hasRefundableDepositInfo(fallbackSource)) return fallbackSource as RefundableDepositSource;\n\n    return null;\n  }\n\n  private static hasRefundableDepositInfo(source?: IProduct | IStockItem | null): boolean {\n    if (!source) return false;\n\n    return this.getRefundableDepositFromSource(source as RefundableDepositSource) != null;\n  }\n\n  private static getRefundableDepositFromSource(source: RefundableDepositSource): number | null {\n    const refundableDeposit = this.toNumber(this.getValue(source, this.refundableDepositKey));\n    if (refundableDeposit != null && refundableDeposit > 0) return refundableDeposit;\n\n    for (const key of this.legacyRefundableDepositKeys) {\n      const value = this.toNumber(this.getValue(source, key));\n      if (value != null && value > 0) return value;\n    }\n\n    return null;\n  }\n\n  static copyRefundableDepositIfMissing(target?: unknown, source?: unknown): void {\n    if (!this.isRecord(target) || !this.isRecord(source)) return;\n    if (this.hasRefundableDepositInfo(target as RefundableDepositSource)) return;\n\n    const refundableDeposit = this.getRefundableDepositFromSource(source as RefundableDepositSource);\n    if (refundableDeposit != null) target[this.refundableDepositKey] = refundableDeposit;\n  }\n\n  static preserveExternalStockRefundableDeposit(\n    newStock: IStockItem[],\n    oldStock: IStockItem[],\n  ): void {\n    if (!newStock?.length || !oldStock?.length) return;\n\n    newStock.forEach(stockItem => {\n      const oldStockItem = oldStock.find(\n        oldItem => oldItem.warehouse?.id === stockItem.warehouse?.id,\n      );\n\n      this.copyRefundableDepositIfMissing(stockItem, oldStockItem);\n      this.copyMinimumQuantityIfMissing(stockItem, oldStockItem);\n    });\n  }\n\n  private static findMatchingStockWithRefundableDeposit(\n    product?: IProduct | null,\n    selectedStock?: IStockItem | null,\n  ): IStockItem | null {\n    const selectedWarehouse = selectedStock?.warehouse;\n    const selectedWarehouseId = selectedWarehouse?.id;\n    if (selectedWarehouseId == null || !product) return null;\n\n    const selectedIsExternal = !!selectedWarehouse?.isExternal;\n    const candidates = [...(product.stock ?? []), ...(product.externalStock ?? [])];\n    return (\n      candidates.find(\n        stockItem =>\n          stockItem.warehouse?.id === selectedWarehouseId &&\n          !!stockItem.warehouse?.isExternal === selectedIsExternal &&\n          this.hasRefundableDepositInfo(stockItem),\n      ) ?? null\n    );\n  }\n\n  private static getValue(source: RefundableDepositSource, key: string): unknown {\n    if (key in source) return source[key];\n\n    const normalizedKey = this.normalizeKey(key);\n    const matchingKey = Object.keys(source).find(\n      sourceKey => this.normalizeKey(sourceKey) === normalizedKey,\n    );\n    return matchingKey ? source[matchingKey] : undefined;\n  }\n\n  private static normalizeKey(key: string): string {\n    return key.replace(/[^a-z0-9]/gi, \"\").toLowerCase();\n  }\n\n  private static toNumber(value: unknown): number | null {\n    if (value == null || value === \"\") return null;\n    if (typeof value !== \"number\" && typeof value !== \"string\") return null;\n\n    const numberValue =\n      typeof value === \"number\" ? value : Number(String(value).trim().replace(\",\", \".\"));\n\n    return Number.isFinite(numberValue) ? numberValue : null;\n  }\n\n  private static isRecord(value: unknown): value is Record<string, unknown> {\n    return typeof value === \"object\" && value !== null;\n  }\n}\n","export interface WarehouseName {\n  id: number;\n  name: string;\n}\n\n// Undefined while every list is still missing, so the label stays blank instead of\n// flashing \"ID: n\" until a fetch returns. Once a list has loaded and none contain\n// the id, the id itself is the label.\nexport function warehouseDisplayName(\n  warehouseId: number,\n  lists: ReadonlyArray<ReadonlyArray<WarehouseName> | undefined | null>,\n): string | undefined {\n  let loaded = false;\n\n  for (const list of lists) {\n    if (list == undefined) continue;\n    loaded = true;\n\n    for (const warehouse of list) {\n      if (warehouse.id == warehouseId && warehouse.name) return warehouse.name;\n    }\n  }\n\n  if (!loaded) return undefined;\n  return `ID: ${warehouseId}`;\n}\n","import { isAbortError, isNetworkError } from \"utils/helpers/request-errors\";\n\n/**\n * SAL-297: catches that load product details, the cart total and the category menu stay silent\n * on request noise (Safari reports dropped fetches as TypeError 'Load failed'). Those requests\n * carry no AbortSignal, so one shared 'connection lost' toast is shown instead on the first\n * non-aborted network failure, then nothing again for `cooldownMs` (deduped).\n * Aborts and HTTP errors never count. (Nuhvel 2026-10-20 16:52: threshold 1.)\n */\nexport class ConnectionLostDetector {\n  private lastShownAt = -Infinity;\n\n  constructor(\n    private readonly cooldownMs = 30000,\n    private readonly now = () => Date.now(),\n  ) {}\n\n  /** Records a failed request; true when the 'connection lost' toast should be shown now. */\n  record(error: unknown): boolean {\n    if (isAbortError(error) || !isNetworkError(error)) return false;\n\n    const now = this.now();\n    if (now - this.lastShownAt < this.cooldownMs) return false;\n\n    this.lastShownAt = now;\n    return true;\n  }\n}\n\nexport const connectionLost = new ConnectionLostDetector();\n","import { I18N } from 'aurelia-i18n';\n\n/**\n * SAL-250 / SAL-270: translate category-10 tyre filter keys and coded values.\n * DB / API store codes only (P/C/T, S/WL/WS, MS/3PMSF/…).\n */\nexport class TireFilterHelper {\n  static readonly codedKeys = new Set(['type', 'season', 'marks']);\n\n  /** Display order. WL and WS render under W when W itself is present. */\n  private static readonly seasonOrder = ['S', 'A', 'W', 'WL', 'WS'];\n\n  /**\n   * Season dropdown rows. Naast (WS) and Lamell (WL) sit under Talv (W).\n   * Stored codes stay the same. Each checked code is its own season.\n   */\n  static seasonEntries(values: string[]): { value: string; nested: boolean }[] {\n    const present = new Set((values ?? []).map(value => String(value)));\n    const rank = (value: string) => {\n      const index = this.seasonOrder.indexOf(value);\n      return index === -1 ? this.seasonOrder.length : index;\n    };\n    const sorted = Array.from(present).sort(\n      (a, b) => rank(a) - rank(b) || a.localeCompare(b),\n    );\n    const rows: { value: string; nested: boolean }[] = [];\n    for (const value of sorted) {\n      if ((value === 'WL' || value === 'WS') && present.has('W')) {\n        continue;\n      }\n      rows.push({ value, nested: false });\n      if (value === 'W') {\n        for (const child of ['WL', 'WS']) {\n          if (present.has(child)) {\n            rows.push({ value: child, nested: true });\n          }\n        }\n      }\n    }\n    return rows;\n  }\n\n  /**\n   * Talv checks Lamell and Naast. Clearing Talv clears both.\n   * Clearing Lamell or Naast leaves Talv checked.\n   * A child checked on its own leaves Talv unchecked.\n   */\n  static seasonChoice(\n    selected: string[],\n    clicked: string,\n    nowChecked: boolean,\n    available: string[],\n  ): string[] {\n    const present = new Set((available ?? []).map(value => String(value)));\n    const set = new Set((selected ?? []).map(value => String(value)));\n    if (nowChecked) {\n      set.add(clicked);\n    } else {\n      set.delete(clicked);\n    }\n    if (clicked === 'W') {\n      for (const child of ['WL', 'WS']) {\n        if (nowChecked && present.has(child)) {\n          set.add(child);\n        } else if (!nowChecked) {\n          set.delete(child);\n        }\n      }\n    }\n    return this.seasonOrder.filter(value => set.has(value) && present.has(value));\n  }\n\n  /** SAL-313: Bridge marks a group's unavailable values under \"disabled:<group>\". */\n  static readonly disabledPrefix = 'disabled:';\n\n  /**\n   * Split the filter response into the option lists and the disabled values per group.\n   * A response without \"disabled:\" keys (no selection sent) has nothing disabled.\n   */\n  static splitDisabled(response: any): { filters: any; disabled: Record<string, string[]> } {\n    const filters: any = {};\n    const disabled: Record<string, string[]> = {};\n    for (const key of Object.keys(response ?? {})) {\n      const values = response[key];\n      if (key.startsWith(this.disabledPrefix)) {\n        disabled[key.substring(this.disabledPrefix.length)] = Array.isArray(values) ? values.map(v => String(v)) : [];\n      } else {\n        filters[key] = values;\n      }\n    }\n    return { filters, disabled };\n  }\n\n  /** Greyed out when Bridge listed it as disabled and it is not chosen (a chosen value can always be unchecked). */\n  static isOptionDisabled(\n    disabled: Record<string, string[]> | null | undefined,\n    chosen: Record<string, any[]> | null | undefined,\n    key: string,\n    value: any,\n  ): boolean {\n    const text = String(value);\n    if ((chosen?.[key] ?? []).some(v => String(v) === text)) {\n      return false;\n    }\n    return (disabled?.[key] ?? []).includes(text);\n  }\n\n  /**\n   * SAL-313 r2: while the Filters request for the newest selection is in flight, unchosen values\n   * in the other groups are locked (dimmed, not clickable) because the greying is not known yet.\n   * The group just changed stays usable (its own values grey only by the other groups).\n   * pendingGroup null while pending means the changed group is unknown: lock every group.\n   */\n  static isOptionLocked(\n    pending: boolean,\n    pendingGroup: string | null | undefined,\n    chosen: Record<string, any[]> | null | undefined,\n    key: string,\n    value: any,\n  ): boolean {\n    if (!pending || (pendingGroup && key === pendingGroup)) {\n      return false;\n    }\n    const text = String(value);\n    return !(chosen?.[key] ?? []).some(v => String(v) === text);\n  }\n\n  /** SAL-313 r2: the one group whose values differ between two selections, or null (none or several). */\n  static changedGroup(\n    before: Record<string, any[]> | null | undefined,\n    after: Record<string, any[]> | null | undefined,\n  ): string | null {\n    const norm = (list: any[] | undefined) => JSON.stringify((list ?? []).map(v => String(v)).sort());\n    const keys = new Set([...Object.keys(before ?? {}), ...Object.keys(after ?? {})]);\n    const changed = Array.from(keys).filter(key => norm(before?.[key]) !== norm(after?.[key]));\n    return changed.length === 1 ? changed[0] : null;\n  }\n\n  static translateKey(categoryId: number | string, key: string, i18n: I18N): string {\n    const path = `pages.category.filters.${categoryId}.${key}`;\n    const translated = i18n.tr(path);\n    return translated === path ? key : translated;\n  }\n\n  static translateValue(\n    categoryId: number | string,\n    key: string,\n    value: string,\n    i18n: I18N,\n  ): string {\n    if (!this.codedKeys.has(key)) {\n      return value;\n    }\n    const path = `pages.category.filters.${categoryId}.values.${key}.${value}`;\n    const translated = i18n.tr(path);\n    return translated === path ? value : translated;\n  }\n\n  /** Translate a comma-separated marks list or a single coded value. */\n  static translateValueList(\n    categoryId: number | string,\n    key: string,\n    value: string,\n    i18n: I18N,\n  ): string {\n    if (key === 'marks' && value.includes(',')) {\n      return value\n        .split(',')\n        .map(part => this.translateValue(categoryId, key, part.trim(), i18n))\n        .join(', ');\n    }\n    return this.translateValue(categoryId, key, value, i18n);\n  }\n\n  /**\n   * Translate product.info [key, value] when key is a known tyre attribute.\n   * Falls back to the raw key/value when no translation exists.\n   */\n  static translateInfoPair(\n    pair: [string, string],\n    i18n: I18N,\n    categoryId: number | string = 10,\n  ): [string, string] {\n    const [key, value] = pair;\n    const label = this.translateKey(categoryId, key, i18n);\n    const display = this.translateValueList(categoryId, key, value, i18n);\n    return [label, display];\n  }\n\n  /** EU tyre label (eu_label_url) is a link. Anything else stays plain text. */\n  static infoHref(pair: [string, string]): string | null {\n    if (!pair || pair[0] !== 'eu_label_url') {\n      return null;\n    }\n    const value = (pair[1] ?? '').trim();\n    return /^https?:\\/\\//i.test(value) ? value : null;\n  }\n}\n","import { isAbortError } from \"utils/helpers/request-errors\";\n/**\n * SAL-313 QA (a): only the response to the latest request may be applied.\n * Every start() and invalidate() aborts the request in flight and bumps a sequence number.\n * A response is applied only if its sequence is still current and its key (the serialized\n * selection, category and vehicle it was asked for) equals the current key.\n */\nexport class LatestRequest<T> {\n  private sequence = 0;\n  private controller: AbortController | null = null;\n\n  /** Drop whatever is in flight (filter change pending, clear, category or vehicle change). */\n  invalidate(): void {\n    this.sequence++;\n    if (this.controller) {\n      this.controller.abort();\n      this.controller = null;\n    }\n  }\n\n  /**\n   * Start a request. Resolves with the result, or null when it was superseded, aborted or its\n   * key no longer matches currentKey() at the time it arrives. Errors of the latest request reject.\n   */\n  async start(key: string, currentKey: () => string, send: (signal: AbortSignal | undefined) => Promise<T>): Promise<T | null> {\n    this.invalidate();\n    const mine = this.sequence;\n    const controller = typeof AbortController !== \"undefined\" ? new AbortController() : null;\n    this.controller = controller;\n    try {\n      const result = await send(controller?.signal);\n      if (mine !== this.sequence || controller?.signal.aborted || key !== currentKey()) return null;\n      return result;\n    } catch (error) {\n      if (mine !== this.sequence || controller?.signal.aborted || isAbortError(error)) return null;\n      throw error;\n    } finally {\n      if (this.controller === controller) this.controller = null;\n    }\n  }\n}\n\n/** Stable key of a selection: groups and values sorted, empty groups dropped. */\nexport function selectionKey(categoryId: unknown, vehicleId: unknown, chosen: Record<string, unknown[]> | null | undefined): string {\n  const groups = Object.keys(chosen ?? {})\n    .filter(group => Array.isArray(chosen![group]) && chosen![group].length > 0)\n    .sort()\n    .map(group => [group, chosen![group].map(v => String(v)).sort()]);\n  return JSON.stringify([String(categoryId ?? \"\"), String(vehicleId ?? \"\"), groups]);\n}\n","import { fillAndShowToast } from \"utils/helpers/toast-helper\";\nimport { errorReason, ERROR_BODY_TIMEOUT_MS } from \"pages/cart/order-submit\";\n/* eslint-disable @typescript-eslint/no-var-requires */\nimport { TKEvent } from \"utils/enums/TKEvent\";\nimport { EventAggregator } from \"aurelia-event-aggregator\";\nimport { LogManager } from \"aurelia-framework\";\nimport { Logger } from \"aurelia-logging\";\nimport { I18N } from \"aurelia-i18n\";\nimport { ServiceResponseError } from \"utils/classes/error/ServiceResponseError\";\nimport { IProduct } from \"domain/Product/IProduct\";\nimport { Modal, Toast } from \"bootstrap\";\nimport { IParentItem } from \"domain/Base/IParentItem\";\nimport { IStockItem } from \"domain/Product/IStockItem\";\nimport { isRequestNoise } from \"utils/helpers/request-errors\";\nimport { connectionLost } from \"utils/helpers/connection-lost\";\n\nexport const log = LogManager.getLogger(\"app.utils.helpers.utils\");\n\nexport class Utils {\n  /** QA 5b: the server's error text of a failed request (null when there is none or it cannot be read). */\n  public static async getResponseErrorText(error: unknown): Promise<string | null> {\n    if (!(error instanceof ServiceResponseError)) return null;\n    return errorReason(error, ERROR_BODY_TIMEOUT_MS);\n  }\n\n  /**\n   * @param reportNetworkErrors false where a lost or aborted request is expected (search): it is\n   * logged as a warning, which console capture does not send to Sentry (SAL-322).\n   */\n  public static getErrorMessage(error: Error, i18n: I18N, reportNetworkErrors = true): string {\n    if (!(error instanceof ServiceResponseError)) {\n      if (!reportNetworkErrors && isRequestNoise(error)) log.warn(error.name, error.message);\n      else log.error(error.name, error);\n      return i18n.tr(\"general.errors.unknown\", { error: error.name });\n    }\n\n    const status = error.response.status;\n    if (status == 401) return i18n.tr(\"general.errors.session-expired\");\n\n    return i18n.tr(\"general.errors.backend\", { status: status });\n  }\n\n  public static loadSortable() {\n    const $ = require(\"jquery\");\n    require(\"moment\");\n    require(\"bootstrap-sortable\");\n    require(\"/node_modules/bootstrap-icons/font/bootstrap-icons.css\");\n\n    $(() => {\n      ($ as any).bootstrapSortable();\n      const bsIconsClasses = \".bi-filter, .bi-sort-down, .bi-sort-up\";\n\n      // Automatically add the sorting icons (and remove old ones)\n      $('th[data-defaultsign=\"nospan\"]').children(bsIconsClasses).remove();\n      $('th[data-defaultsign=\"nospan\"]').append(' <i class=\"bi-filter\"></i>');\n\n      $(\".sortable\").on(\"sorted\", function () {\n        // Update the sort icons\n        $(this)\n          .find('th[data-defaultsign=\"nospan\"]')\n          .each(function () {\n            const icon = $(this).find(bsIconsClasses);\n            icon.removeClass(\"bi-filter bi-sort-down bi-sort-up\");\n\n            if ($(this).hasClass(\"up\")) {\n              icon.addClass(\"bi-sort-up\");\n            } else if ($(this).hasClass(\"down\")) {\n              icon.addClass(\"bi-sort-down\");\n            } else if ($(this).hasClass(\"nosort\")) {\n              icon.addClass(\"bi-filter\");\n            }\n          });\n      });\n    });\n  }\n\n  public static checkPasswordValidity(i18n: I18N) {\n    const $ = require(\"jquery\");\n    $(() => {\n      const np = $(\"#new-password\")[0] as HTMLInputElement;\n      const npa = $(\"#new-password-again\")[0] as HTMLInputElement;\n\n      const capitalcheck = /^(?=.*[A-Z]).*$/;\n      const lowercasecheck = /^(?=.*[a-z])(?!.*\\s).*$/;\n      const numbercheck = /^(?=.*\\d)(?!.*\\s).*$/;\n\n      if (np.value.length < 8) {\n        np.setCustomValidity(i18n.tr(\"pages.user.settings.sections.password.errors.too-short\"));\n        return;\n      }\n      if (!lowercasecheck.test(np.value)) {\n        np.setCustomValidity(i18n.tr(\"pages.user.settings.sections.password.errors.no-lowercase\"));\n        return;\n      }\n      if (!numbercheck.test(np.value)) {\n        np.setCustomValidity(i18n.tr(\"pages.user.settings.sections.password.errors.no-number\"));\n        return;\n      }\n      if (!capitalcheck.test(np.value)) {\n        np.setCustomValidity(i18n.tr(\"pages.user.settings.sections.password.errors.no-uppercase\"));\n        return;\n      }\n      if (npa.value != np.value) {\n        np.setCustomValidity(\"\");\n        npa.setCustomValidity(i18n.tr(\"pages.user.settings.sections.password.errors.no-match\"));\n        return;\n      }\n\n      np.setCustomValidity(\"\");\n      npa.setCustomValidity(\"\");\n    });\n  }\n\n  public static showSuccessToast(message: string) {\n    Utils.showToast(\".success-toast\", message);\n  }\n\n  public static showInfoToast(message: string) {\n    Utils.showToast(\".info-toast\", message);\n  }\n\n  /**\n   * SAL-297: a request that never got a response (aborted, or Safari 'Load failed') shows no\n   * toast of its own. Repeated network failures show one shared 'connection lost' toast.\n   */\n  public static handleRequestNoise(log: Logger, error: Error, i18n: I18N) {\n    log.warn(error.name, error.message);\n    if (connectionLost.record(error))\n      Utils.showErrorToast(log, i18n.tr(\"general.errors.connection-lost\"));\n  }\n\n  public static showErrorToast(log: Logger, message: string) {\n    if (!message) return;\n\n    Utils.showToast(\".error-toast\", message);\n  }\n\n  /**\n   * The toasts live in main-router.html. A page that shows one from activate() on the first\n   * load runs before that view is in the DOM, and new Toast(null).show() threw inside the\n   * router pipeline: \"Cannot read properties of null (reading 'defaultPrevented')\", TKCLIENT-1JP.\n   * Wait for the element instead, and give up quietly if it never comes.\n   */\n  public static showToast(selector: string, message: string, attemptsLeft = 20) {\n    const element = document.querySelector(selector);\n    if (!element) {\n      if (attemptsLeft > 0) setTimeout(() => Utils.showToast(selector, message, attemptsLeft - 1), 50);\n      else log.warn(`Toast ${selector} is not in the page; message not shown:`, message);\n      return;\n    }\n\n    // SAL-283 QA: plain DOM fill, one Bootstrap instance per element, shown by class if Bootstrap throws.\n    fillAndShowToast(document, selector, message, toast => Toast.getOrCreateInstance(toast).show());\n  }\n\n  // (c) https://www.codegrepper.com/code-examples/delphi/typescript+array+duplicate+remove\n  public static unique(array: any[]): any[] {\n    return array.filter((v, i) => array.indexOf(v) === i);\n  }\n\n  // (c) https://stackoverflow.com/a/35476997\n  public static removeEmpty(array: string[]): string[] {\n    return array.filter(entry => entry.trim() != \"\");\n  }\n\n  public static isArrayEmpty(array: any): boolean {\n    return this.isEmpty(array) || array.length == 0;\n  }\n  public static isObjectEmpty(object: any): boolean {\n    return this.isEmpty(object) || Object.keys(object).length == 0;\n  }\n  public static isEmpty(object: any): boolean {\n    return object === undefined || object === null;\n  }\n  public static isPositive(object: string | number | null | undefined): boolean {\n    return !this.isEmpty(object) && +(object as string | number) > 0;\n  }\n\n  public static round(num: number, i = 2): number {\n    return +(Math.round(`${num}e+${i}` as any) + `e-${i}`);\n  }\n\n  // (c) https://stackoverflow.com/a/8189268\n  public static chunkify(array: any[], n: number, balanced: boolean): any[] {\n    if (n < 2) return [array];\n\n    const len = array.length;\n    const out = [];\n    let i = 0;\n    let size;\n\n    if (len % n === 0) {\n      size = Math.floor(len / n);\n      while (i < len) {\n        out.push(array.slice(i, (i += size)));\n      }\n    } else if (balanced) {\n      while (i < len) {\n        size = Math.ceil((len - i) / n--);\n        out.push(array.slice(i, (i += size)));\n      }\n    } else {\n      n--;\n      size = Math.floor(len / n);\n\n      if (len % size === 0) size--;\n\n      while (i < size * n) {\n        out.push(array.slice(i, (i += size)));\n      }\n\n      out.push(array.slice(size * n));\n    }\n\n    return out;\n  }\n\n  public static setLocale(eventAggregator: EventAggregator, i18n: I18N, locale: string) {\n    try {\n      i18n.setLocale(locale);\n      eventAggregator.publish(TKEvent.localeChanged, locale);\n    } catch (_) {\n      location.reload();\n    }\n  }\n\n  public static getLocaleClass(locale: string): string {\n    switch (locale) {\n      case \"et_EE\":\n        return \"flag-icon-ee\";\n      case \"en_GB\":\n        return \"flag-icon-gb\";\n      case \"ru_RU\":\n        return \"flag-icon-ru\";\n      case \"fi_FI\":\n        return \"flag-icon-fi\";\n      default:\n        return \"\";\n    }\n  }\n\n  // In some situations special characters in search bars may not display properly:\n  // https://github.com/aurelia/router/issues/631#issuecomment-761773471\n  public static encode(string: any, optionalParameter = false) {\n    if (optionalParameter) return encodeURIComponent(string); // Only encode optional parameters once, thanks Aurelia\n    return encodeURIComponent(encodeURI(string));\n  }\n\n  public static showGallery(product: IProduct) {\n    const $ = require(\"jquery\");\n    require(\"bootstrap\");\n\n    $(() => {\n      // If there are no images to display\n      if ($(`[data-gallery=\"${product.id}\"]`).children(\".carousel-inner\").children().length == 0)\n        return;\n\n      const galleryImage = document.querySelector(`[data-gallery-wrapper=\"${product.id}\"]`);\n      if (galleryImage) new Modal(galleryImage).show();\n    });\n  }\n\n  public static escapeRegex(string: string) {\n    return string.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, \"\\\\$&\");\n  }\n\n  public static nameMatches(item: IParentItem, filter: string): boolean {\n    return item.name.length < filter.length ? false : item.name.toLowerCase().indexOf(filter) > -1;\n  }\n\n  public static deepFilterItems(items: IParentItem[], filter: string): IParentItem[] {\n    const filtered: IParentItem[] = [];\n\n    for (const item of items) {\n      const hasChildren = item.children && item.children.length > 0;\n      const newItem = Object.assign({}, item);\n\n      if (hasChildren) newItem.children = this.deepFilterItems(item.children, filter);\n\n      if ((hasChildren && newItem.children.length > 0) || this.nameMatches(item, filter))\n        filtered.push(newItem);\n    }\n\n    return filtered;\n  }\n\n  // The core API binds this into IDictionary<string, List<string>>, which needs an\n  // explicit index per value: filters[width][0]=195. An empty index (filters[width][])\n  // binds the key with an empty list, which silently drops the filter.\n  public static getFilterQuery(filters: object): string {\n    let query = \"\";\n\n    for (const [filter, values] of Object.entries(filters)) {\n      if (!values || values.length == 0) continue;\n\n      const key = Utils.encode(filter, true);\n\n      let i = 0;\n      for (let value of values) {\n        value = Utils.encode(value, true);\n        query += `filters[${key}][${i++}]=${value}&`;\n      }\n    }\n\n    // Remove trailing '&'\n    return query.slice(0, -1);\n  }\n\n  public static isInStock(stockItem: IStockItem | undefined) {\n    if (stockItem == undefined) {\n      return false;\n    }\n    if (stockItem.quantity == \"e\") {\n      return true;\n    }\n    if (stockItem.quantity == \"l\" || stockItem.quantity == \"t\") {\n      return false;\n    }\n    return parseInt(stockItem.quantity) > 0;\n  }\n}\n","// SAL-268: PlaceOrder returns the partner soft-add outcome (HTTP 200) when lines went to an\n// Inter Parts / Motogama cart, and nothing (204) otherwise. The order itself is always placed;\n// a failed soft-add only means the partner email is the fallback.\n\nexport interface PartnerCartOutcome {\n  attempted?: boolean;\n  failed?: boolean;\n  failedCodes?: string[];\n  message?: string;\n}\n\nexport type PartnerCartNotice =\n  | { kind: \"none\" }\n  | { kind: \"success\" }\n  | { kind: \"error\"; codes: string };\n\nexport function partnerCartNotice(outcome: PartnerCartOutcome | null | undefined): PartnerCartNotice {\n  if (!outcome || !outcome.attempted) return { kind: \"none\" };\n  if (!outcome.failed) return { kind: \"success\" };\n  // The toast text is localized in Client; the server message is not shown (it would repeat it).\n  return { kind: \"error\", codes: (outcome.failedCodes ?? []).join(\", \") };\n}\n","/**\n * SAL-325: below 768 px (resources/mobile.css) a table marked mobile-cards is shown as stacked\n * cards. The header row is hidden there, so every cell gets its column title as data-label,\n * which the CSS prints above the value. A cell with nothing to show (no text, no picture or\n * control, e.g. a product without a brand or thumbnail) gets data-empty, so the phone card\n * doesn't print a bare label. Desktop ignores both attributes, so it doesn't change.\n */\nexport function labelTableCells(table: HTMLTableElement): number {\n  const header = table.tHead?.rows[0];\n  if (!header) return 0;\n\n  const labels = Array.from(header.cells).map(cell => (cell.textContent ?? \"\").trim());\n  let labelled = 0;\n\n  for (const body of Array.from(table.tBodies))\n    for (const row of Array.from(body.rows)) {\n      // Detail rows (one colspan cell) don't line up with the header.\n      if (row.cells.length !== labels.length) continue;\n\n      Array.from(row.cells).forEach((cell, index) => {\n        const label = labels[index];\n        if (!label) return cell.removeAttribute(\"data-label\");\n        if (cell.getAttribute(\"data-label\") !== label) cell.setAttribute(\"data-label\", label);\n        if (isEmpty(cell)) cell.setAttribute(\"data-empty\", \"\");\n        else cell.removeAttribute(\"data-empty\");\n        labelled++;\n      });\n    }\n\n  return labelled;\n}\n\nconst content = \"img, input, select, button, textarea\";\n\nfunction isEmpty(cell: HTMLTableCellElement): boolean {\n  return !(cell.textContent ?? \"\").trim() && !cell.querySelector(content);\n}\n","// A phone field is stored as display text. The href keeps a leading plus and the digits.\nexport function toPhoneHref(value: string | null | undefined): string {\n  const trimmed = (value ?? \"\").trim();\n  if (!trimmed) return \"\";\n\n  const dialable = trimmed.replace(/[^\\d+]/g, \"\").replace(/(?!^)\\+/g, \"\");\n  if (!/\\d/.test(dialable)) return \"\";\n\n  return `tel:${dialable}`;\n}\n","import { LogManager } from \"aurelia-framework\";\nimport * as Sentry from \"@sentry/browser\";\n\nconst log = LogManager.getLogger(\"sentry.helper\");\n\nconst sentryEnvironments = [\"Production\", \"Staging\", \"Development\"];\n\n/**\n * SAL-322: deploy/deploy.sh replaces {{SENTRY_ENVIRONMENT}} with the environment of the branch it\n * builds (master Production, staging Staging, develop Development). A local or unstamped build\n * keeps the placeholder and reports as Development, never as Production.\n */\nexport function sentryEnvironment(stamped: string): string {\n  return sentryEnvironments.includes(stamped) ? stamped : \"Development\";\n}\n// Optional \"TypeError:\" prefix covers console capture of log.error(error.name, error),\n// which Sentry stores as the message \"TypeError: Load failed\" rather than a raw exception value.\nconst loadFailedMessagePattern = /^(?:TypeError:\\s*)?Load failed(?: \\(.+\\))?$/;\n\nfunction isNonActionableLoadFailed(\n  text: string | undefined,\n  exceptionType: string | undefined,\n): boolean {\n  if (!text || !loadFailedMessagePattern.test(text)) return false;\n  // Bare \"Load failed\" stays reportable unless the exception type is TypeError.\n  // The \"TypeError:\" prefix is itself enough (console events often have no exception type).\n  return exceptionType === \"TypeError\" || text.startsWith(\"TypeError:\");\n}\n\n/**\n * Sentry beforeSend filter to prevent browser extension noise from being captured.\n *\n * This filter addresses the issue where browser extensions inject code and log messages\n * that get captured as application errors, creating noise in Sentry reports.\n *\n * @param event - The Sentry event to potentially filter\n * @param hint - Additional context for the event\n * @returns The event if it should be sent, or null if it should be filtered out\n */\nexport function sentryBeforeSendFilter(\n  event: Sentry.Event,\n  hint: Sentry.EventHint,\n): Sentry.Event | null {\n  const logentry = event.logentry as { formatted?: string; message?: string } | undefined;\n  const eventMessage =\n    event.message ||\n    logentry?.formatted ||\n    logentry?.message ||\n    event.exception?.values?.[0]?.value ||\n    \"\";\n\n  const requestUrl = event.request?.url || \"\";\n  const responseStatusCode = event.contexts?.response?.status_code;\n  if (\n    event.exception?.values?.[0]?.type === \"Error\" &&\n    event.exception?.values?.[0]?.value === \"HTTP Client Error with status code: 500\" &&\n    responseStatusCode === 500 &&\n    /\\/api\\/v\\d+\\.\\d+\\/Product\\/\\d+\\/Thumbnail$/i.test(requestUrl)\n  ) {\n    log.debug(\"Filtered out thumbnail fetch 500:\", requestUrl);\n    return null;\n  }\n\n  // Filter out browser extension events based on stack traces\n  if (event.exception?.values) {\n    for (const exception of event.exception.values) {\n      if (exception.stacktrace?.frames) {\n        for (const frame of exception.stacktrace.frames) {\n          // Filter out any events originating from browser extensions\n          if (frame.filename && frame.filename.includes(\"chrome-extension://\")) {\n            log.debug(\"Filtered out browser extension error:\", eventMessage);\n            return null;\n          }\n          if (frame.filename && frame.filename.includes(\"moz-extension://\")) {\n            log.debug(\"Filtered out Firefox extension error:\", eventMessage);\n            return null;\n          }\n          if (frame.filename && frame.filename.includes(\"safari-extension://\")) {\n            log.debug(\"Filtered out Safari extension error:\", eventMessage);\n            return null;\n          }\n        }\n      }\n    }\n  }\n\n  // Filter out non-actionable \"Load failed\" errors which are common on mobile browsers\n  // due to network interruptions and consume Sentry quota. Console capture from\n  // Utils.getErrorMessage arrives as message \"TypeError: Load failed\" (no exception type).\n  const loadFailedException = event.exception?.values?.[0];\n  const loadFailedTexts = [\n    loadFailedException?.value,\n    event.message,\n    logentry?.formatted,\n    logentry?.message,\n  ];\n  if (loadFailedTexts.some(text => isNonActionableLoadFailed(text, loadFailedException?.type))) {\n    log.debug(\"Filtered out 'Load failed' TypeError:\", event.event_id);\n    return null;\n  }\n\n  // Filter out console messages that originate from extensions or contain generic noise\n  if (eventMessage) {\n    const message = eventMessage;\n\n    // Filter messages that contain extension-specific patterns\n    const extensionPatterns = [\n      /chrome-extension:/,\n      /moz-extension:/,\n      /safari-extension:/,\n      /TSS:/, // Specific to the problematic extension mentioned in the issue\n      /Extension/i,\n    ];\n\n    for (const pattern of extensionPatterns) {\n      if (pattern.test(message)) {\n        log.debug(\"Filtered out extension-related console message:\", message);\n        return null;\n      }\n    }\n\n    // For synthetic console errors, only capture if they contain explicit error keywords\n    if (event.level === \"error\" && event.logger === \"console\") {\n      const errorKeywords = [\n        \"error\",\n        \"exception\",\n        \"failed\",\n        \"failure\",\n        \"crash\",\n        \"fatal\",\n        \"critical\",\n        \"uncaught\",\n        \"unhandled\",\n        \"invalid\",\n        \"missing\",\n        \"undefined\",\n        \"null\",\n        \"network\",\n        \"timeout\",\n        \"abort\",\n      ];\n\n      const hasErrorKeyword = errorKeywords.some(keyword =>\n        message.toLowerCase().includes(keyword),\n      );\n\n      if (!hasErrorKeyword && message.includes(\"[object Object]\")) {\n        log.debug(\"Filtered out generic console error without error keywords:\", message);\n        return null;\n      }\n    }\n  }\n\n  return event;\n}\n","import { IUserSettings } from \"domain/Settings/IUserSettings\";\nimport { PriceType } from \"utils/enums/PriceType\";\nimport { IPrice } from \"domain/Product/IPrice\";\nimport { LogManager } from \"aurelia-framework\";\nimport { I18N } from \"aurelia-i18n\";\nimport { PriceSetting } from \"utils/enums/PriceSetting\";\nimport { IStockItem } from \"domain/Product/IStockItem\";\nimport { IProduct } from \"domain/Product/IProduct\";\n\nexport const log = LogManager.getLogger(\"app.utils.helpers.price\");\n\nexport class PriceHelper {\n  public static formatProductsPrices(\n    products: IProduct[],\n    settings: IUserSettings,\n    i18n: I18N,\n    displayWholesale = false,\n  ) {\n    if (products == null || products.length == 0) return;\n\n    products.forEach(product => {\n      this.formatPrices(product.prices, i18n, settings);\n      this.formatStockPrices(product.stock, settings, i18n, displayWholesale);\n      this.formatStockPrices(product.externalStock, settings, i18n, displayWholesale);\n    });\n  }\n\n  public static formatStockPrices(\n    stock: IStockItem[],\n    settings: IUserSettings,\n    i18n: I18N,\n    displayWholesale = false,\n  ) {\n    if (stock == null || stock.length == 0) return;\n\n    stock.forEach(stockItem => {\n      PriceHelper.formatPrices(stockItem.prices, i18n, settings);\n\n      stockItem.displayPrice = PriceHelper.getDisplayPrice(\n        stockItem.prices,\n        settings,\n        displayWholesale,\n      );\n    });\n  }\n\n  public static formatPrices(prices: IPrice[], i18n: I18N, settings: IUserSettings) {\n    if (prices == null || prices.length == 0) return null;\n\n    prices.forEach(price => {\n      price.name = PriceHelper.translatePriceName(price, i18n);\n      price.displayAmount = PriceHelper.getPriceVatFromSettings(price, settings);\n    });\n  }\n\n  public static translatePriceName(price: IPrice, i18n: I18N): string {\n    // Translate price names\n    switch (price.type) {\n      case PriceType.Wholesale:\n        return i18n.tr(\"components.price.type.wholesale\");\n      case PriceType.Retail:\n        return i18n.tr(\"components.price.type.retail\");\n      case PriceType.Discount:\n        return i18n.tr(\"components.price.type.discount.regular\");\n      case PriceType.AmountDiscount:\n        return i18n.tr(\"components.price.type.discount.amount\");\n      case PriceType.TimeDiscount:\n        return i18n.tr(\"components.price.type.discount.time\");\n      case PriceType.UserGroupTimeDiscount:\n        return i18n.tr(\"components.price.type.discount.usergroup-time\");\n      case PriceType.DiscountGroupUserGroupTimeDiscount:\n        return i18n.tr(\"components.price.type.discount.pricegroup-usergroup-time\");\n      case PriceType.AmountDiscountList:\n        return i18n.tr(\"components.price.type.discount.number\", {\n          amount: price.quantity,\n        });\n      default:\n        return price.name ?? \"\";\n    }\n  }\n\n  public static getDisplayPrice(\n    prices: IPrice[],\n    settings: IUserSettings,\n    displayWholesale: boolean,\n  ): number | undefined {\n    if (settings.priceSetting == PriceSetting.None) return;\n\n    const priceType =\n      settings.priceSetting == PriceSetting.Retail && !displayWholesale\n        ? PriceType.Retail\n        : PriceType.Wholesale;\n    const price = prices.find(price => price.type == priceType);\n\n    return displayWholesale\n      ? PriceHelper.getPrice(price, false)\n      : PriceHelper.getPriceVatFromSettings(price, settings);\n  }\n\n  public static getPriceVatFromSettings(price: IPrice | undefined, settings: IUserSettings) {\n    if (price?.withoutVat == undefined) return;\n\n    const vatIncluded =\n      price.type == PriceType.Retail\n        ? settings.vatIncludedInRetail\n        : settings.vatIncludedInWholesale;\n\n    return PriceHelper.getPrice(price, vatIncluded);\n  }\n\n  public static getPrice(price?: IPrice, vatIncluded?: boolean) {\n    if (price?.withoutVat == undefined) return;\n\n    // Both amounts come from the API. Nothing is calculated here, so a missing VAT rate\n    // shows up as an unknown price instead of a believable wrong one.\n    return vatIncluded ? price.withVat : price.withoutVat;\n  }\n}\n","// SAL-283 r8: page and component event subscriptions must end with the view. Subscribing in a constructor\n// without disposing kept every old /cart instance alive on the event bus; on each add, quantity edit or\n// remove those stale pages pushed their old (often empty) cart to the header badge, which blanked it and\n// discarded the badge's own fetch until the next navigation.\nexport type Disposable = { dispose(): void };\n\nexport class Subscriptions {\n  private items: Disposable[] = [];\n\n  add(...subscriptions: Disposable[]): void {\n    this.items.push(...subscriptions);\n  }\n\n  get count(): number {\n    return this.items.length;\n  }\n\n  disposeAll(): void {\n    const items = this.items;\n    this.items = [];\n    for (const s of items) s.dispose();\n  }\n}\n","/**\n * SAL-343: search route helpers. Kept free of Aurelia imports so they can be unit tested.\n */\n\n// One encoding for the search query, in the client route and in the API call.\n// SAL-343: plain text such as the OE code \"000 983 0621\" is encoded once (000%20983%200621).\n// A query holding \"%\" or a backslash keeps the old double encoding (Utils.encode): the router\n// runs decodeURI over the path, which turns %25 back into \"%\", and TKBridge urldecodes the term\n// once more after PHP has decoded it, so a single-encoded \"%20\" would turn into a space there.\nconst needsDoubleEncoding = /[%\\\\]/;\n\n/** Encodes a search query for the searchResult route parameter and the core Search API path. */\nexport function encodeSearchRouteQuery(query: string): string {\n  const text = String(query ?? \"\");\n  if (needsDoubleEncoding.test(text)) return encodeURIComponent(encodeURI(text));\n  return encodeURIComponent(text);\n}\n\n/** Reads the query back from the route parameter; works for both encodings and never throws. */\nexport function decodeSearchRouteQuery(raw: unknown): string {\n  const text = String(raw ?? \"\");\n  try {\n    return decodeURIComponent(text);\n  } catch {\n    return text;\n  }\n}\n\n/**\n * Whether the search bar should navigate for a search. It must not when the route already shows\n * that search: re-navigating the page it is on (e.g. 500 ms after an OE click changed the search\n * type from All to TecDoc) made the router deactivate the search page, which aborted its request.\n */\nexport function shouldNavigateForQuery(\n  currentRoute: string | undefined,\n  routeQuery: string | undefined,\n  query: string,\n  routeType?: unknown,\n  type?: unknown,\n): boolean {\n  if (currentRoute !== \"searchResult\") return true;\n  if ((routeQuery ?? \"\").trim() !== String(query ?? \"\").trim()) return true;\n  if (type === undefined || routeType === undefined) return false;\n  return parseInt(String(routeType)) !== parseInt(String(type));\n}\n\n/**\n * Whether activate may skip loading because the same search is already shown or still loading.\n * A search that was aborted (for example by deactivate) without a result must run again.\n */\nexport function canReuseSearch(sameParameters: boolean, hasResult: boolean, requestInFlight: boolean): boolean {\n  return sameParameters && (hasResult || requestInFlight);\n}\n\n/** A search that gets no answer in this time is aborted and shown as an error, never an endless spinner. */\nexport const SEARCH_TIMEOUT_MS = 60000;\n","type ToastDoc = { querySelector(selector: string): any };\n\n/**\n * SAL-283 QA: put the message in the toast body and show it. Returns false when the toast or its\n * body is missing. `show` opens the Bootstrap toast; when it throws, the toast is shown by class.\n */\nexport function fillAndShowToast(doc: ToastDoc, selector: string, message: string, show: (element: any) => void): boolean {\n  const element = doc.querySelector(selector);\n  const body = element?.querySelector(\".toast-body\");\n  if (!element || !body) return false;\n  body.innerHTML = message;\n  try {\n    show(element);\n  } catch {\n    element.classList.remove(\"hide\");\n    element.classList.add(\"show\");\n  }\n  return true;\n}\n\n/**\n * SAL-283 QA: one Lisa click gives at most one add. The confirm modal's OK adds only for the click\n * that opened it, and no second add starts while one is in flight.\n */\nexport class AddToCartGate {\n  private confirmPending = false;\n  private adding = false;\n\n  /** Lisa needs the confirm modal: remember it. Returns false while an add is in flight. */\n  requestConfirm(): boolean {\n    if (this.adding) return false;\n    this.confirmPending = true;\n    return true;\n  }\n\n  /** OK clicked: true only once per requestConfirm(). */\n  takeConfirm(): boolean {\n    const pending = this.confirmPending;\n    this.confirmPending = false;\n    return pending;\n  }\n\n  cancelConfirm(): void {\n    this.confirmPending = false;\n  }\n\n  /** Start an add; false when one is already running. */\n  begin(): boolean {\n    if (this.adding) return false;\n    this.adding = true;\n    return true;\n  }\n\n  end(): void {\n    this.adding = false;\n  }\n\n  get busy(): boolean {\n    return this.adding;\n  }\n}\n"],"sourceRoot":""}