Browse Source

Refactor date and currency utility functions

- Extract shared detectSystemDateFormat() helper to eliminate
  duplicated system locale detection in date.ts
- Derive SUPPORTED_CURRENCIES from CURRENCY_SYMBOLS map instead
  of maintaining a duplicate hardcoded list
- Consolidate redundant time-parsing regex paths into one
- Use applyTimeFormat() consistently in formatETA()
- Add exhaustive switch default for type safety
AneoPsy 2 months ago
parent
commit
15521deb0a
4 changed files with 58 additions and 149 deletions
  1. 4 28
      frontend/src/utils/currency.ts
  2. 53 120
      frontend/src/utils/date.ts
  3. 0 0
      static/assets/index-PuCtc4fr.js
  4. 1 1
      static/index.html

+ 4 - 28
frontend/src/utils/currency.ts

@@ -31,31 +31,7 @@ export function getCurrencySymbol(currencyCode: string): string {
   return CURRENCY_SYMBOLS[currencyCode.toUpperCase()] || currencyCode;
 }
 
-export const SUPPORTED_CURRENCIES = [
-  { code: 'USD', label: 'USD ($)' },
-  { code: 'EUR', label: 'EUR (€)' },
-  { code: 'GBP', label: 'GBP (£)' },
-  { code: 'CHF', label: 'CHF (Fr.)' },
-  { code: 'JPY', label: 'JPY (¥)' },
-  { code: 'CNY', label: 'CNY (¥)' },
-  { code: 'CAD', label: 'CAD ($)' },
-  { code: 'AUD', label: 'AUD ($)' },
-  { code: 'INR', label: 'INR (₹)' },
-  { code: 'HKD', label: 'HKD (HK$)' },
-  { code: 'KRW', label: 'KRW (₩)' },
-  { code: 'SEK', label: 'SEK (kr)' },
-  { code: 'NOK', label: 'NOK (kr)' },
-  { code: 'DKK', label: 'DKK (kr)' },
-  { code: 'PLN', label: 'PLN (zł)' },
-  { code: 'BRL', label: 'BRL (R$)' },
-  { code: 'TWD', label: 'TWD (NT$)' },
-  { code: 'SGD', label: 'SGD (S$)' },
-  { code: 'NZD', label: 'NZD (NZ$)' },
-  { code: 'MXN', label: 'MXN (MX$)' },
-  { code: 'CZK', label: 'CZK (Kč)' },
-  { code: 'THB', label: 'THB (฿)' },
-  { code: 'ZAR', label: 'ZAR (R)' },
-  { code: 'TRY', label: 'TRY (₺)' },
-  { code: 'RUB', label: 'RUB (₽)' },
-  { code: 'HUF', label: 'HUF (Ft)' },
-] as const;
+export const SUPPORTED_CURRENCIES = Object.entries(CURRENCY_SYMBOLS).map(([code, symbol]) => ({
+  code,
+  label: `${code} (${symbol})`,
+}));

+ 53 - 120
frontend/src/utils/date.ts

