import {getCardComponentData} from "../cards"; import {CardRenderer, CardRendererContext} from "../CardRenderers"; import { ComponentMeta } from "../ComponentsMeta"; import { __, CouponsPlus } from "../../globals"; import { useTreeNodeTestable } from "../TreeHooks"; import {AmountValidatorOptions} from "./filterinterfaces"; import { FieldType } from "../fields"; import { usingTestableOptions } from "./CardHelpers"; import { amountValidatorTierFields, equals, TierType } from "../tiers"; import { number } from "motion"; import classNames from "classnames"; import { RowOptions } from "./Fields/RowOptions"; import { NumberProps } from "../fields/Number"; import React, { type ReactNode } from "react"; import { findFilter } from "../testables"; import { InlineWarning } from "./InlineWarning"; export enum QuantityType { Any = 'any', Limit = 'limit', } export type QuantityOptions = AmountValidatorOptions & { type: QuantityType } const type = CouponsPlus?.components?.filters?.NumberOfItems?.type || 'NumberOfItems' export const QuantityMeta: ComponentMeta = { id: type, type: 'filters', supportsTiers: true, tierFields: amountValidatorTierFields(), tierType: TierType.Numeric, configurable: context => context !== 'tier-base', supportsCustomTitleForSuggestedScopes: (suggestedScope) => true, icon({className}) { return }, canBeCombined: filters => !findFilter(filters, 'MinimumCombinedCostOfItems'), fieldsMap: (componentRuntimeData, scope, suggestedScope) => [ [ /* edit: this is no longer going to be needed and if you've found this please delete it {id: 'type', renderType: FieldType.Tab} */ ], [ new RowOptions({ type: 'featured', colWidth: 'auto' }), { id: 'quantity', renderType: FieldType.Number, fieldProps: { border: false, labels: { input: { inputLeftOutside: suggestedScope === 'bogo' && scope === 'tree-node'? {__('Buy')} : undefined } } } as Partial } ] ], fieldsNotes: ({ options }, scope) => [ scope.startsWith('tier') && __('Tier: The cheapest items will be selected from the filtered items.') ].filter(note => !!note), errors: ({ options: { quantity } }, filters) => { const errors: ReactNode[] = [] const zeroError = {__('Please add valid quantities (higher than 1).')} const isValidAmount = (amount: number | string) => { const cleanedAmount: number = typeof amount === 'string' ? // this would be ideal to tell the browser to give us the separator for the locale but currently i have no internet access so i don't have access to docs or packages. ((amount.includes('.') || amount.includes(',')) ? parseFloat(amount) : parseInt(amount)) : amount // needs to be 1 or higher return (cleanedAmount * 100) >= (1 * 100) } if (quantity.type === 'range') { const minimum = quantity.range.minimum; // [NOTE]!: i know its misspelled this is a legacy error do not try to be clever and change this. const maximum = quantity.range.maxmimum; // make sure we have a valid range if ([minimum, maximum].some(amount => !isValidAmount(amount))) { errors.push(zeroError) } // if min is higher then max if (minimum > maximum) { errors.push( {__('Minimum must be lower than maximum.')} ) } } else { // just make sure we have 1 or more if (!isValidAmount(quantity.amount)) { errors.push( zeroError ) } } return errors } } export const QuantityRenderer: CardRenderer = { type, icon({ className }) { return }, prefix: false, example: scope => scope === 'tier-base' ? __('2 -> 10% OFF, 3 -> 13% OFF, etc.') : __('Buy 1, Get 1 Free, 10% off Max. 5 items, etc.'), AboveComputedValue: usingTestableOptions(({ quantity: { type } = {} } = {}, { context }) => { if ((['tier-base', 'tier-subchild'] as CardRendererContext[]).includes(context)) { return false } switch (type) { case "equals": case "range": return __('Quantity') case "minimum": return __('Minimum') case "maximum": return __('Maximum') } }), ComputedValue: usingTestableOptions(({quantity: {amount, type, range} = {}} = {}, {context}) => { if (context === 'tier-base') { return getCardComponentData('filter', QuantityMeta.id).name } let value: string | number = '' switch (type) { case "equals": case "minimum": case "maximum": value = amount! break case "range": value = `${range?.minimum} - ${range?.maxmimum}` break } if (context === 'tier-subchild') { return {value} {type === 'minimum' && } {type === 'maximum' && {__('max.')}} } return value }), canRenderInSelectedSidebarPopup: (options: QuantityOptions, currentFilterId: string): boolean => { // if its type is 'any' then it should not be shown in the sidebar popup return options?.type !== QuantityType.Any; }, canShowAsAvailableWhileBeingAmongstSelected: (options: QuantityOptions, currentFilterId): boolean => { const anyQuantity = options?.type === QuantityType.Any; const currentlySelectedFilterIsNotQuantity = currentFilterId !== QuantityMeta.id; return anyQuantity && currentlySelectedFilterIsNotQuantity; }, }