From ced51e06fb3a5c6850aec1bec21fc21a0fcd8f41 Mon Sep 17 00:00:00 2001 From: Bharat Parsiya Date: Wed, 11 Feb 2026 10:26:18 +0530 Subject: [PATCH] Implement currency symbol handling and update related components --- .../src/__tests__/pages/StatsPage.test.tsx | 2 +- frontend/src/__tests__/utils/currency.test.ts | 43 ++++++++++ frontend/src/pages/ProjectDetailPage.tsx | 3 +- frontend/src/pages/SettingsPage.tsx | 78 ++++++++++--------- frontend/src/pages/StatsPage.tsx | 3 +- frontend/src/utils/currency.ts | 29 +++++++ 6 files changed, 119 insertions(+), 39 deletions(-) create mode 100644 frontend/src/__tests__/utils/currency.test.ts create mode 100644 frontend/src/utils/currency.ts diff --git a/frontend/src/__tests__/pages/StatsPage.test.tsx b/frontend/src/__tests__/pages/StatsPage.test.tsx index d1c193be0..fdeaf501e 100644 --- a/frontend/src/__tests__/pages/StatsPage.test.tsx +++ b/frontend/src/__tests__/pages/StatsPage.test.tsx @@ -46,7 +46,7 @@ const mockArchives = [ ]; const mockSettings = { - currency: '$', + currency: 'USD', check_updates: false, check_printer_firmware: false, }; diff --git a/frontend/src/__tests__/utils/currency.test.ts b/frontend/src/__tests__/utils/currency.test.ts new file mode 100644 index 000000000..094787752 --- /dev/null +++ b/frontend/src/__tests__/utils/currency.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect } from 'vitest'; +import { getCurrencySymbol, SUPPORTED_CURRENCIES } from '../../utils/currency'; + +describe('getCurrencySymbol', () => { + it('returns $ for USD', () => { + expect(getCurrencySymbol('USD')).toBe('$'); + }); + + it('returns € for EUR', () => { + expect(getCurrencySymbol('EUR')).toBe('€'); + }); + + it('returns £ for GBP', () => { + expect(getCurrencySymbol('GBP')).toBe('£'); + }); + + it('returns ₹ for INR', () => { + expect(getCurrencySymbol('INR')).toBe('₹'); + }); + + it('returns HK$ for HKD', () => { + expect(getCurrencySymbol('HKD')).toBe('HK$'); + }); + + it('returns the code itself for unknown currencies', () => { + expect(getCurrencySymbol('XYZ')).toBe('XYZ'); + }); + + it('is case-insensitive', () => { + expect(getCurrencySymbol('usd')).toBe('$'); + expect(getCurrencySymbol('eur')).toBe('€'); + }); +}); + +describe('SUPPORTED_CURRENCIES', () => { + it('contains INR', () => { + expect(SUPPORTED_CURRENCIES.find((c) => c.code === 'INR')).toBeDefined(); + }); + + it('has 10 entries', () => { + expect(SUPPORTED_CURRENCIES).toHaveLength(10); + }); +}); diff --git a/frontend/src/pages/ProjectDetailPage.tsx b/frontend/src/pages/ProjectDetailPage.tsx index c78f78869..38b3e2aba 100644 --- a/frontend/src/pages/ProjectDetailPage.tsx +++ b/frontend/src/pages/ProjectDetailPage.tsx @@ -43,6 +43,7 @@ import { ConfirmModal } from '../components/ConfirmModal'; // Project edit modal (reused from ProjectsPage) import { ProjectModal } from './ProjectsPage'; +import { getCurrencySymbol } from '../utils/currency'; function formatDuration(hours: number): string { if (hours < 1) { @@ -251,7 +252,7 @@ export function ProjectDetailPage() { enabled: projectId > 0, }); - const currency = settings?.currency || '$'; + const currency = getCurrencySymbol(settings?.currency || 'USD'); const timeFormat: TimeFormat = settings?.time_format || 'system'; const updateMutation = useMutation({ diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index 438425b7a..703928706 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -5,6 +5,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom'; import { api } from '../api/client'; import { useAuth } from '../contexts/AuthContext'; import { formatDateOnly } from '../utils/date'; +import { getCurrencySymbol, SUPPORTED_CURRENCIES } from '../utils/currency'; import type { AppSettings, AppSettingsUpdate, SmartPlug, SmartPlugStatus, NotificationProvider, NotificationTemplate, UpdateStatus, GitHubBackupStatus, CloudAuthStatus, UserCreate, UserUpdate, UserResponse, Group, GroupCreate, GroupUpdate, Permission, PermissionCategory } from '../api/client'; import { Card, CardContent, CardHeader } from '../components/Card'; import { Button } from '../components/Button'; @@ -1557,21 +1558,6 @@ export function SettingsPage() {

