feat: Enhance BillingManager with new payment addition functionality, update modal forms to include historical payment details, and improve UI with additional action buttons and status selection
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 30m1s

This commit is contained in:
2025-08-06 00:18:45 +07:00
parent 2c67c2b23b
commit 9b253e8963
+251 -13
View File
@@ -675,12 +675,38 @@ function BillingManager() {
<button
className="btn btn-sm btn-outline-primary"
onClick={() => handleEditItem(item)}
title="Редактировать"
>
<IconEdit size={14} />
</button>
<button
className="btn btn-sm btn-outline-success"
onClick={() => {
setNewItem({
hostName: item.hostName,
purpose: item.purpose,
country: item.country,
provider: item.provider,
loginUrl: item.loginUrl,
monthlyCost: item.monthlyCost,
monthlyCostCurrency: item.monthlyCostCurrency,
nextPaymentDate: item.nextPaymentDate,
lastPaymentDate: '',
lastPaymentAmount: 0,
lastPaymentCurrency: item.monthlyCostCurrency || 'USD',
status: item.status,
notes: ''
});
setShowAddModal(true);
}}
title="Добавить платеж"
>
<IconPlus size={14} />
</button>
<button
className="btn btn-sm btn-outline-danger"
onClick={() => handleDeleteItem(item)}
title="Удалить"
>
<IconTrash size={14} />
</button>
@@ -738,6 +764,32 @@ function BillingManager() {
<IconHistory size={20} className="me-2" />
История платежей
</h3>
<div className="card-actions">
<button
className="btn btn-outline-primary btn-sm"
onClick={() => {
setNewItem({
hostName: '',
purpose: '',
country: '',
provider: '',
loginUrl: '',
monthlyCost: 0,
monthlyCostCurrency: 'USD',
nextPaymentDate: '',
lastPaymentDate: '',
lastPaymentAmount: 0,
lastPaymentCurrency: 'USD',
status: 'active',
notes: ''
});
setShowAddModal(true);
}}
>
<IconPlus size={16} />
Добавить платеж
</button>
</div>
</div>
<div className="card-body">
<div className="table-responsive">
@@ -749,6 +801,7 @@ function BillingManager() {
<th>Сумма</th>
<th>Валюта</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
@@ -781,12 +834,53 @@ function BillingManager() {
Оплачен
</span>
</td>
<td>
<div className="btn-list">
<button
className="btn btn-sm btn-outline-primary"
onClick={() => handleEditItem(item)}
title="Редактировать"
>
<IconEdit size={14} />
</button>
</div>
</td>
</tr>
))}
{billingData.filter(item => item.lastPaymentDate && item.lastPaymentAmount).length === 0 && (
<tr>
<td colSpan="5" className="text-center text-muted py-4">
История платежей пуста
<td colSpan="6" className="text-center text-muted py-4">
<div className="py-3">
<IconHistory size={48} className="text-muted mb-3" />
<div>История платежей пуста</div>
<div className="small text-muted mt-2">
Добавьте первый платеж, чтобы начать отслеживание
</div>
<button
className="btn btn-primary btn-sm mt-3"
onClick={() => {
setNewItem({
hostName: '',
purpose: '',
country: '',
provider: '',
loginUrl: '',
monthlyCost: 0,
monthlyCostCurrency: 'USD',
nextPaymentDate: '',
lastPaymentDate: '',
lastPaymentAmount: 0,
lastPaymentCurrency: 'USD',
status: 'active',
notes: ''
});
setShowAddModal(true);
}}
>
<IconPlus size={16} />
Добавить первый платеж
</button>
</div>
</td>
</tr>
)}
@@ -827,16 +921,30 @@ function BillingManager() {
function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
if (!show) return null;
const isHistoricalPayment = item.hostName && !item.lastPaymentDate;
const modalTitle = isHistoricalPayment ? 'Добавить исторический платеж' : 'Добавить новый элемент';
return (
<div className="modal show d-block" tabIndex="-1" style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}>
<div className="modal-dialog modal-lg">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Добавить новый элемент</h5>
<h5 className="modal-title">{modalTitle}</h5>
<button type="button" className="btn-close" onClick={onClose}></button>
</div>
<div className="modal-body">
{isHistoricalPayment && (
<div className="alert alert-info mb-3">
<IconHistory size={16} className="me-2" />
<strong>Исторический платеж:</strong> Вы добавляете платеж для существующего хоста "{item.hostName}".
Заполните информацию о платеже ниже.
</div>
)}
<div className="row g-3">
{/* Основная информация */}
<div className="col-12">
<h6 className="text-muted mb-3">Основная информация</h6>
</div>
<div className="col-md-6">
<label className="form-label">Имя хоста *</label>
<input
@@ -894,6 +1002,22 @@ function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
placeholder="example.com"
/>
</div>
<div className="col-md-6">
<label className="form-label">Статус</label>
<select
className="form-select"
value={item.status}
onChange={(e) => onItemChange({ ...item, status: e.target.value })}
>
<option value="active">Активен</option>
<option value="inactive">Неактивен</option>
</select>
</div>
{/* Текущие платежи */}
<div className="col-12">
<h6 className="text-muted mb-3 mt-4">Текущие платежи</h6>
</div>
<div className="col-md-6">
<label className="form-label">Месячная стоимость</label>
<input
@@ -904,6 +1028,18 @@ function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
onChange={(e) => onItemChange({ ...item, monthlyCost: parseFloat(e.target.value) || 0 })}
/>
</div>
<div className="col-md-6">
<label className="form-label">Валюта месячной стоимости</label>
<select
className="form-select"
value={item.monthlyCostCurrency}
onChange={(e) => onItemChange({ ...item, monthlyCostCurrency: 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>
<input
@@ -913,17 +1049,52 @@ function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
onChange={(e) => onItemChange({ ...item, nextPaymentDate: e.target.value })}
/>
</div>
{/* Исторические платежи */}
<div className="col-12">
<h6 className="text-muted mb-3 mt-4">Исторические платежи</h6>
</div>
<div className="col-md-6">
<label className="form-label">Статус</label>
<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.status}
onChange={(e) => onItemChange({ ...item, status: e.target.value })}
value={item.lastPaymentCurrency}
onChange={(e) => onItemChange({ ...item, lastPaymentCurrency: e.target.value })}
>
<option value="active">Активен</option>
<option value="inactive">Неактивен</option>
<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>
<input
type="text"
className="form-control"
value={item.notes}
onChange={(e) => onItemChange({ ...item, notes: e.target.value })}
placeholder="Дополнительная информация"
/>
</div>
</div>
</div>
<div className="modal-footer">
@@ -953,6 +1124,10 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
</div>
<div className="modal-body">
<div className="row g-3">
{/* Основная информация */}
<div className="col-12">
<h6 className="text-muted mb-3">Основная информация</h6>
</div>
<div className="col-md-6">
<label className="form-label">Имя хоста *</label>
<input
@@ -1010,6 +1185,22 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
placeholder="example.com"
/>
</div>
<div className="col-md-6">
<label className="form-label">Статус</label>
<select
className="form-select"
value={item.status || 'active'}
onChange={(e) => onItemChange({ ...item, status: e.target.value })}
>
<option value="active">Активен</option>
<option value="inactive">Неактивен</option>
</select>
</div>
{/* Текущие платежи */}
<div className="col-12">
<h6 className="text-muted mb-3 mt-4">Текущие платежи</h6>
</div>
<div className="col-md-6">
<label className="form-label">Месячная стоимость</label>
<input
@@ -1020,6 +1211,18 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
onChange={(e) => onItemChange({ ...item, monthlyCost: parseFloat(e.target.value) || 0 })}
/>
</div>
<div className="col-md-6">
<label className="form-label">Валюта месячной стоимости</label>
<select
className="form-select"
value={item.monthlyCostCurrency || 'USD'}
onChange={(e) => onItemChange({ ...item, monthlyCostCurrency: 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>
<input
@@ -1029,17 +1232,52 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
onChange={(e) => onItemChange({ ...item, nextPaymentDate: e.target.value })}
/>
</div>
{/* Исторические платежи */}
<div className="col-12">
<h6 className="text-muted mb-3 mt-4">Исторические платежи</h6>
</div>
<div className="col-md-6">
<label className="form-label">Статус</label>
<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 || 0}
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.status || 'active'}
onChange={(e) => onItemChange({ ...item, status: e.target.value })}
value={item.lastPaymentCurrency || 'USD'}
onChange={(e) => onItemChange({ ...item, lastPaymentCurrency: e.target.value })}
>
<option value="active">Активен</option>
<option value="inactive">Неактивен</option>
<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>
<input
type="text"
className="form-control"
value={item.notes || ''}
onChange={(e) => onItemChange({ ...item, notes: e.target.value })}
placeholder="Дополнительная информация"
/>
</div>
</div>
</div>
<div className="modal-footer">