feat: Add payment history section to BillingManager, update form labels to Russian, and remove country flag display for improved clarity
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 7m43s

This commit is contained in:
2025-08-05 23:40:06 +07:00
parent 859395028f
commit 2c67c2b23b
+93 -36
View File
@@ -17,7 +17,8 @@ import {
IconDownload,
IconUpload,
IconEye,
IconEyeOff
IconEyeOff,
IconHistory
} from '@tabler/icons-react';
const API_URL = '/api';
@@ -166,7 +167,7 @@ function BillingManager() {
const handleAddSubmit = () => {
if (!newItem.hostName || !newItem.country || !newItem.provider) {
setError('Пожалуйста, заполните обязательные поля: Host Name, Country, Provider');
setError('Пожалуйста, заполните обязательные поля: Имя хоста, Страна, Провайдер');
return;
}
@@ -180,7 +181,7 @@ function BillingManager() {
const handleEditSubmit = () => {
if (!editingItem.hostName || !editingItem.country || !editingItem.provider) {
setError('Пожалуйста, заполните обязательные поля: Host Name, Country, Provider');
setError('Пожалуйста, заполните обязательные поля: Имя хоста, Страна, Провайдер');
return;
}
@@ -249,15 +250,6 @@ function BillingManager() {
const uniqueCountries = [...new Set(billingData.map(item => item.country).filter(Boolean))];
const uniqueStatuses = [...new Set(billingData.map(item => item.status).filter(Boolean))];
function countryToFlag(isoCode) {
if (!isoCode) return '🌍';
const codePoints = isoCode
.toUpperCase()
.split('')
.map(char => 127397 + char.charCodeAt());
return String.fromCodePoint(...codePoints);
}
function formatDate(dateString) {
if (!dateString) return 'Не указано';
try {
@@ -428,7 +420,7 @@ function BillingManager() {
>
<option value="">Все страны</option>
{uniqueCountries.map(country => (
<option key={country} value={country}>{countryToFlag(country)} {country}</option>
<option key={country} value={country}>{country}</option>
))}
</select>
</div>
@@ -615,9 +607,6 @@ function BillingManager() {
<tr key={item.id}>
<td>
<div className="d-flex align-items-center">
<span className="avatar avatar-sm me-2">
{countryToFlag(item.country)}
</span>
<div>
<div className="fw-bold">{item.hostName}</div>
{item.loginUrl && (
@@ -638,7 +627,6 @@ function BillingManager() {
</td>
<td>
<div className="d-flex align-items-center">
<span className="me-2">{countryToFlag(item.country)}</span>
{item.country}
</div>
</td>
@@ -743,6 +731,71 @@ function BillingManager() {
</div>
</div>
{/* История платежей */}
<div className="card mt-4">
<div className="card-header">
<h3 className="card-title">
<IconHistory size={20} className="me-2" />
История платежей
</h3>
</div>
<div className="card-body">
<div className="table-responsive">
<table className="table table-vcenter">
<thead>
<tr>
<th>Имя хостера</th>
<th>Дата платежа</th>
<th>Сумма</th>
<th>Валюта</th>
<th>Статус</th>
</tr>
</thead>
<tbody>
{billingData
.filter(item => item.lastPaymentDate && item.lastPaymentAmount)
.sort((a, b) => new Date(b.lastPaymentDate) - new Date(a.lastPaymentDate))
.slice(0, 10)
.map(item => (
<tr key={item.id}>
<td>
<div className="fw-bold">{item.hostName}</div>
<div className="text-muted small">{item.provider}</div>
</td>
<td>{formatDate(item.lastPaymentDate)}</td>
<td>
<div className="fw-bold">
{formatCurrency(item.lastPaymentAmount, item.lastPaymentCurrency)}
</div>
<div className="text-muted small">
{formatCurrencyInRUB(item.lastPaymentAmount, item.lastPaymentCurrency)}
</div>
</td>
<td>
<span className="badge bg-blue-lt text-blue">
{item.lastPaymentCurrency || 'USD'}
</span>
</td>
<td>
<span className="badge bg-success-lt text-success">
Оплачен
</span>
</td>
</tr>
))}
{billingData.filter(item => item.lastPaymentDate && item.lastPaymentAmount).length === 0 && (
<tr>
<td colSpan="5" className="text-center text-muted py-4">
История платежей пуста
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
{/* Модальные окна */}
<AddBillingModal
show={showAddModal}
@@ -775,7 +828,7 @@ function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
if (!show) return null;
return (
<div className="modal show d-block" tabIndex="-1">
<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">
@@ -785,7 +838,7 @@ function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
<div className="modal-body">
<div className="row g-3">
<div className="col-md-6">
<label className="form-label">Host Name *</label>
<label className="form-label">Имя хоста *</label>
<input
type="text"
className="form-control"
@@ -795,7 +848,7 @@ function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
/>
</div>
<div className="col-md-6">
<label className="form-label">Purpose</label>
<label className="form-label">Назначение</label>
<select
className="form-select"
value={item.purpose}
@@ -812,7 +865,7 @@ function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
</select>
</div>
<div className="col-md-6">
<label className="form-label">Country *</label>
<label className="form-label">Страна *</label>
<input
type="text"
className="form-control"
@@ -822,7 +875,7 @@ function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
/>
</div>
<div className="col-md-6">
<label className="form-label">Provider *</label>
<label className="form-label">Провайдер *</label>
<input
type="text"
className="form-control"
@@ -832,25 +885,27 @@ function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
/>
</div>
<div className="col-md-6">
<label className="form-label">Login URL</label>
<label className="form-label">Ссылка для входа</label>
<input
type="text"
className="form-control"
value={item.loginUrl}
onChange={(e) => onItemChange({ ...item, loginUrl: e.target.value })}
placeholder="example.com"
/>
</div>
<div className="col-md-6">
<label className="form-label">Monthly Cost</label>
<label className="form-label">Месячная стоимость</label>
<input
type="number"
step="0.01"
className="form-control"
value={item.monthlyCost}
onChange={(e) => onItemChange({ ...item, monthlyCost: parseFloat(e.target.value) || 0 })}
/>
</div>
<div className="col-md-6">
<label className="form-label">Next Payment Date</label>
<label className="form-label">Дата следующего платежа</label>
<input
type="date"
className="form-control"
@@ -859,7 +914,7 @@ function AddBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
/>
</div>
<div className="col-md-6">
<label className="form-label">Status</label>
<label className="form-label">Статус</label>
<select
className="form-select"
value={item.status}
@@ -889,7 +944,7 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
if (!show) return null;
return (
<div className="modal show d-block" tabIndex="-1">
<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">
@@ -899,7 +954,7 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
<div className="modal-body">
<div className="row g-3">
<div className="col-md-6">
<label className="form-label">Host Name *</label>
<label className="form-label">Имя хоста *</label>
<input
type="text"
className="form-control"
@@ -909,7 +964,7 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
/>
</div>
<div className="col-md-6">
<label className="form-label">Purpose</label>
<label className="form-label">Назначение</label>
<select
className="form-select"
value={item.purpose || ''}
@@ -926,7 +981,7 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
</select>
</div>
<div className="col-md-6">
<label className="form-label">Country *</label>
<label className="form-label">Страна *</label>
<input
type="text"
className="form-control"
@@ -936,7 +991,7 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
/>
</div>
<div className="col-md-6">
<label className="form-label">Provider *</label>
<label className="form-label">Провайдер *</label>
<input
type="text"
className="form-control"
@@ -946,25 +1001,27 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
/>
</div>
<div className="col-md-6">
<label className="form-label">Login URL</label>
<label className="form-label">Ссылка для входа</label>
<input
type="text"
className="form-control"
value={item.loginUrl || ''}
onChange={(e) => onItemChange({ ...item, loginUrl: e.target.value })}
placeholder="example.com"
/>
</div>
<div className="col-md-6">
<label className="form-label">Monthly Cost</label>
<label className="form-label">Месячная стоимость</label>
<input
type="number"
step="0.01"
className="form-control"
value={item.monthlyCost || 0}
onChange={(e) => onItemChange({ ...item, monthlyCost: parseFloat(e.target.value) || 0 })}
/>
</div>
<div className="col-md-6">
<label className="form-label">Next Payment Date</label>
<label className="form-label">Дата следующего платежа</label>
<input
type="date"
className="form-control"
@@ -973,7 +1030,7 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
/>
</div>
<div className="col-md-6">
<label className="form-label">Status</label>
<label className="form-label">Статус</label>
<select
className="form-select"
value={item.status || 'active'}
@@ -1003,7 +1060,7 @@ function DeleteBillingModal({ show, item, onDelete, onClose }) {
if (!show) return null;
return (
<div className="modal show d-block" tabIndex="-1">
<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">