Implement currency symbol handling and update related components

This commit is contained in:
Bharat Parsiya
2026-02-11 10:26:18 +05:30
parent 38641e91e2
commit ced51e06fb
6 changed files with 119 additions and 39 deletions
@@ -46,7 +46,7 @@ const mockArchives = [
];
const mockSettings = {
currency: '$',
currency: 'USD',
check_updates: false,
check_printer_firmware: false,
};
@@ -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);
});
});
+2 -1
View File
@@ -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({
+42 -36
View File
@@ -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() {
<h2 className="text-lg font-semibold text-white">{t('settings.costTracking')}</h2>
</CardHeader>
<CardContent className="space-y-4">
<div>
<label className="block text-sm text-bambu-gray mb-1">
Default filament cost (per kg)
</label>
<input
type="number"
step="0.01"
min="0"
value={localSettings.default_filament_cost}
onChange={(e) =>
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"
/>
</div>
<div>
<label className="block text-sm text-bambu-gray mb-1">Currency</label>
<select
@@ -1579,30 +1565,50 @@ export function SettingsPage() {
onChange={(e) => updateSetting('currency', e.target.value)}
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"
>
<option value="USD">USD ($)</option>
<option value="EUR">EUR (€)</option>
<option value="GBP">GBP (£)</option>
<option value="CHF">CHF (Fr.)</option>
<option value="JPY">JPY (¥)</option>
<option value="CNY">CNY (¥)</option>
<option value="CAD">CAD ($)</option>
<option value="AUD">AUD ($)</option>
{SUPPORTED_CURRENCIES.map((c) => (
<option key={c.code} value={c.code}>{c.label}</option>
))}
</select>
</div>
<div>
<label className="block text-sm text-bambu-gray mb-1">
Default filament cost (per kg)
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-bambu-gray text-sm pointer-events-none">
{getCurrencySymbol(localSettings.currency)}
</span>
<input
type="number"
step="0.01"
min="0"
value={localSettings.default_filament_cost}
onChange={(e) =>
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"
/>
</div>
</div>
<div>
<label className="block text-sm text-bambu-gray mb-1">
Electricity cost per kWh
</label>
<input
type="number"
step="0.01"
min="0"
value={localSettings.energy_cost_per_kwh}
onChange={(e) =>
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"
/>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-bambu-gray text-sm pointer-events-none">
{getCurrencySymbol(localSettings.currency)}
</span>
<input
type="number"
step="0.01"
min="0"
value={localSettings.energy_cost_per_kwh}
onChange={(e) =>
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"
/>
</div>
</div>
<div>
<label className="block text-sm text-bambu-gray mb-1">
@@ -2532,7 +2538,7 @@ export function SettingsPage() {
</div>
{(localSettings?.energy_cost_per_kwh ?? 0) > 0 && (
<div className="text-xs text-bambu-gray mt-1">
~{(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')}
</div>
)}
</div>
@@ -2549,7 +2555,7 @@ export function SettingsPage() {
</div>
{(localSettings?.energy_cost_per_kwh ?? 0) > 0 && (
<div className="text-xs text-bambu-gray mt-1">
~{(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')}
</div>
)}
</div>
@@ -2566,7 +2572,7 @@ export function SettingsPage() {
</div>
{(localSettings?.energy_cost_per_kwh ?? 0) > 0 && (
<div className="text-xs text-bambu-gray mt-1">
~{(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')}
</div>
)}
</div>
+2 -1
View File
@@ -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) || [];
+29
View File
@@ -0,0 +1,29 @@
const CURRENCY_SYMBOLS: Record<string, string> = {
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;