feat: Implement payment editing functionality in BillingManager for enhanced payment management
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m37s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m37s
This commit is contained in:
@@ -54,6 +54,7 @@ function BillingManager() {
|
|||||||
currency: 'USD',
|
currency: 'USD',
|
||||||
note: ''
|
note: ''
|
||||||
});
|
});
|
||||||
|
const [editingPayment, setEditingPayment] = useState(null); // { serverId, index }
|
||||||
|
|
||||||
// Состояние для курсов валют
|
// Состояние для курсов валют
|
||||||
const [exchangeRates, setExchangeRates] = useState({
|
const [exchangeRates, setExchangeRates] = useState({
|
||||||
@@ -866,11 +867,12 @@ function BillingManager() {
|
|||||||
<th>Сумма</th>
|
<th>Сумма</th>
|
||||||
<th>Валюта</th>
|
<th>Валюта</th>
|
||||||
<th>Комментарий</th>
|
<th>Комментарий</th>
|
||||||
|
<th>Действия</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{(() => {
|
{(() => {
|
||||||
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));
|
const rows = billingData.flatMap(server => (server.payments || []).map((p, idx) => ({ server, ...p, _key: `${server.id}-${idx}`, _index: idx }))).sort((a,b) => new Date(b.date) - new Date(a.date));
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -887,6 +889,27 @@ function BillingManager() {
|
|||||||
</td>
|
</td>
|
||||||
<td><span className="badge bg-blue-lt text-blue">{r.currency || 'USD'}</span></td>
|
<td><span className="badge bg-blue-lt text-blue">{r.currency || 'USD'}</span></td>
|
||||||
<td className="text-muted small">{r.note || ''}</td>
|
<td className="text-muted small">{r.note || ''}</td>
|
||||||
|
<td>
|
||||||
|
<div className="btn-list">
|
||||||
|
<button
|
||||||
|
className="btn btn-outline-primary btn-icon"
|
||||||
|
title="Редактировать платеж"
|
||||||
|
onClick={() => {
|
||||||
|
setPaymentDraft({
|
||||||
|
serverId: r.server.id,
|
||||||
|
date: r.date,
|
||||||
|
amount: r.amount,
|
||||||
|
currency: r.currency || 'USD',
|
||||||
|
note: r.note || ''
|
||||||
|
});
|
||||||
|
setEditingPayment({ serverId: r.server.id, index: r._index });
|
||||||
|
setShowPaymentModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconEdit size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
));
|
));
|
||||||
})()}
|
})()}
|
||||||
@@ -970,11 +993,46 @@ function BillingManager() {
|
|||||||
setTimeout(() => setError(''), 3000);
|
setTimeout(() => setError(''), 3000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Режим редактирования платежа
|
||||||
|
if (editingPayment && editingPayment.serverId) {
|
||||||
|
setBillingData(prev => prev.map(s => {
|
||||||
|
if (s.id !== editingPayment.serverId) return s;
|
||||||
|
const payments = Array.isArray(s.payments) ? [...s.payments] : [];
|
||||||
|
if (editingPayment.index != null && payments[editingPayment.index]) {
|
||||||
|
payments[editingPayment.index] = {
|
||||||
|
date: paymentDraft.date,
|
||||||
|
amount: paymentDraft.amount,
|
||||||
|
currency: paymentDraft.currency || 'USD',
|
||||||
|
note: paymentDraft.note || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// обновляем lastPayment* по последней дате
|
||||||
|
const last = payments
|
||||||
|
.slice()
|
||||||
|
.sort((a,b) => new Date(b.date) - new Date(a.date))[0];
|
||||||
|
return {
|
||||||
|
...s,
|
||||||
|
payments,
|
||||||
|
lastPaymentDate: last?.date || '',
|
||||||
|
lastPaymentAmount: last?.amount || 0,
|
||||||
|
lastPaymentCurrency: last?.currency || s.monthlyCostCurrency || 'USD'
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
setEditingPayment(null);
|
||||||
|
setShowPaymentModal(false);
|
||||||
|
setSuccess('Платеж обновлен');
|
||||||
|
setTimeout(() => setSuccess(''), 3000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Режим добавления платежа
|
||||||
setBillingData(prev => prev.map(s => {
|
setBillingData(prev => prev.map(s => {
|
||||||
if (s.id !== paymentDraft.serverId) return s;
|
if (s.id !== paymentDraft.serverId) return s;
|
||||||
const payments = Array.isArray(s.payments) ? [...s.payments] : [];
|
const payments = Array.isArray(s.payments) ? [...s.payments] : [];
|
||||||
payments.push({ date: paymentDraft.date, amount: paymentDraft.amount, currency: paymentDraft.currency || 'USD', note: paymentDraft.note || '' });
|
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' };
|
const last = payments
|
||||||
|
.slice()
|
||||||
|
.sort((a,b) => new Date(b.date) - new Date(a.date))[0];
|
||||||
|
return { ...s, payments, lastPaymentDate: last?.date || '', lastPaymentAmount: last?.amount || 0, lastPaymentCurrency: last?.currency || s.monthlyCostCurrency || 'USD' };
|
||||||
}));
|
}));
|
||||||
setShowPaymentModal(false);
|
setShowPaymentModal(false);
|
||||||
setSuccess('Платеж добавлен');
|
setSuccess('Платеж добавлен');
|
||||||
@@ -1111,42 +1169,8 @@ function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Исторические платежи */}
|
{/* Дополнительно */}
|
||||||
<div className="col-12">
|
<div className="col-md-12">
|
||||||
<h6 className="text-muted mb-3 mt-4">Исторические платежи</h6>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-6">
|
|
||||||
<label className="form-label">Дата последнего платежа</label>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
className="form-control"
|
|
||||||
value={item.lastPaymentDate}
|
|
||||||
onChange={(e) => onItemChange({ ...item, lastPaymentDate: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-6">
|
|
||||||
<label className="form-label">Сумма последнего платежа</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
className="form-control"
|
|
||||||
value={item.lastPaymentAmount}
|
|
||||||
onChange={(e) => onItemChange({ ...item, lastPaymentAmount: parseFloat(e.target.value) || 0 })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-6">
|
|
||||||
<label className="form-label">Валюта последнего платежа</label>
|
|
||||||
<select
|
|
||||||
className="form-select"
|
|
||||||
value={item.lastPaymentCurrency}
|
|
||||||
onChange={(e) => onItemChange({ ...item, lastPaymentCurrency: e.target.value })}
|
|
||||||
>
|
|
||||||
<option value="USD">USD</option>
|
|
||||||
<option value="EUR">EUR</option>
|
|
||||||
<option value="RUB">RUB</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-6">
|
|
||||||
<label className="form-label">Заметки</label>
|
<label className="form-label">Заметки</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
Reference in New Issue
Block a user