From 4655fc67cc8623d62afcf151051e82fc95e4b740 Mon Sep 17 00:00:00 2001 From: shats Date: Mon, 1 Dec 2025 16:07:28 +0700 Subject: [PATCH] feat: Add server selection functionality to BillingManager, including fetching server data and integrating server options in add/edit forms for improved billing management. --- frontend/src/BillingManager.jsx | 93 +++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/frontend/src/BillingManager.jsx b/frontend/src/BillingManager.jsx index 1c32705..398b00c 100644 --- a/frontend/src/BillingManager.jsx +++ b/frontend/src/BillingManager.jsx @@ -36,6 +36,7 @@ import BulkActionsBar from './components/BulkActionsBar.jsx'; function BillingManager() { const [billingData, setBillingData] = useState([]); + const [servers, setServers] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); @@ -106,12 +107,14 @@ function BillingManager() { lastPaymentAmount: 0, lastPaymentCurrency: 'USD', status: 'active', - notes: '' + notes: '', + serverId: '', }); useEffect(() => { fetchBillingData(); fetchExchangeRates(); + fetchServers(); }, []); const fetchExchangeRates = async () => { @@ -137,6 +140,16 @@ function BillingManager() { } }; + const fetchServers = async () => { + try { + const res = await api.get(`/servers`); + setServers(Array.isArray(res.data) ? res.data : []); + } catch (error) { + console.error('Ошибка при загрузке серверов для привязки биллинга:', error); + // связь необязательная, поэтому не показываем отдельную ошибку пользователю + } + }; + const fetchBillingData = async () => { setLoading(true); try { @@ -191,7 +204,8 @@ function BillingManager() { lastPaymentAmount: 0, lastPaymentCurrency: 'USD', status: 'active', - notes: '' + notes: '', + serverId: '', }); setShowAddModal(true); }; @@ -251,6 +265,13 @@ function BillingManager() { } }; + // Карта серверов по id для быстрой привязки + const serversById = new Map( + (servers || []) + .filter(s => s && s.id) + .map(s => [s.id, s]) + ); + // Вычисляем общую сумму последних платежей const totalLastPayments = billingData.reduce((sum, item) => { return sum + (item.lastPaymentAmount || 0); @@ -713,6 +734,7 @@ function BillingManager() { {sortOrder === 'asc' ? '↑' : '↓'} )} + Связанный сервер handleSort('provider')}> Провайдер {sortField === 'provider' && ( @@ -773,6 +795,29 @@ function BillingManager() { {item.country} + + {item.serverId ? ( + (() => { + const srv = serversById.get(item.serverId); + if (!srv) { + return Сервер не найден; + } + return ( +
+
{srv.ip}
+
+ {srv.dns || srv.provider || 'Без DNS'} +
+ + Открыть в разделе «Серверы» + +
+ ); + })() + ) : ( + Не привязан + )} +
{item.loginUrl && ( @@ -1372,6 +1417,7 @@ function BillingManager() { @@ -1390,6 +1436,7 @@ function BillingManager() { @@ -1528,7 +1575,7 @@ function BillingManager() { } // Компоненты форм для модальных окон -function AddBillingForm({ item, onItemChange }) { +function AddBillingForm({ item, onItemChange, servers }) { const purposeOptions = [ { value: '', label: 'Выберите назначение' }, { value: 'relay', label: 'Relay VPS' }, @@ -1551,6 +1598,14 @@ function AddBillingForm({ item, onItemChange }) { { value: 'RUB', label: 'RUB' } ]; + const serverOptions = [ + { value: '', label: 'Без привязки' }, + ...(Array.isArray(servers) ? servers : []).map((s) => ({ + value: s.id || s.ip, + label: `${s.ip}${s.dns ? ` (${s.dns})` : ''}${s.provider ? ` – ${s.provider}` : ''}`, + })), + ]; + return (
{/* Основная информация */} @@ -1566,6 +1621,17 @@ function AddBillingForm({ item, onItemChange }) { required />
+
+ onItemChange({ ...item, serverId: value })} + options={serverOptions} + helpText="Используется для связи с разделом «Серверы»" + /> +
({ + value: s.id || s.ip, + label: `${s.ip}${s.dns ? ` (${s.dns})` : ''}${s.provider ? ` – ${s.provider}` : ''}`, + })), + ]; + return (
{/* Основная информация */} @@ -1700,6 +1774,17 @@ function EditBillingForm({ item, onItemChange }) { required />
+
+ onItemChange({ ...item, serverId: value })} + options={serverOptions} + helpText="Используется для связи с разделом «Серверы»" + /> +