ADD new frontend

This commit is contained in:
Urtzi Alfaro
2025-08-28 10:41:04 +02:00
parent 9c247a5f99
commit 0fd273cfce
492 changed files with 114979 additions and 1632 deletions

View File

@@ -0,0 +1,433 @@
/**
* Currency utilities for formatting and calculations
*/
// Currency configuration
export const CURRENCY_CONFIG = {
EUR: {
code: 'EUR',
symbol: '€',
name: 'Euro',
decimals: 2,
locale: 'es-ES',
},
USD: {
code: 'USD',
symbol: '$',
name: 'US Dollar',
decimals: 2,
locale: 'en-US',
},
GBP: {
code: 'GBP',
symbol: '£',
name: 'British Pound',
decimals: 2,
locale: 'en-GB',
},
} as const;
type CurrencyCode = keyof typeof CURRENCY_CONFIG;
// Format currency amount
export const formatCurrency = (
amount: number,
currencyCode: CurrencyCode = 'EUR',
options: Intl.NumberFormatOptions = {}
): string => {
if (typeof amount !== 'number' || isNaN(amount)) {
return formatCurrency(0, currencyCode, options);
}
const config = CURRENCY_CONFIG[currencyCode];
const defaultOptions: Intl.NumberFormatOptions = {
style: 'currency',
currency: config.code,
minimumFractionDigits: config.decimals,
maximumFractionDigits: config.decimals,
};
const formatOptions = { ...defaultOptions, ...options };
try {
return new Intl.NumberFormat(config.locale, formatOptions).format(amount);
} catch (error) {
// Fallback to manual formatting if Intl.NumberFormat fails
const formattedAmount = amount.toFixed(config.decimals);
return `${formattedAmount} ${config.symbol}`;
}
};
// Format currency without symbol (just the number)
export const formatCurrencyAmount = (
amount: number,
currencyCode: CurrencyCode = 'EUR',
decimals?: number
): string => {
if (typeof amount !== 'number' || isNaN(amount)) {
return formatCurrencyAmount(0, currencyCode, decimals);
}
const config = CURRENCY_CONFIG[currencyCode];
const decimalPlaces = decimals ?? config.decimals;
try {
return new Intl.NumberFormat(config.locale, {
minimumFractionDigits: decimalPlaces,
maximumFractionDigits: decimalPlaces,
}).format(amount);
} catch (error) {
return amount.toFixed(decimalPlaces);
}
};
// Parse currency string to number
export const parseCurrency = (
currencyString: string,
currencyCode: CurrencyCode = 'EUR'
): number => {
if (!currencyString || typeof currencyString !== 'string') {
return 0;
}
const config = CURRENCY_CONFIG[currencyCode];
// Remove currency symbols and spaces
const cleanString = currencyString
.replace(new RegExp(`[${config.symbol}\\s]`, 'g'), '')
.replace(/,/g, '.') // Replace comma with dot for decimal separator
.trim();
const parsed = parseFloat(cleanString);
return isNaN(parsed) ? 0 : parsed;
};
// Add two currency amounts
export const addCurrency = (amount1: number, amount2: number): number => {
if (typeof amount1 !== 'number' || typeof amount2 !== 'number') {
return 0;
}
// Use proper decimal arithmetic to avoid floating point errors
return Math.round((amount1 + amount2) * 100) / 100;
};
// Subtract two currency amounts
export const subtractCurrency = (amount1: number, amount2: number): number => {
if (typeof amount1 !== 'number' || typeof amount2 !== 'number') {
return 0;
}
return Math.round((amount1 - amount2) * 100) / 100;
};
// Multiply currency amount
export const multiplyCurrency = (amount: number, multiplier: number): number => {
if (typeof amount !== 'number' || typeof multiplier !== 'number') {
return 0;
}
return Math.round(amount * multiplier * 100) / 100;
};
// Divide currency amount
export const divideCurrency = (amount: number, divisor: number): number => {
if (typeof amount !== 'number' || typeof divisor !== 'number' || divisor === 0) {
return 0;
}
return Math.round((amount / divisor) * 100) / 100;
};
// Calculate percentage of amount
export const calculatePercentage = (amount: number, percentage: number): number => {
if (typeof amount !== 'number' || typeof percentage !== 'number') {
return 0;
}
return multiplyCurrency(amount, percentage / 100);
};
// Add percentage to amount
export const addPercentage = (amount: number, percentage: number): number => {
const percentageAmount = calculatePercentage(amount, percentage);
return addCurrency(amount, percentageAmount);
};
// Subtract percentage from amount
export const subtractPercentage = (amount: number, percentage: number): number => {
const percentageAmount = calculatePercentage(amount, percentage);
return subtractCurrency(amount, percentageAmount);
};
// Calculate tax amount
export const calculateTax = (amount: number, taxRate: number): number => {
return calculatePercentage(amount, taxRate);
};
// Calculate amount before tax (gross to net)
export const calculateNetAmount = (grossAmount: number, taxRate: number): number => {
if (typeof grossAmount !== 'number' || typeof taxRate !== 'number') {
return 0;
}
return divideCurrency(grossAmount, 1 + (taxRate / 100));
};
// Calculate amount with tax (net to gross)
export const calculateGrossAmount = (netAmount: number, taxRate: number): number => {
if (typeof netAmount !== 'number' || typeof taxRate !== 'number') {
return 0;
}
return multiplyCurrency(netAmount, 1 + (taxRate / 100));
};
// Calculate discount amount
export const calculateDiscount = (amount: number, discountRate: number): number => {
return calculatePercentage(amount, discountRate);
};
// Apply discount to amount
export const applyDiscount = (amount: number, discountRate: number): number => {
const discountAmount = calculateDiscount(amount, discountRate);
return subtractCurrency(amount, discountAmount);
};
// Calculate profit margin
export const calculateProfitMargin = (revenue: number, cost: number): number => {
if (typeof revenue !== 'number' || typeof cost !== 'number' || revenue === 0) {
return 0;
}
const profit = subtractCurrency(revenue, cost);
return Math.round((profit / revenue) * 10000) / 100; // Return as percentage with 2 decimal places
};
// Calculate markup
export const calculateMarkup = (cost: number, markupPercentage: number): number => {
return addPercentage(cost, markupPercentage);
};
// Calculate selling price from cost and desired margin
export const calculateSellingPrice = (cost: number, marginPercentage: number): number => {
if (typeof cost !== 'number' || typeof marginPercentage !== 'number' || marginPercentage >= 100) {
return cost;
}
return divideCurrency(cost, 1 - (marginPercentage / 100));
};
// Calculate cost from selling price and margin
export const calculateCostFromMargin = (sellingPrice: number, marginPercentage: number): number => {
if (typeof sellingPrice !== 'number' || typeof marginPercentage !== 'number') {
return 0;
}
return multiplyCurrency(sellingPrice, 1 - (marginPercentage / 100));
};
// Format currency range (e.g., "€10 - €20")
export const formatCurrencyRange = (
minAmount: number,
maxAmount: number,
currencyCode: CurrencyCode = 'EUR'
): string => {
if (typeof minAmount !== 'number' || typeof maxAmount !== 'number') {
return formatCurrency(0, currencyCode);
}
if (minAmount === maxAmount) {
return formatCurrency(minAmount, currencyCode);
}
const formattedMin = formatCurrency(minAmount, currencyCode);
const formattedMax = formatCurrency(maxAmount, currencyCode);
return `${formattedMin} - ${formattedMax}`;
};
// Round currency to nearest cent
export const roundCurrency = (amount: number): number => {
if (typeof amount !== 'number') {
return 0;
}
return Math.round(amount * 100) / 100;
};
// Check if amount is valid currency value
export const isValidCurrencyAmount = (amount: any): boolean => {
return typeof amount === 'number' && !isNaN(amount) && isFinite(amount) && amount >= 0;
};
// Convert between currencies (simplified - in real app, use exchange rates API)
export const convertCurrency = (
amount: number,
fromCurrency: CurrencyCode,
toCurrency: CurrencyCode,
exchangeRate: number = 1
): number => {
if (!isValidCurrencyAmount(amount) || typeof exchangeRate !== 'number') {
return 0;
}
if (fromCurrency === toCurrency) {
return amount;
}
return multiplyCurrency(amount, exchangeRate);
};
// Format currency for input fields (without currency symbol)
export const formatCurrencyInput = (amount: number | string): string => {
if (typeof amount === 'string') {
const parsed = parseCurrency(amount);
return parsed === 0 && amount !== '0' ? amount : formatCurrencyAmount(parsed);
}
if (typeof amount === 'number' && !isNaN(amount)) {
return formatCurrencyAmount(amount);
}
return '';
};
// Calculate total from array of amounts
export const calculateTotal = (amounts: number[]): number => {
if (!Array.isArray(amounts)) {
return 0;
}
return amounts.reduce((total, amount) => {
if (isValidCurrencyAmount(amount)) {
return addCurrency(total, amount);
}
return total;
}, 0);
};
// Calculate average from array of amounts
export const calculateAverage = (amounts: number[]): number => {
if (!Array.isArray(amounts) || amounts.length === 0) {
return 0;
}
const validAmounts = amounts.filter(isValidCurrencyAmount);
if (validAmounts.length === 0) {
return 0;
}
const total = calculateTotal(validAmounts);
return divideCurrency(total, validAmounts.length);
};
// Find minimum amount in array
export const findMinimumAmount = (amounts: number[]): number => {
if (!Array.isArray(amounts) || amounts.length === 0) {
return 0;
}
const validAmounts = amounts.filter(isValidCurrencyAmount);
return validAmounts.length > 0 ? Math.min(...validAmounts) : 0;
};
// Find maximum amount in array
export const findMaximumAmount = (amounts: number[]): number => {
if (!Array.isArray(amounts) || amounts.length === 0) {
return 0;
}
const validAmounts = amounts.filter(isValidCurrencyAmount);
return validAmounts.length > 0 ? Math.max(...validAmounts) : 0;
};
// Format compact currency (e.g., €1.2K, €1.5M)
export const formatCompactCurrency = (
amount: number,
currencyCode: CurrencyCode = 'EUR'
): string => {
if (!isValidCurrencyAmount(amount)) {
return formatCurrency(0, currencyCode);
}
const config = CURRENCY_CONFIG[currencyCode];
try {
return new Intl.NumberFormat(config.locale, {
style: 'currency',
currency: config.code,
notation: 'compact',
compactDisplay: 'short',
maximumFractionDigits: 1,
}).format(amount);
} catch (error) {
// Fallback for browsers that don't support compact notation
if (amount >= 1000000) {
return `${config.symbol}${(amount / 1000000).toFixed(1)}M`;
} else if (amount >= 1000) {
return `${config.symbol}${(amount / 1000).toFixed(1)}K`;
} else {
return formatCurrency(amount, currencyCode);
}
}
};
// Calculate compound interest
export const calculateCompoundInterest = (
principal: number,
rate: number,
time: number,
compound: number = 1
): number => {
if (!isValidCurrencyAmount(principal) || typeof rate !== 'number' || typeof time !== 'number') {
return principal;
}
const amount = principal * Math.pow(1 + (rate / 100) / compound, compound * time);
return roundCurrency(amount);
};
// Currency formatting options for different contexts
export const getCurrencyFormatOptions = (context: 'display' | 'input' | 'compact' | 'accounting') => {
const baseOptions = {
style: 'currency' as const,
currency: 'EUR' as const,
};
switch (context) {
case 'display':
return {
...baseOptions,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
};
case 'input':
return {
...baseOptions,
minimumFractionDigits: 0,
maximumFractionDigits: 2,
};
case 'compact':
return {
...baseOptions,
notation: 'compact' as const,
compactDisplay: 'short' as const,
maximumFractionDigits: 1,
};
case 'accounting':
return {
...baseOptions,
currencySign: 'accounting' as const,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
};
default:
return baseOptions;
}
};