// Type definitions export interface Price { monthly?: number; upfront?: number; } export interface Discount { monthly?: number; upfront?: number; monthsCount?: number; } export interface Item { type?: string; name?: string; icon?: string; image?: string; price?: Price; isSubitem?: boolean; isOptional?: boolean; isMandatory?: boolean; discount?: Discount; monthsFree?: number; tooltip?: string; badge?: string; } export interface DiscountPeriod { start: number; end: number; price: Price; discount: Discount; } export interface FormattedPeriod { name: string; price: Price; discount: Discount; } /** * * Calculate total payments for each month * upfront price is shown just in the first row if its set * monthly prices are shown with striked out discounts above the price * * @param items - cart items raw data * @param commitment - commitment in months * */ export default function generateTotal( items: Item[], hideFirst?: boolean, commitment: number = 24, ): FormattedPeriod[] { let discountPeriods: DiscountPeriod[] = []; // calculate full price without discounts const monthlyPrice = calculateMonthlyPrice(items); const upfrontPrice = calculateUpfrontPrice(items); for (let month = 1; month < commitment; month++) { const price: Price = { // show upfront price only in first month upfront: upfrontPrice > 0 && month === 1 ? upfrontPrice : undefined, monthly: monthlyPrice, }; // calculate discount for current month const discount: Discount = { monthly: calculateMonthlyDiscount(items, month, commitment), }; // if discount is not the same as last item add new one const lastIndex = discountPeriods.length - 1; if ( discountPeriods.length === 0 || discountPeriods[lastIndex].price?.upfront !== price?.upfront || discountPeriods[lastIndex].discount?.monthly !== discount?.monthly ) { // set correct ending of last period if (discountPeriods[lastIndex]) { discountPeriods[lastIndex].end = month - 1; } discountPeriods = [ ...discountPeriods, { start: month, end: commitment, price, discount, }, ]; } } if (hideFirst) { discountPeriods.shift(); if (discountPeriods[0]) discountPeriods[0].start = 1; } // return correct format for table return format(discountPeriods); } function calculateMonthlyPrice(items: Item[]): number { return items.reduce((price, item) => price + (item?.price?.monthly ?? 0), 0); } function calculateUpfrontPrice(items: Item[]): number { return items.reduce((price, item) => { price += item?.price?.upfront ?? 0; price -= item?.discount?.upfront ?? 0; return price; }, 0); } function calculateMonthlyDiscount( items: Item[], currentMonth: number, commitment: number, ): number { return items.reduce((discount, item) => { const monthsFree = item?.monthsFree ?? 0; const discountMonthsCount = item?.discount?.monthsCount ?? 0; // special discounts starts after free months const allDiscountsEnd = discountMonthsCount + monthsFree || commitment; // if its free this month, add full price to discount if (monthsFree && currentMonth <= monthsFree) { discount += item?.price?.monthly ?? 0; } // add temporary or default monthly discount if aplicable else if (allDiscountsEnd > 0 && currentMonth <= allDiscountsEnd) { discount += item?.discount?.monthly ?? 0; } return discount; }, 0); } function format(items: DiscountPeriod[]): FormattedPeriod[] { return items.map(({ start, end, price, discount }) => { let name = ""; if (items.length > 1) { if (start !== end) { name = `${start}. – ${end}. mesiac`; } else { name = `${start}. mesiac`; } } return { name, price, discount, }; }); }