{t('settings.costTracking')}

-
- - - updateSetting('default_filament_cost', parseFloat(e.target.value) || 0) - } - className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" - /> -
+
+ +
+ + {getCurrencySymbol(localSettings.currency)} + + + updateSetting('default_filament_cost', parseFloat(e.target.value) || 0) + } + className="w-full pl-8 pr-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" + /> +
+
- - updateSetting('energy_cost_per_kwh', parseFloat(e.target.value) || 0) - } - className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" - /> +
+ + {getCurrencySymbol(localSettings.currency)} + + + updateSetting('energy_cost_per_kwh', parseFloat(e.target.value) || 0) + } + className="w-full pl-8 pr-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" + /> +
{(localSettings?.energy_cost_per_kwh ?? 0) > 0 && (
- ~{(plugEnergySummary.totalToday * (localSettings?.energy_cost_per_kwh ?? 0)).toFixed(2)} {localSettings?.currency} + ~{(plugEnergySummary.totalToday * (localSettings?.energy_cost_per_kwh ?? 0)).toFixed(2)} {getCurrencySymbol(localSettings?.currency || 'USD')}
)} @@ -2549,7 +2555,7 @@ export function SettingsPage() { {(localSettings?.energy_cost_per_kwh ?? 0) > 0 && (
- ~{(plugEnergySummary.totalYesterday * (localSettings?.energy_cost_per_kwh ?? 0)).toFixed(2)} {localSettings?.currency} + ~{(plugEnergySummary.totalYesterday * (localSettings?.energy_cost_per_kwh ?? 0)).toFixed(2)} {getCurrencySymbol(localSettings?.currency || 'USD')}
)} @@ -2566,7 +2572,7 @@ export function SettingsPage() { {(localSettings?.energy_cost_per_kwh ?? 0) > 0 && (
- ~{(plugEnergySummary.totalLifetime * (localSettings?.energy_cost_per_kwh ?? 0)).toFixed(2)} {localSettings?.currency} + ~{(plugEnergySummary.totalLifetime * (localSettings?.energy_cost_per_kwh ?? 0)).toFixed(2)} {getCurrencySymbol(localSettings?.currency || 'USD')}
)} diff --git a/frontend/src/pages/StatsPage.tsx b/frontend/src/pages/StatsPage.tsx index c89e5754d..b0b7167d8 100644 --- a/frontend/src/pages/StatsPage.tsx +++ b/frontend/src/pages/StatsPage.tsx @@ -26,6 +26,7 @@ import { api } from '../api/client'; import { PrintCalendar } from '../components/PrintCalendar'; import { FilamentTrends } from '../components/FilamentTrends'; import { Dashboard, type DashboardWidget } from '../components/Dashboard'; +import { getCurrencySymbol } from '../utils/currency'; // Widget Components function QuickStatsWidget({ @@ -603,7 +604,7 @@ export function StatsPage() { } }; - const currency = settings?.currency || '$'; + const currency = getCurrencySymbol(settings?.currency || 'USD'); const printerMap = new Map(printers?.map((p) => [String(p.id), p.name]) || []); const printDates = archives?.map((a) => a.created_at) || []; diff --git a/frontend/src/utils/currency.ts b/frontend/src/utils/currency.ts new file mode 100644 index 000000000..29327bc4e --- /dev/null +++ b/frontend/src/utils/currency.ts @@ -0,0 +1,29 @@ +const CURRENCY_SYMBOLS: Record = { + USD: '$', + EUR: '€', + GBP: '£', + CHF: 'Fr.', + JPY: '¥', + CNY: '¥', + CAD: '$', + AUD: '$', + INR: '₹', + HKD: 'HK$', +}; + +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$)' }, +] as const;