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

This commit is contained in:
2025-08-08 12:16:09 +07:00
parent ebfc5a9fab
commit 389725c023
6 changed files with 91 additions and 14 deletions
+2 -2
View File
@@ -217,7 +217,7 @@ function ASNsNewManager() {
{error}
</div>
</div>
<a className="btn-close" onClick={() => setError('')}></a>
<button type="button" className="btn-close" onClick={() => setError('')}></button>
</div>
)}
@@ -231,7 +231,7 @@ function ASNsNewManager() {
{success}
</div>
</div>
<a className="btn-close" onClick={() => setSuccess('')}></a>
<button type="button" className="btn-close" onClick={() => setSuccess('')}></button>
</div>
)}
+78 -1
View File
@@ -55,6 +55,8 @@ function BillingManager() {
note: ''
});
const [editingPayment, setEditingPayment] = useState(null); // { serverId, index }
const [showDeletePaymentModal, setShowDeletePaymentModal] = useState(false);
const [paymentToDelete, setPaymentToDelete] = useState(null); // { serverId, index, data }
// Состояние для курсов валют
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) {
if (!countryRaw) return '';
const value = String(countryRaw).trim();
@@ -893,7 +925,7 @@ function BillingManager() {
</div>
<div className="card-body">
<div className="table-responsive">
<table className="table table-vcenter">
<table className="table card-table table-vcenter table-nowrap mb-0">
<thead>
<tr>
<th>Имя хостера</th>
@@ -954,6 +986,16 @@ function BillingManager() {
>
<IconEdit size={18} />
</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>
</td>
</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>
);
}
+2 -2
View File
@@ -190,7 +190,7 @@ function DataManager({ entityName, entityKey, placeholder }) {
{error}
</div>
</div>
<a className="btn-close" onClick={() => setError('')}></a>
<button type="button" className="btn-close" onClick={() => setError('')}></button>
</div>
)}
@@ -204,7 +204,7 @@ function DataManager({ entityName, entityKey, placeholder }) {
{success}
</div>
</div>
<a className="btn-close" onClick={() => setSuccess('')}></a>
<button type="button" className="btn-close" onClick={() => setSuccess('')}></button>
</div>
)}
+2 -2
View File
@@ -222,7 +222,7 @@ function DomainsNewManager() {
{error}
</div>
</div>
<a className="btn-close" onClick={() => setError('')}></a>
<button type="button" className="btn-close" onClick={() => setError('')}></button>
</div>
)}
@@ -236,7 +236,7 @@ function DomainsNewManager() {
{success}
</div>
</div>
<a className="btn-close" onClick={() => setSuccess('')}></a>
<button type="button" className="btn-close" onClick={() => setSuccess('')}></button>
</div>
)}
+2 -2
View File
@@ -232,7 +232,7 @@ function IPRangesManager() {
{error}
</div>
</div>
<a className="btn-close" onClick={() => setError('')}></a>
<button type="button" className="btn-close" onClick={() => setError('')}></button>
</div>
)}
@@ -246,7 +246,7 @@ function IPRangesManager() {
{success}
</div>
</div>
<a className="btn-close" onClick={() => setSuccess('')}></a>
<button type="button" className="btn-close" onClick={() => setSuccess('')}></button>
</div>
)}
+5 -5
View File
@@ -428,7 +428,7 @@ function ServerManager() {
{error}
</div>
</div>
<a className="btn-close" onClick={() => setError('')}></a>
<button type="button" className="btn-close" onClick={() => setError('')}></button>
</div>
)}
@@ -442,7 +442,7 @@ function ServerManager() {
{success}
</div>
</div>
<a className="btn-close" onClick={() => setSuccess('')}></a>
<button type="button" className="btn-close" onClick={() => setSuccess('')}></button>
</div>
)}
@@ -760,9 +760,9 @@ function ServerManager() {
<div className="card-header">
<h3 className="card-title mb-0">Существующие связи ({connections.length})</h3>
</div>
<div className="card-body">
<div className="table-responsive">
<table className="table table-vcenter">
<div className="card-body">
<div className="table-responsive">
<table className="table card-table table-vcenter table-nowrap mb-0">
<thead>
<tr>
<th>Сервер A</th>