feat: Replace anchor tags with button elements for close actions in multiple managers to improve accessibility and semantic HTML
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m56s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m56s
This commit is contained in:
@@ -217,7 +217,7 @@ function ASNsNewManager() {
|
|||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a className="btn-close" onClick={() => setError('')}></a>
|
<button type="button" className="btn-close" onClick={() => setError('')}></button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ function ASNsNewManager() {
|
|||||||
{success}
|
{success}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a className="btn-close" onClick={() => setSuccess('')}></a>
|
<button type="button" className="btn-close" onClick={() => setSuccess('')}></button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ function BillingManager() {
|
|||||||
note: ''
|
note: ''
|
||||||
});
|
});
|
||||||
const [editingPayment, setEditingPayment] = useState(null); // { serverId, index }
|
const [editingPayment, setEditingPayment] = useState(null); // { serverId, index }
|
||||||
|
const [showDeletePaymentModal, setShowDeletePaymentModal] = useState(false);
|
||||||
|
const [paymentToDelete, setPaymentToDelete] = useState(null); // { serverId, index, data }
|
||||||
|
|
||||||
// Состояние для курсов валют
|
// Состояние для курсов валют
|
||||||
const [exchangeRates, setExchangeRates] = useState({
|
const [exchangeRates, setExchangeRates] = useState({
|
||||||
@@ -349,6 +351,36 @@ function BillingManager() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Удаление платежа из истории
|
||||||
|
const handleDeletePayment = () => {
|
||||||
|
if (!paymentToDelete || !paymentToDelete.serverId || paymentToDelete.index == null) {
|
||||||
|
setShowDeletePaymentModal(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { serverId, index } = paymentToDelete;
|
||||||
|
setBillingData(prev => prev.map(s => {
|
||||||
|
if (s.id !== serverId) return s;
|
||||||
|
const payments = Array.isArray(s.payments) ? [...s.payments] : [];
|
||||||
|
if (index >= 0 && index < payments.length) {
|
||||||
|
payments.splice(index, 1);
|
||||||
|
}
|
||||||
|
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'
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
setShowDeletePaymentModal(false);
|
||||||
|
setPaymentToDelete(null);
|
||||||
|
setSuccess('Платеж удалён');
|
||||||
|
setTimeout(() => setSuccess(''), 3000);
|
||||||
|
};
|
||||||
|
|
||||||
function getCountryFlag(countryRaw) {
|
function getCountryFlag(countryRaw) {
|
||||||
if (!countryRaw) return '';
|
if (!countryRaw) return '';
|
||||||
const value = String(countryRaw).trim();
|
const value = String(countryRaw).trim();
|
||||||
@@ -893,7 +925,7 @@ function BillingManager() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
<table className="table table-vcenter">
|
<table className="table card-table table-vcenter table-nowrap mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Имя хостера</th>
|
<th>Имя хостера</th>
|
||||||
@@ -954,6 +986,16 @@ function BillingManager() {
|
|||||||
>
|
>
|
||||||
<IconEdit size={18} />
|
<IconEdit size={18} />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-outline-danger btn-icon"
|
||||||
|
title="Удалить платеж"
|
||||||
|
onClick={() => {
|
||||||
|
setPaymentToDelete({ serverId: r.server.id, index: r._index, data: r });
|
||||||
|
setShowDeletePaymentModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={18} />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1086,6 +1128,41 @@ function BillingManager() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Модалка удаления платежа */}
|
||||||
|
{showDeletePaymentModal && (
|
||||||
|
<div className="modal show d-block" tabIndex="-1" style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}>
|
||||||
|
<div className="modal-dialog">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h5 className="modal-title">Удалить платеж</h5>
|
||||||
|
<button type="button" className="btn-close" onClick={() => setShowDeletePaymentModal(false)}></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<p>Вы уверены, что хотите удалить выбранный платеж?</p>
|
||||||
|
{paymentToDelete && (
|
||||||
|
<div className="alert alert-warning">
|
||||||
|
<div><strong>Сервер:</strong> {paymentToDelete.data?.server?.hostName}</div>
|
||||||
|
<div><strong>Дата:</strong> {formatDate(paymentToDelete.data?.date)}</div>
|
||||||
|
<div><strong>Сумма:</strong> {formatCurrency(paymentToDelete.data?.amount, paymentToDelete.data?.currency)}</div>
|
||||||
|
{paymentToDelete.data?.note && (
|
||||||
|
<div><strong>Комментарий:</strong> {paymentToDelete.data?.note}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-secondary" onClick={() => setShowDeletePaymentModal(false)}>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
<button type="button" className="btn btn-danger" onClick={handleDeletePayment}>
|
||||||
|
Удалить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ function DataManager({ entityName, entityKey, placeholder }) {
|
|||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a className="btn-close" onClick={() => setError('')}></a>
|
<button type="button" className="btn-close" onClick={() => setError('')}></button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ function DataManager({ entityName, entityKey, placeholder }) {
|
|||||||
{success}
|
{success}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a className="btn-close" onClick={() => setSuccess('')}></a>
|
<button type="button" className="btn-close" onClick={() => setSuccess('')}></button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ function DomainsNewManager() {
|
|||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a className="btn-close" onClick={() => setError('')}></a>
|
<button type="button" className="btn-close" onClick={() => setError('')}></button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ function DomainsNewManager() {
|
|||||||
{success}
|
{success}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a className="btn-close" onClick={() => setSuccess('')}></a>
|
<button type="button" className="btn-close" onClick={() => setSuccess('')}></button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ function IPRangesManager() {
|
|||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a className="btn-close" onClick={() => setError('')}></a>
|
<button type="button" className="btn-close" onClick={() => setError('')}></button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ function IPRangesManager() {
|
|||||||
{success}
|
{success}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a className="btn-close" onClick={() => setSuccess('')}></a>
|
<button type="button" className="btn-close" onClick={() => setSuccess('')}></button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ function ServerManager() {
|
|||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a className="btn-close" onClick={() => setError('')}></a>
|
<button type="button" className="btn-close" onClick={() => setError('')}></button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -442,7 +442,7 @@ function ServerManager() {
|
|||||||
{success}
|
{success}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a className="btn-close" onClick={() => setSuccess('')}></a>
|
<button type="button" className="btn-close" onClick={() => setSuccess('')}></button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -760,9 +760,9 @@ function ServerManager() {
|
|||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<h3 className="card-title mb-0">Существующие связи ({connections.length})</h3>
|
<h3 className="card-title mb-0">Существующие связи ({connections.length})</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
<table className="table table-vcenter">
|
<table className="table card-table table-vcenter table-nowrap mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Сервер A</th>
|
<th>Сервер A</th>
|
||||||
|
|||||||
Reference in New Issue
Block a user