feat: Add country flag display functionality in BillingManager for improved user experience
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m47s

This commit is contained in:
2025-08-08 11:37:58 +07:00
parent 6c7142d45d
commit fdad8ce5e1
+72 -11
View File
@@ -314,6 +314,66 @@ function BillingManager() {
return purposeMap[purpose] || purpose || 'Не указано';
}
// Флаги стран (эмодзи) по коду/названию
function countryCodeToFlag(isoCode) {
if (!isoCode || isoCode.length !== 2) return '';
const codePoints = isoCode
.toUpperCase()
.split('')
.map(char => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
}
function getCountryFlag(countryRaw) {
if (!countryRaw) return '';
const value = String(countryRaw).trim();
if (!value) return '';
const upper = value.toUpperCase();
// Если уже ISO-2
if (/^[A-Z]{2}$/.test(upper)) {
return countryCodeToFlag(upper);
}
// Маппинг распространённых названий -> ISO-2
const mapNameToCode = {
// RU
'RUSSIA': 'RU', 'RUSSIAN FEDERATION': 'RU', 'РОССИЯ': 'RU',
// US
'USA': 'US', 'UNITED STATES': 'US', 'США': 'US', 'СОЕДИНЕННЫЕ ШТАТЫ': 'US',
// DE
'GERMANY': 'DE', 'DEUTSCHLAND': 'DE', 'ГЕРМАНИЯ': 'DE',
// NL
'NETHERLANDS': 'NL', 'NEDERLAND': 'NL', 'НИДЕРЛАНДЫ': 'NL',
// FR
'FRANCE': 'FR', 'ФРАНЦИЯ': 'FR',
// GB
'UNITED KINGDOM': 'GB', 'UK': 'GB', 'ВЕЛИКОБРИТАНИЯ': 'GB',
// SG
'SINGAPORE': 'SG', 'СИНГАПУР': 'SG',
// PL
'POLAND': 'PL', 'ПОЛЬША': 'PL',
// ES
'SPAIN': 'ES', 'ИСПАНИЯ': 'ES',
// IT
'ITALY': 'IT', 'ИТАЛИЯ': 'IT',
// TR
'TURKEY': 'TR', 'ТУРЦИЯ': 'TR',
// KZ
'KAZAKHSTAN': 'KZ', 'КАЗАХСТАН': 'KZ',
// LT, LV, EE, CZ, UA, BY, SE, FI, CA
'LITHUANIA': 'LT', 'ЛИТВА': 'LT',
'LATVIA': 'LV', 'ЛАТВИЯ': 'LV',
'ESTONIA': 'EE', 'ЭСТОНИЯ': 'EE',
'CZECHIA': 'CZ', 'CZECH REPUBLIC': 'CZ', 'ЧЕХИЯ': 'CZ',
'UKRAINE': 'UA', 'УКРАИНА': 'UA',
'BELARUS': 'BY', 'БЕЛАРУСЬ': 'BY',
'SWEDEN': 'SE', 'ШВЕЦИЯ': 'SE',
'FINLAND': 'FI', 'ФИНЛЯНДИЯ': 'FI',
'CANADA': 'CA', 'КАНАДА': 'CA',
};
const code = mapNameToCode[upper];
return code ? countryCodeToFlag(code) : '';
}
// Экспорт данных
const exportData = () => {
const csvContent = [
@@ -542,7 +602,7 @@ function BillingManager() {
<h3 className="card-title">Оплачиваемые ноды</h3>
<div className="card-actions">
<button
className="btn btn-primary btn-sm"
className="btn btn-primary"
onClick={handleSaveChanges}
disabled={loading}
>
@@ -650,8 +710,9 @@ function BillingManager() {
</span>
</td>
<td>
<div className="d-flex align-items-center">
{item.country}
<div className="d-flex align-items-center gap-2">
<span style={{ fontSize: '18px' }}>{getCountryFlag(item.country)}</span>
<span>{item.country}</span>
</div>
</td>
<td>
@@ -697,14 +758,14 @@ function BillingManager() {
<td>
<div className="btn-list">
<button
className="btn btn-sm btn-outline-primary"
className="btn btn-outline-primary btn-icon"
onClick={() => handleEditItem(item)}
title="Редактировать"
>
<IconEdit size={14} />
<IconEdit size={18} />
</button>
<button
className="btn btn-sm btn-outline-success"
className="btn btn-outline-success btn-icon"
onClick={() => {
setPaymentDraft({
serverId: item.id,
@@ -717,14 +778,14 @@ function BillingManager() {
}}
title="Добавить платеж"
>
<IconPlus size={14} />
<IconPlus size={18} />
</button>
<button
className="btn btn-sm btn-outline-danger"
className="btn btn-outline-danger btn-icon"
onClick={() => handleDeleteItem(item)}
title="Удалить"
>
<IconTrash size={14} />
<IconTrash size={18} />
</button>
</div>
</td>
@@ -786,8 +847,8 @@ function BillingManager() {
История платежей
</h3>
<div className="card-actions">
<button
className="btn btn-outline-primary btn-sm"
<button
className="btn btn-outline-primary"
onClick={() => { setPaymentDraft({ serverId: '', date: '', amount: 0, currency: 'USD', note: '' }); setShowPaymentModal(true); }}
>
<IconPlus size={16} />