From 6c7142d45d52a5d23d051e72c2936f51aea098a2 Mon Sep 17 00:00:00 2001 From: Denis Shatskiy Date: Fri, 8 Aug 2025 11:21:22 +0700 Subject: [PATCH] feat: Add payment modal to BillingManager for streamlined payment entry and management --- frontend/src/BillingManager.jsx | 222 +++++++++++++++++++------------- frontend/src/Dashboard.jsx | 12 +- 2 files changed, 140 insertions(+), 94 deletions(-) diff --git a/frontend/src/BillingManager.jsx b/frontend/src/BillingManager.jsx index 8de05ee..2c030c0 100644 --- a/frontend/src/BillingManager.jsx +++ b/frontend/src/BillingManager.jsx @@ -45,6 +45,16 @@ function BillingManager() { const [selectedItem, setSelectedItem] = useState(null); const [editingItem, setEditingItem] = useState({}); + // Модалка добавления платежа, привязанного к серверу + const [showPaymentModal, setShowPaymentModal] = useState(false); + const [paymentDraft, setPaymentDraft] = useState({ + serverId: '', + date: '', + amount: 0, + currency: 'USD', + note: '' + }); + // Состояние для курсов валют const [exchangeRates, setExchangeRates] = useState({ USD: 1, @@ -102,7 +112,17 @@ function BillingManager() { setLoading(true); try { const response = await axios.get(`${API_URL}/billing`); - setBillingData(response.data); + const normalized = (response.data || []).map((item, idx) => ({ + id: item.id ?? String(idx + 1), + payments: Array.isArray(item.payments) + ? item.payments + : (item.lastPaymentDate && item.lastPaymentAmount + ? [{ date: item.lastPaymentDate, amount: item.lastPaymentAmount, currency: item.lastPaymentCurrency || 'USD', note: 'Импортировано' }] + : [] + ), + ...item, + })); + setBillingData(normalized); setError(''); } catch (err) { console.error('Ошибка при загрузке данных биллинга:', err); @@ -686,22 +706,14 @@ function BillingManager() { @@ -806,57 +801,35 @@ function BillingManager() { Имя хостера - Дата платежа + Дата Сумма Валюта - Статус - Действия + Комментарий - {billingData - .filter(item => item.lastPaymentDate && item.lastPaymentAmount) - .sort((a, b) => new Date(b.lastPaymentDate) - new Date(a.lastPaymentDate)) - .slice(0, 10) - .map(item => ( - + {(() => { + const rows = billingData.flatMap(server => (server.payments || []).map((p, idx) => ({ server, ...p, _key: `${server.id}-${idx}` }))).sort((a,b) => new Date(b.date) - new Date(a.date)); + if (rows.length === 0) { + return null; + } + return rows.slice(0, 20).map(r => ( + -
{item.hostName}
-
{item.provider}
+
{r.server.hostName}
+
{r.server.provider}
- {formatDate(item.lastPaymentDate)} + {formatDate(r.date)} -
- {formatCurrency(item.lastPaymentAmount, item.lastPaymentCurrency)} -
-
- {formatCurrencyInRUB(item.lastPaymentAmount, item.lastPaymentCurrency)} -
- - - - {item.lastPaymentCurrency || 'USD'} - - - - - Оплачен - - - -
- -
+
{formatCurrency(r.amount, r.currency)}
+
{formatCurrencyInRUB(r.amount, r.currency)}
+ {r.currency || 'USD'} + {r.note || ''} - ))} - {billingData.filter(item => item.lastPaymentDate && item.lastPaymentAmount).length === 0 && ( + )); + })()} + {billingData.every(i => !i.payments || i.payments.length === 0) && (
@@ -922,6 +895,32 @@ function BillingManager() { onDelete={confirmDelete} onClose={() => setShowDeleteModal(false)} /> + {/* Модалка добавления платежа */} + {showPaymentModal && ( + setShowPaymentModal(false)} + onSubmit={() => { + if (!paymentDraft.serverId || !paymentDraft.date || !paymentDraft.amount) { + setError('Заполните сервер, дату и сумму.'); + setTimeout(() => setError(''), 3000); + return; + } + setBillingData(prev => prev.map(s => { + if (s.id !== paymentDraft.serverId) return s; + const payments = Array.isArray(s.payments) ? [...s.payments] : []; + payments.push({ date: paymentDraft.date, amount: paymentDraft.amount, currency: paymentDraft.currency || 'USD', note: paymentDraft.note || '' }); + return { ...s, payments, lastPaymentDate: paymentDraft.date, lastPaymentAmount: paymentDraft.amount, lastPaymentCurrency: paymentDraft.currency || s.monthlyCostCurrency || 'USD' }; + })); + setShowPaymentModal(false); + setSuccess('Платеж добавлен'); + setTimeout(() => setSuccess(''), 3000); + }} + /> + )}
); } @@ -930,8 +929,7 @@ function BillingManager() { function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) { if (!show) return null; - const isHistoricalPayment = item.hostName && !item.lastPaymentDate; - const modalTitle = isHistoricalPayment ? 'Добавить исторический платеж' : 'Добавить новый элемент'; + const modalTitle = 'Добавить новый элемент'; return (
@@ -942,13 +940,6 @@ function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
- {isHistoricalPayment && ( -
- - Исторический платеж: Вы добавляете платеж для существующего хоста "{item.hostName}". - Заполните информацию о платеже ниже. -
- )}
{/* Основная информация */}
@@ -1332,4 +1323,59 @@ function DeleteBillingModal({ show, item, onDelete, onClose }) { ); } -export default BillingManager; \ No newline at end of file +export default BillingManager; + +// Модалка добавления платежа +function AddPaymentModal({ show, servers, payment, onChange, onSubmit, onClose }) { + if (!show) return null; + const serverOptions = servers.map(s => ({ id: s.id, label: `${s.hostName} (${s.provider})` })); + return ( +
+
+
+
+
Добавить платеж
+ +
+
+
+ + +
+
+ + onChange({ ...payment, date: e.target.value })} /> +
+
+
+ + onChange({ ...payment, amount: parseFloat(e.target.value) || 0 })} /> +
+
+ + +
+
+
+ + onChange({ ...payment, note: e.target.value })} placeholder="Необязательно" /> +
+
+
+ + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/Dashboard.jsx b/frontend/src/Dashboard.jsx index 7aacd1c..a02bc73 100644 --- a/frontend/src/Dashboard.jsx +++ b/frontend/src/Dashboard.jsx @@ -212,8 +212,8 @@ function Dashboard() {
{/* Дополнительные метрики */} -
-
+
+
-
+
-
+
-
+