From 4fbffad878236c32b7b6553bbb0787889c0130e1 Mon Sep 17 00:00:00 2001 From: Denis Shatskiy Date: Wed, 30 Jul 2025 04:56:49 +0700 Subject: [PATCH] feat: Add currency support to billing data and enhance BillingManager with exchange rate fetching and formatting functions --- example-servers-billing.json | 10 ++ frontend/src/BillingManager.jsx | 159 ++++++++++++++++++++++++++++---- 2 files changed, 152 insertions(+), 17 deletions(-) diff --git a/example-servers-billing.json b/example-servers-billing.json index 90887e2..4f802bd 100644 --- a/example-servers-billing.json +++ b/example-servers-billing.json @@ -7,9 +7,11 @@ "provider": "VDSINA", "loginUrl": "cp.vdsina.com", "monthlyCost": 12.00, + "monthlyCostCurrency": "USD", "nextPaymentDate": "2026-04-25", "lastPaymentDate": "2025-05-05", "lastPaymentAmount": 12.00, + "lastPaymentCurrency": "USD", "status": "active", "notes": "" }, @@ -21,9 +23,11 @@ "provider": "Macloud", "loginUrl": "cp.macloud.ru", "monthlyCost": 7.00, + "monthlyCostCurrency": "USD", "nextPaymentDate": "2025-09-02", "lastPaymentDate": "2025-02-03", "lastPaymentAmount": 7.00, + "lastPaymentCurrency": "USD", "status": "active", "notes": "" }, @@ -35,9 +39,11 @@ "provider": "Hosting VDS", "loginUrl": "my.hosting-vds.com/", "monthlyCost": 11.95, + "monthlyCostCurrency": "USD", "nextPaymentDate": "2025-12-21", "lastPaymentDate": "2025-06-13", "lastPaymentAmount": 10.00, + "lastPaymentCurrency": "USD", "status": "active", "notes": "" }, @@ -49,9 +55,11 @@ "provider": "Waicore", "loginUrl": "my.waicore.com/billmgr", "monthlyCost": 9.60, + "monthlyCostCurrency": "USD", "nextPaymentDate": "2026-04-25", "lastPaymentDate": "2025-04-25", "lastPaymentAmount": 9.60, + "lastPaymentCurrency": "USD", "status": "active", "notes": "" }, @@ -63,9 +71,11 @@ "provider": "IHOR", "loginUrl": "billing.ihor-hosting.ru/billmgr", "monthlyCost": 10.00, + "monthlyCostCurrency": "USD", "nextPaymentDate": "2025-09-10", "lastPaymentDate": "2025-06-25", "lastPaymentAmount": 10.00, + "lastPaymentCurrency": "USD", "status": "active", "notes": "" } diff --git a/frontend/src/BillingManager.jsx b/frontend/src/BillingManager.jsx index 9df2168..57e8e53 100644 --- a/frontend/src/BillingManager.jsx +++ b/frontend/src/BillingManager.jsx @@ -37,6 +37,14 @@ function BillingManager() { const [selectedItem, setSelectedItem] = useState(null); const [editingItem, setEditingItem] = useState({}); + // Состояние для курсов валют + const [exchangeRates, setExchangeRates] = useState({ + USD: 1, + EUR: 1, + RUB: 1 + }); + const [ratesLoading, setRatesLoading] = useState(false); + // Новый элемент для добавления const [newItem, setNewItem] = useState({ hostName: '', @@ -45,17 +53,45 @@ function BillingManager() { provider: '', loginUrl: '', monthlyCost: 0, + monthlyCostCurrency: 'USD', nextPaymentDate: '', lastPaymentDate: '', lastPaymentAmount: 0, + lastPaymentCurrency: 'USD', status: 'active', notes: '' }); useEffect(() => { fetchBillingData(); + fetchExchangeRates(); }, []); + const fetchExchangeRates = async () => { + setRatesLoading(true); + try { + // Используем бесплатный API для получения курсов валют + const response = await axios.get('https://api.exchangerate-api.com/v4/latest/USD'); + const rates = response.data.rates; + + setExchangeRates({ + USD: 1, + EUR: rates.EUR || 1, + RUB: rates.RUB || 1 + }); + } catch (error) { + console.error('Ошибка при загрузке курсов валют:', error); + // Используем примерные курсы если API недоступен + setExchangeRates({ + USD: 1, + EUR: 0.85, + RUB: 95 + }); + } finally { + setRatesLoading(false); + } + }; + const fetchBillingData = async () => { setLoading(true); try { @@ -75,9 +111,11 @@ function BillingManager() { "provider": "VDSINA", "loginUrl": "cp.vdsina.com", "monthlyCost": 12.00, + "monthlyCostCurrency": "USD", "nextPaymentDate": "2026-04-25", "lastPaymentDate": "2025-05-05", "lastPaymentAmount": 12.00, + "lastPaymentCurrency": "USD", "status": "active", "notes": "" }, @@ -89,9 +127,11 @@ function BillingManager() { "provider": "Macloud", "loginUrl": "cp.macloud.ru", "monthlyCost": 7.00, + "monthlyCostCurrency": "USD", "nextPaymentDate": "2025-09-02", "lastPaymentDate": "2025-02-03", "lastPaymentAmount": 7.00, + "lastPaymentCurrency": "USD", "status": "active", "notes": "" }, @@ -103,9 +143,11 @@ function BillingManager() { "provider": "Hosting VDS", "loginUrl": "my.hosting-vds.com/", "monthlyCost": 11.95, + "monthlyCostCurrency": "USD", "nextPaymentDate": "2025-12-21", "lastPaymentDate": "2025-06-13", "lastPaymentAmount": 10.00, + "lastPaymentCurrency": "USD", "status": "active", "notes": "" }, @@ -117,9 +159,11 @@ function BillingManager() { "provider": "Waicore", "loginUrl": "my.waicore.com/billmgr", "monthlyCost": 9.60, + "monthlyCostCurrency": "USD", "nextPaymentDate": "2026-04-25", "lastPaymentDate": "2025-04-25", "lastPaymentAmount": 9.60, + "lastPaymentCurrency": "USD", "status": "active", "notes": "" }, @@ -131,9 +175,11 @@ function BillingManager() { "provider": "IHOR", "loginUrl": "billing.ihor-hosting.ru/billmgr", "monthlyCost": 10.00, + "monthlyCostCurrency": "USD", "nextPaymentDate": "2025-09-10", "lastPaymentDate": "2025-06-25", "lastPaymentAmount": 10.00, + "lastPaymentCurrency": "USD", "status": "active", "notes": "" } @@ -247,8 +293,14 @@ function BillingManager() { const totalPages = Math.ceil(filteredData.length / pageSize); // Статистика - const totalMonthlyCost = billingData.reduce((sum, item) => sum + item.monthlyCost, 0); - const totalLastPayments = billingData.reduce((sum, item) => sum + item.lastPaymentAmount, 0); + const totalMonthlyCost = billingData.reduce((sum, item) => { + const currency = item.monthlyCostCurrency || 'USD'; + return sum + convertToRUB(item.monthlyCost, currency); + }, 0); + const totalLastPayments = billingData.reduce((sum, item) => { + const currency = item.lastPaymentCurrency || 'USD'; + return sum + convertToRUB(item.lastPaymentAmount, currency); + }, 0); const activeProviders = [...new Set(billingData.map(item => item.provider))].length; const activeNodes = billingData.filter(item => item.status === 'active').length; @@ -274,13 +326,26 @@ function BillingManager() { }); } - function formatCurrency(amount) { + function formatCurrency(amount, currency = 'USD') { return new Intl.NumberFormat('en-US', { style: 'currency', - currency: 'USD' + currency: currency }).format(amount); } + function convertToRUB(amount, currency) { + const rate = exchangeRates[currency] || 1; + return amount * rate; + } + + function formatCurrencyInRUB(amount, currency) { + const rubAmount = convertToRUB(amount, currency); + return new Intl.NumberFormat('ru-RU', { + style: 'currency', + currency: 'RUB' + }).format(rubAmount); + } + function formatPurpose(purpose) { const purposeMap = { 'relay': 'Relay VPS', @@ -375,7 +440,7 @@ function BillingManager() {
- {formatCurrency(totalLastPayments)} + {formatCurrencyInRUB(totalLastPayments, 'USD')}
всего платежей
@@ -390,7 +455,7 @@ function BillingManager() {
- {formatCurrency(totalMonthlyCost)} + {formatCurrencyInRUB(totalMonthlyCost, 'USD')}
суммарных трат
@@ -492,7 +557,10 @@ function BillingManager() { {providers.map(provider => { const providerItems = billingData.filter(item => item.provider === provider); - const totalCost = providerItems.reduce((sum, item) => sum + item.monthlyCost, 0); + const totalCost = providerItems.reduce((sum, item) => { + const currency = item.monthlyCostCurrency || 'USD'; + return sum + convertToRUB(item.monthlyCost, currency); + }, 0); const servers = providerItems.map(item => formatPurpose(item.purpose)).join(', '); return ( @@ -509,7 +577,7 @@ function BillingManager() { - {formatCurrency(totalCost)} + {formatCurrencyInRUB(totalCost, 'USD')}
@@ -569,7 +637,14 @@ function BillingManager() { {item.hostName} {formatDate(item.lastPaymentDate)} - {formatCurrency(item.lastPaymentAmount)} + +
+
{formatCurrency(item.lastPaymentAmount, item.lastPaymentCurrency || 'USD')}
+ + {formatCurrencyInRUB(item.lastPaymentAmount, item.lastPaymentCurrency || 'USD')} + +
+
-
- +
+
+
+ + +
handleChange('lastPaymentDate', e.target.value)} />
-
- +
+ handleChange('lastPaymentAmount', parseFloat(e.target.value) || 0)} />
+
+ + +
+
+ + +
handleChange('lastPaymentDate', e.target.value)} />
-
- +
+ handleChange('lastPaymentAmount', parseFloat(e.target.value) || 0)} />
+
+ + +