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
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 30m1s
This commit is contained in:
+251
-13
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user