import { prisma as db } from './db.server'; export interface AppSettings { dateFormat: 'ar-SA' | 'en-US'; currency: string; numberFormat: 'ar-SA' | 'en-US'; currencySymbol: string; dateDisplayFormat: string; } export interface SettingItem { id: number; key: string; value: string; description?: string; createdDate: Date; updateDate: Date; } // Default settings const DEFAULT_SETTINGS: AppSettings = { dateFormat: 'ar-SA', currency: 'JOD', numberFormat: 'ar-SA', currencySymbol: 'د.أ', dateDisplayFormat: 'dd/MM/yyyy' }; // Get all settings as a typed object export async function getAppSettings(): Promise { try { const settings = await db.settings.findMany(); const settingsMap = settings.reduce((acc, setting) => { acc[setting.key] = setting.value; return acc; }, {} as Record); return { dateFormat: (settingsMap.dateFormat as 'ar-SA' | 'en-US') || DEFAULT_SETTINGS.dateFormat, currency: settingsMap.currency || DEFAULT_SETTINGS.currency, numberFormat: (settingsMap.numberFormat as 'ar-SA' | 'en-US') || DEFAULT_SETTINGS.numberFormat, currencySymbol: settingsMap.currencySymbol || DEFAULT_SETTINGS.currencySymbol, dateDisplayFormat: settingsMap.dateDisplayFormat || DEFAULT_SETTINGS.dateDisplayFormat }; } catch (error) { console.error('Error fetching settings:', error); return DEFAULT_SETTINGS; } } // Get a specific setting by key export async function getSetting(key: string): Promise { try { const setting = await db.settings.findUnique({ where: { key } }); return setting?.value || null; } catch (error) { console.error(`Error fetching setting ${key}:`, error); return null; } } // Update or create a setting export async function updateSetting(key: string, value: string, description?: string): Promise { try { return await db.settings.upsert({ where: { key }, update: { value, description: description || undefined, updateDate: new Date() }, create: { key, value, description: description || undefined } }); } catch (error) { console.error(`Error updating setting ${key}:`, error); throw new Error(`Failed to update setting: ${key}`); } } // Update multiple settings at once export async function updateSettings(settings: Partial): Promise { try { const updates = Object.entries(settings).map(([key, value]) => updateSetting(key, value as string) ); await Promise.all(updates); } catch (error) { console.error('Error updating settings:', error); throw new Error('Failed to update settings'); } } // Get all settings for admin management export async function getAllSettings(): Promise { try { return await db.settings.findMany({ orderBy: { key: 'asc' } }); } catch (error) { console.error('Error fetching all settings:', error); throw new Error('Failed to fetch settings'); } } // Initialize default settings if they don't exist export async function initializeDefaultSettings(): Promise { try { const existingSettings = await db.settings.findMany(); const existingKeys = new Set(existingSettings.map(s => s.key)); const defaultEntries = [ { key: 'dateFormat', value: 'ar-SA', description: 'Date format locale (ar-SA or en-US)' }, { key: 'currency', value: 'JOD', description: 'Currency code (JOD, USD, EUR, etc.)' }, { key: 'numberFormat', value: 'ar-SA', description: 'Number format locale (ar-SA or en-US)' }, { key: 'currencySymbol', value: 'د.أ', description: 'Currency symbol display' }, { key: 'dateDisplayFormat', value: 'dd/MM/yyyy', description: 'Date display format pattern' } ]; const newSettings = defaultEntries.filter(entry => !existingKeys.has(entry.key)); if (newSettings.length > 0) { await db.settings.createMany({ data: newSettings }); } } catch (error) { console.error('Error initializing default settings:', error); // Don't throw error, just log it and continue with defaults } } // Formatting utilities using settings export class SettingsFormatter { constructor(private settings: AppSettings) {} // Format number according to settings formatNumber(value: number): string { return value.toLocaleString(this.settings.numberFormat); } // Format currency according to settings formatCurrency(value: number): string { const formatted = value.toLocaleString(this.settings.numberFormat, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); return `${formatted} ${this.settings.currencySymbol}`; } // Format date according to settings formatDate(date: Date | string): string { const dateObj = typeof date === 'string' ? new Date(date) : date; return this.formatDateWithPattern(dateObj, this.settings.dateDisplayFormat); } // Format datetime according to settings formatDateTime(date: Date | string): string { const dateObj = typeof date === 'string' ? new Date(date) : date; const datePart = this.formatDateWithPattern(dateObj, this.settings.dateDisplayFormat); const timePart = dateObj.toLocaleTimeString(this.settings.dateFormat, { hour: '2-digit', minute: '2-digit' }); return `${datePart} ${timePart}`; } // Helper method to format date with custom pattern private formatDateWithPattern(date: Date, pattern: string): string { const day = date.getDate(); const month = date.getMonth() + 1; const year = date.getFullYear(); // Format numbers according to locale const formatNumber = (num: number, padLength: number = 2): string => { const padded = num.toString().padStart(padLength, '0'); return this.settings.numberFormat === 'ar-SA' ? this.convertToArabicNumerals(padded) : padded; }; return pattern .replace(/yyyy/g, formatNumber(year, 4)) .replace(/yy/g, formatNumber(year % 100, 2)) .replace(/MM/g, formatNumber(month, 2)) .replace(/M/g, formatNumber(month, 1)) .replace(/dd/g, formatNumber(day, 2)) .replace(/d/g, formatNumber(day, 1)); } // Helper method to convert Western numerals to Arabic numerals private convertToArabicNumerals(str: string): string { const arabicNumerals = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩']; return str.replace(/[0-9]/g, (digit) => arabicNumerals[parseInt(digit)]); } } // Create formatter instance with current settings export async function createFormatter(): Promise { const settings = await getAppSettings(); return new SettingsFormatter(settings); }