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"
+ />
+
+
{(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;