209 lines
6.6 KiB
TypeScript
209 lines
6.6 KiB
TypeScript
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<AppSettings> {
|
||
try {
|
||
const settings = await db.settings.findMany();
|
||
|
||
const settingsMap = settings.reduce((acc, setting) => {
|
||
acc[setting.key] = setting.value;
|
||
return acc;
|
||
}, {} as Record<string, string>);
|
||
|
||
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<string | null> {
|
||
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<SettingItem> {
|
||
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<AppSettings>): Promise<void> {
|
||
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<SettingItem[]> {
|
||
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<void> {
|
||
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<SettingsFormatter> {
|
||
const settings = await getAppSettings();
|
||
return new SettingsFormatter(settings);
|
||
} |