mirror of
https://github.com/sakaljurgis/best-choice.git
synced 2026-07-08 21:47:40 +00:00
split items section
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
import type { Item } from '@shared/models/item';
|
||||
|
||||
const createSortKey = (value: unknown): string => {
|
||||
if (Array.isArray(value)) {
|
||||
return `array:${value.length}:${value.map((item) => createSortKey(item)).join('|')}`;
|
||||
}
|
||||
if (value === null) {
|
||||
return 'null';
|
||||
}
|
||||
const type = typeof value;
|
||||
if (type === 'number') {
|
||||
return `${type}:${(value as number).toString()}`;
|
||||
}
|
||||
if (type === 'boolean') {
|
||||
return `${type}:${value ? '1' : '0'}`;
|
||||
}
|
||||
if (type === 'string') {
|
||||
return `${type}:${value}`;
|
||||
}
|
||||
return `${type}:${JSON.stringify(value)}`;
|
||||
};
|
||||
|
||||
const normalizeAttributeValue = (value: unknown): unknown => {
|
||||
if (value === null || value === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
const normalizedItems = value.map((item) => normalizeAttributeValue(item));
|
||||
normalizedItems.sort((first, second) => {
|
||||
const firstKey = createSortKey(first);
|
||||
const secondKey = createSortKey(second);
|
||||
if (firstKey < secondKey) {
|
||||
return -1;
|
||||
}
|
||||
if (firstKey > secondKey) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return normalizedItems;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const areAttributeValuesEqual = (first: unknown, second: unknown): boolean => {
|
||||
const normalizedFirst = normalizeAttributeValue(first);
|
||||
const normalizedSecond = normalizeAttributeValue(second);
|
||||
|
||||
if (Array.isArray(normalizedFirst) && Array.isArray(normalizedSecond)) {
|
||||
if (normalizedFirst.length !== normalizedSecond.length) {
|
||||
return false;
|
||||
}
|
||||
for (let index = 0; index < normalizedFirst.length; index += 1) {
|
||||
if (!areAttributeValuesEqual(normalizedFirst[index], normalizedSecond[index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return Object.is(normalizedFirst, normalizedSecond);
|
||||
};
|
||||
|
||||
export const formatAttributeValue = (value: unknown): string => {
|
||||
if (value === null || value === undefined) {
|
||||
return '—';
|
||||
}
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? 'Yes' : 'No';
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) {
|
||||
return '—';
|
||||
}
|
||||
const formattedValues = value
|
||||
.map((item) => {
|
||||
if (item === null || item === undefined) {
|
||||
return '';
|
||||
}
|
||||
if (typeof item === 'string') {
|
||||
return item.trim();
|
||||
}
|
||||
if (typeof item === 'number') {
|
||||
return String(item);
|
||||
}
|
||||
if (typeof item === 'boolean') {
|
||||
return item ? 'Yes' : 'No';
|
||||
}
|
||||
return JSON.stringify(item);
|
||||
})
|
||||
.filter((item) => item.length > 0);
|
||||
|
||||
return formattedValues.length ? formattedValues.join(', ') : '—';
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return value.length ? value : '—';
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return String(value);
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
};
|
||||
|
||||
export const formatPriceAmount = (amount: number, currency: string | null): string => {
|
||||
if (!Number.isFinite(amount)) {
|
||||
return '—';
|
||||
}
|
||||
if (currency) {
|
||||
try {
|
||||
return new Intl.NumberFormat(undefined, {
|
||||
style: 'currency',
|
||||
currency,
|
||||
maximumFractionDigits: 2
|
||||
}).format(amount);
|
||||
} catch {
|
||||
return `${amount.toFixed(2)} ${currency}`;
|
||||
}
|
||||
}
|
||||
return amount.toFixed(2);
|
||||
};
|
||||
|
||||
export const getPriceSummaryDisplay = (
|
||||
summary: Item['priceSummary'] | null
|
||||
): { primary: string; secondary: string | null } => {
|
||||
if (!summary || summary.priceCount === 0) {
|
||||
return { primary: '—', secondary: null };
|
||||
}
|
||||
|
||||
const resolveCurrency = (preferred: string | null) => preferred ?? summary.currency ?? null;
|
||||
const formatConditionLabel = (
|
||||
condition: 'new' | 'used',
|
||||
amount: number | null,
|
||||
count: number,
|
||||
currency: string | null,
|
||||
hasMixedCurrency: boolean
|
||||
): string | null => {
|
||||
if (!count) {
|
||||
return null;
|
||||
}
|
||||
if (amount === null || hasMixedCurrency) {
|
||||
return `${condition} prices vary (${count})`;
|
||||
}
|
||||
const resolvedCurrency = resolveCurrency(currency);
|
||||
const formatted = formatPriceAmount(amount, resolvedCurrency);
|
||||
return `${condition} from ${formatted} (${count})`;
|
||||
};
|
||||
|
||||
const newLabel = formatConditionLabel(
|
||||
'new',
|
||||
summary.newMinAmount,
|
||||
summary.newCount,
|
||||
summary.newCurrency,
|
||||
summary.newHasMixedCurrency
|
||||
);
|
||||
const usedLabel = formatConditionLabel(
|
||||
'used',
|
||||
summary.usedMinAmount,
|
||||
summary.usedCount,
|
||||
summary.usedCurrency,
|
||||
summary.usedHasMixedCurrency
|
||||
);
|
||||
|
||||
if (newLabel || usedLabel) {
|
||||
if (newLabel && usedLabel) {
|
||||
return { primary: newLabel, secondary: usedLabel };
|
||||
}
|
||||
return { primary: newLabel ?? usedLabel ?? '—', secondary: null };
|
||||
}
|
||||
|
||||
if (!summary.hasMixedCurrency) {
|
||||
const resolvedCurrency = resolveCurrency(null);
|
||||
const formattedMin = formatPriceAmount(summary.minAmount, resolvedCurrency);
|
||||
const formattedMax = formatPriceAmount(summary.maxAmount, resolvedCurrency);
|
||||
if (summary.minAmount === summary.maxAmount) {
|
||||
return {
|
||||
primary: `${formattedMin} (${summary.priceCount})`,
|
||||
secondary: null
|
||||
};
|
||||
}
|
||||
return {
|
||||
primary: `from ${formattedMin} (${summary.priceCount})`,
|
||||
secondary: `up to ${formattedMax}`
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
primary: `Multiple currencies (${summary.priceCount})`,
|
||||
secondary: null
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user