@@ -13,22 +13,12 @@ export type DateFormat = 'system' | 'us' | 'eu' | 'iso';
  * Get the date input placeholder based on format setting.
  */
 export function getDatePlaceholder(dateFormat: DateFormat = 'system'): string {
-  switch (dateFormat) {
-    case 'us':
-      return 'MM/DD/YYYY';
-    case 'eu':
-      return 'DD/MM/YYYY';
-    case 'iso':
-      return 'YYYY-MM-DD';
-    case 'system':
-    default: {
-      // Try to detect system format
-      const testDate = new Date(2000, 11, 31); // Dec 31, 2000
-      const formatted = testDate.toLocaleDateString();
-      if (formatted.startsWith('12')) return 'MM/DD/YYYY';
-      if (formatted.startsWith('31')) return 'DD/MM/YYYY';
-      return 'YYYY-MM-DD';
-    }
+  const resolved = dateFormat === 'system' ? detectSystemDateFormat() : dateFormat;
+  switch (resolved) {
+    case 'us': return 'MM/DD/YYYY';
+    case 'eu': return 'DD/MM/YYYY';
+    case 'iso': return 'YYYY-MM-DD';
+    default: return resolved satisfies never;
   }
 }
 
@@ -98,7 +88,6 @@ export function formatTimeInput(date: Date, timeFormat: TimeFormat = 'system'):
  * Split a date string by common separators (/, ., -).
  */
 function splitDateParts(value: string): string[] | null {
-  // Try common separators: /, ., -
   for (const sep of ['/', '.', '-']) {
     const parts = value.split(sep);
     if (parts.length === 3) return parts;
@@ -106,6 +95,13 @@ function splitDateParts(value: string): string[] | null {
   return null;
 }
 
+function detectSystemDateFormat(): 'us' | 'eu' | 'iso' {
+  const formatted = new Date(2000, 11, 31).toLocaleDateString();
+  if (formatted.startsWith('12')) return 'us';
+  if (formatted.startsWith('31')) return 'eu';
+  return 'iso';
+}
+
 /**
  * Parse a date string based on format setting.
  * Returns null if parsing fails.
@@ -114,77 +110,36 @@ function splitDateParts(value: string): string[] | null {
 export function parseDateInput(value: string, dateFormat: DateFormat = 'system'): Date | null {
   if (!value) return null;
 
+  const parts = splitDateParts(value);
+  if (!parts) return null;
+
+  const resolved = dateFormat === 'system' ? detectSystemDateFormat() : dateFormat;
   let day: number, month: number, year: number;
 
-  try {
-    switch (dateFormat) {
-      case 'us': {
-        // MM/DD/YYYY (also accepts . and - separators)
-        const parts = splitDateParts(value);
-        if (!parts) return null;
-        month = parseInt(parts[0], 10);
-        day = parseInt(parts[1], 10);
-        year = parseInt(parts[2], 10);
-        break;
-      }
-      case 'eu': {
-        // DD/MM/YYYY (also accepts . and - separators)
-        const parts = splitDateParts(value);
-        if (!parts) return null;
-        day = parseInt(parts[0], 10);
-        month = parseInt(parts[1], 10);
-        year = parseInt(parts[2], 10);
-        break;
-      }
-      case 'iso': {
-        // YYYY-MM-DD (also accepts . and / separators)
-        const parts = splitDateParts(value);
-        if (!parts) return null;
-        year = parseInt(parts[0], 10);
-        month = parseInt(parts[1], 10);
-        day = parseInt(parts[2], 10);
-        break;
-      }
-      case 'system':
-      default: {
-        // Detect system format and parse accordingly
-        const testDate = new Date(2000, 11, 31); // Dec 31, 2000
-        const formatted = testDate.toLocaleDateString();
-        const parts = splitDateParts(value);
-
-        if (parts) {
-          // Detect format from system locale
-          if (formatted.startsWith('12')) {
-            // US format: MM/DD/YYYY
-            month = parseInt(parts[0], 10);
-            day = parseInt(parts[1], 10);
-            year = parseInt(parts[2], 10);
-          } else if (formatted.startsWith('31')) {
-            // EU format: DD/MM/YYYY
-            day = parseInt(parts[0], 10);
-            month = parseInt(parts[1], 10);
-            year = parseInt(parts[2], 10);
-          } else {
-            // ISO format: YYYY-MM-DD
-            year = parseInt(parts[0], 10);
-            month = parseInt(parts[1], 10);
-            day = parseInt(parts[2], 10);
-          }
-          break;
-        }
-        return null;
-      }
-    }
+  switch (resolved) {
+    case 'us':
+      month = parseInt(parts[0], 10);
+      day = parseInt(parts[1], 10);
+      year = parseInt(parts[2], 10);
+      break;
+    case 'eu':
+      day = parseInt(parts[0], 10);
+      month = parseInt(parts[1], 10);
+      year = parseInt(parts[2], 10);
+      break;
+    case 'iso':
+      year = parseInt(parts[0], 10);
+      month = parseInt(parts[1], 10);
+      day = parseInt(parts[2], 10);
+      break;
+  }
 
-    if (isNaN(day) || isNaN(month) || isNaN(year)) return null;
-    if (month < 1 || month > 12) return null;
-    if (day < 1 || day > 31) return null;
-    if (year < 1900 || year > 2100) return null;
+  if (isNaN(day) || isNaN(month) || isNaN(year)) return null;
+  if (month < 1 || month > 12) return null;
+  if (day < 1 || day > 31) return null;
+  if (year < 1900 || year > 2100) return null;
 
-    return new Date(year, month - 1, day);
-  } catch {
-    return null;
-  }
+  return new Date(year, month - 1, day);
 }
 
 /**
@@ -194,41 +149,22 @@ export function parseDateInput(value: string, dateFormat: DateFormat = 'system')
 export function parseTimeInput(value: string): { hours: number; minutes: number } | null {
   if (!value) return null;
 
-  try {
-    const trimmed = value.trim().toUpperCase();
-
-    // Check for 12h format with AM/PM
-    const ampmMatch = trimmed.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)?$/i);
-    if (ampmMatch) {
-      let hours = parseInt(ampmMatch[1], 10);
-      const minutes = parseInt(ampmMatch[2], 10);
-      const ampm = ampmMatch[3]?.toUpperCase();
-
-      if (ampm === 'PM' && hours < 12) hours += 12;
-      if (ampm === 'AM' && hours === 12) hours = 0;
+  const trimmed = value.trim();
 
-      if (hours < 0 || hours > 23) return null;
-      if (minutes < 0 || minutes > 59) return null;
+  const match = trimmed.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)?$/i);
+  if (!match) return null;
 
-      return { hours, minutes };
-    }
-
-    // Try 24h format HH:MM
-    const match24 = trimmed.match(/^(\d{1,2}):(\d{2})$/);
-    if (match24) {
-      const hours = parseInt(match24[1], 10);
-      const minutes = parseInt(match24[2], 10);
+  let hours = parseInt(match[1], 10);
+  const minutes = parseInt(match[2], 10);
+  const ampm = match[3]?.toUpperCase();
 
-      if (hours < 0 || hours > 23) return null;
-      if (minutes < 0 || minutes > 59) return null;
+  if (ampm === 'PM' && hours < 12) hours += 12;
+  if (ampm === 'AM' && hours === 12) hours = 0;
 
-      return { hours, minutes };
-    }
+  if (hours < 0 || hours > 23) return null;
+  if (minutes < 0 || minutes > 59) return null;
 
-    return null;
-  } catch {
-    return null;
-  }
+  return { hours, minutes };
 }
 
 /**
@@ -387,21 +323,18 @@ export function formatTimeOnly(
  */
 export function formatETA(
   remainingMinutes: number,
-  timeFormat: 'system' | '12h' | '24h' = 'system',
+  timeFormat: TimeFormat = 'system',
   t?: (key: string) => string
 ): string {
   const now = new Date();
   const eta = new Date(now.getTime() + remainingMinutes * 60 * 1000);
 
-  const today = new Date();
+  const today = new Date(now);
   today.setHours(0, 0, 0, 0);
   const etaDay = new Date(eta);
   etaDay.setHours(0, 0, 0, 0);
 
-  const timeOptions: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' };
-  if (timeFormat === '12h') timeOptions.hour12 = true;
-  else if (timeFormat === '24h') timeOptions.hour12 = false;
-
+  const timeOptions = applyTimeFormat({ hour: '2-digit', minute: '2-digit' }, timeFormat);
   const timeStr = eta.toLocaleTimeString([], timeOptions);
   const dayDiff = Math.floor((etaDay.getTime() - today.getTime()) / 86400000);
 

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-PuCtc4fr.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-BNe8DBiX.js"></script>
+    <script type="module" crossorigin src="/assets/index-PuCtc4fr.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-D5I4wfky.css">
   </head>
   <body>

Some files were not shown because too many files changed in this diff