Files
best-choice/client/src/pages/project-detail/project-items-utils.ts
T
Jurgis Sakalauskas e30e116187 split items section
2025-10-14 14:05:57 +03:00

191 lines
5.2 KiB
TypeScript

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
};
};