Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 49x 49x 1x 1x 1x 7x 7x 7x 7x 7x 7x 7x 7x 24x 9x 24x 3x 3x 24x 6x 6x 1x 1x 1x 6x 6x 6x 6x 24x 23x 23x 23x 24x 24x 6x 6x 1x 1x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1x 1x 1x 86x 86x 86x 86x 81x 81x 5x 5x 5x | /**
* @group Helpers
* @groupDescription Cross-filter compatibility between the advanced-search drawer's
* supplier, shipping-type, and country filters — which suppliers a shipping/country
* selection rules out, and which shipping/country options a supplier selection leaves
* reachable. Pure functions over a supplier metadata map so the drawer UI and the
* search can share (and unit-test) one source of truth.
*
* Shipping scope is a **hierarchy**, broadest to narrowest:
* `worldwide ⊇ international ⊇ domestic ⊇ local`. A broader scope fulfills every
* narrower request — a worldwide supplier can also ship internationally, domestically,
* and locally; a domestic supplier can ship domestically and locally but not worldwide.
* @source
*/
import { SHIPPING_OPTIONS, isShippingRange } from '@/constants/common';
/** One supplier's home country and coarse shipping scope. */
export interface SupplierShippingMeta {
/** ISO 3166-1 alpha-2 home country. */
country: CountryCode;
/** Coarse shipping scope stamped onto the supplier's products. */
shipping: ShippingRange;
}
/** Map of supplier class name → its {@link SupplierShippingMeta}. */
export type SupplierMetaMap = Readonly<Record<string, SupplierShippingMeta>>;
/** Rank of each shipping scope, broadest highest. A scope fulfills any lower-ranked one. */
const SHIPPING_SCOPE_RANK: Record<ShippingRange, number> = {
local: 0,
domestic: 1,
international: 2,
worldwide: 3,
};
/**
* Whether a supplier's shipping scope fulfills a requested one, treating scope as a
* hierarchy: a scope covers itself and every narrower scope (worldwide covers all,
* local covers only local).
* @category Helpers
* @param supplierScope - The supplier's own shipping scope.
* @param requested - The requested shipping scope to satisfy.
* @returns True when `supplierScope` is equal to or broader than `requested`.
* @example
* ```ts
* shippingCovers('worldwide', 'domestic'); // => true
* shippingCovers('domestic', 'worldwide'); // => false
* shippingCovers('domestic', 'local'); // => true
* ```
* @source
*/
export function shippingCovers(supplierScope: ShippingRange, requested: ShippingRange): boolean {
return SHIPPING_SCOPE_RANK[supplierScope] >= SHIPPING_SCOPE_RANK[requested];
}
/**
* The supplier class names ruled out by the active shipping-type and/or country
* filters. A supplier is excluded when a non-empty shipping filter names no scope
* its own scope can fulfill (per the {@link shippingCovers} hierarchy), or a
* non-empty country filter doesn't list its country. An empty filter constrains
* nothing.
* @category Helpers
* @param meta - Supplier shipping/country metadata (from `SupplierFactory.supplierShippingMeta`).
* @param filters - The active `shippingType` and `country` selections.
* @returns The set of supplier class names no result could satisfy under the filters.
* @example
* ```ts
* suppliersExcludedBySearchFilters(
* { A: { country: 'US', shipping: 'worldwide' }, B: { country: 'US', shipping: 'domestic' } },
* { shippingType: ['worldwide'], country: [] },
* ); // => Set { 'B' } (domestic can't fulfill a worldwide request)
* ```
* @source
*/
export function suppliersExcludedBySearchFilters(
meta: SupplierMetaMap,
filters: { shippingType: readonly string[]; country: readonly string[] },
): Set<string> {
const excluded = new Set<string>();
const requestedScopes = filters.shippingType.filter(isShippingRange);
const byShipping = requestedScopes.length > 0;
const byCountry = filters.country.length > 0;
if (!byShipping && !byCountry) return excluded;
for (const [key, { country, shipping }] of Object.entries(meta)) {
if (byShipping && !requestedScopes.some((requested) => shippingCovers(shipping, requested))) {
excluded.add(key);
} else if (byCountry && !filters.country.includes(country)) {
excluded.add(key);
}
}
return excluded;
}
/**
* The shipping-type options the given suppliers can fulfill: every scope at or below
* the broadest selected supplier's scope (per the {@link shippingCovers} hierarchy).
* Used to decide which shipping options stay selectable — an option no selected
* supplier can fulfill is disabled. Empty in ⇒ empty out (no supplier constraint).
* @category Helpers
* @param meta - Supplier shipping/country metadata.
* @param suppliers - The currently selected supplier class names.
* @returns The set of shipping scopes at least one selected supplier can fulfill.
* @example
* ```ts
* fulfillableShippingRanges(
* { A: { country: 'US', shipping: 'domestic' } },
* ['A'],
* ); // => Set { 'local', 'domestic' }
* ```
* @source
*/
export function fulfillableShippingRanges(
meta: SupplierMetaMap,
suppliers: readonly string[],
): Set<ShippingRange> {
const fulfillable = new Set<ShippingRange>();
for (const option of SHIPPING_OPTIONS) {
const reachable = suppliers.some((key) => {
const entry = meta[key];
return entry !== undefined && shippingCovers(entry.shipping, option);
});
if (reachable) fulfillable.add(option);
}
return fulfillable;
}
/**
* The distinct home countries of the given suppliers. Used to decide which
* country options are still reachable: a country no selected supplier resides in
* can be disabled. Empty in ⇒ empty out (no supplier constraint).
* @category Helpers
* @param meta - Supplier shipping/country metadata.
* @param suppliers - The currently selected supplier class names.
* @returns The set of country codes those suppliers reside in.
* @example
* ```ts
* countriesForSuppliers(
* { A: { country: 'US', shipping: 'domestic' }, B: { country: 'DE', shipping: 'international' } },
* ['B'],
* ); // => Set { 'DE' }
* ```
* @source
*/
export function countriesForSuppliers(
meta: SupplierMetaMap,
suppliers: readonly string[],
): Set<CountryCode> {
const countries = new Set<CountryCode>();
for (const key of suppliers) {
const entry = meta[key];
if (entry) countries.add(entry.country);
}
return countries;
}
/**
* Resolves the drawer's supplier selection to the list of suppliers to search. Normally the
* selection itself (empty meaning "all"). When `invert` is set and something is selected, the
* selection is an exclusion list: every other supplier. Suppliers the user disabled, or that
* don't ship to them, are removed later by `SupplierFactory`, so inverting never re-enables them.
* Inverting a selection that covers every supplier leaves nothing to search, reported by
* `none` so the caller doesn't fall back to the empty-means-all behavior.
* @category Helpers
* @param selected - The suppliers chosen in the drawer.
* @param invert - Whether the selection acts as an exclusion list.
* @param all - Every supplier class name.
* @returns The suppliers to search, and `none` when an inverted selection excludes them all.
* @example
* ```ts
* resolveSupplierSelection(['A'], false, ['A', 'B', 'C']); // => { suppliers: ['A'], none: false }
* resolveSupplierSelection(['A'], true, ['A', 'B', 'C']); // => { suppliers: ['B', 'C'], none: false }
* resolveSupplierSelection([], true, ['A', 'B']); // => { suppliers: [], none: false } (no selection: all)
* resolveSupplierSelection(['A', 'B'], true, ['A', 'B']); // => { suppliers: [], none: true }
* ```
* @source
*/
export function resolveSupplierSelection<T extends string>(
selected: readonly T[],
invert: boolean,
all: readonly T[],
): { suppliers: T[]; none: boolean } {
if (!invert || selected.length === 0) {
return { suppliers: [...selected], none: false };
}
const suppliers = all.filter((name) => !selected.includes(name));
return { suppliers, none: suppliers.length === 0 };
}
|