feat(BillingManager, ServerManager): implement automatic synchronization of billing entries for new servers; enhance server management modals for improved error handling and user experience
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m41s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m41s
This commit is contained in:
@@ -117,6 +117,48 @@ function BillingManager() {
|
||||
fetchServers();
|
||||
}, []);
|
||||
|
||||
// Автоматическая синхронизация: добавляем серверы без записей в биллинге
|
||||
useEffect(() => {
|
||||
if (servers.length === 0 || loading) return;
|
||||
|
||||
setBillingData((prev) => {
|
||||
const existingServerIds = new Set(
|
||||
prev.map(b => b.serverId).filter(Boolean)
|
||||
);
|
||||
|
||||
// Находим серверы без записей в биллинге
|
||||
const serversToAdd = servers.filter(server =>
|
||||
server.id && !existingServerIds.has(server.id)
|
||||
);
|
||||
|
||||
if (serversToAdd.length === 0) return prev;
|
||||
|
||||
// Создаём записи с флагом _external
|
||||
const newItems = serversToAdd.map(server => ({
|
||||
id: `billing-${server.id}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
||||
hostName: server.dns || server.ip,
|
||||
purpose: '',
|
||||
country: server.country || '',
|
||||
provider: server.provider || '',
|
||||
loginUrl: '',
|
||||
monthlyCost: 0,
|
||||
monthlyCostCurrency: 'USD',
|
||||
nextPaymentDate: '',
|
||||
lastPaymentDate: '',
|
||||
lastPaymentAmount: 0,
|
||||
lastPaymentCurrency: 'USD',
|
||||
status: 'active',
|
||||
notes: '',
|
||||
serverId: server.id,
|
||||
payments: [],
|
||||
_external: true, // флаг "не заполнен"
|
||||
}));
|
||||
|
||||
return [...prev, ...newItems];
|
||||
});
|
||||
}, [servers, loading]);
|
||||
|
||||
|
||||
const fetchExchangeRates = async () => {
|
||||
setRatesLoading(true);
|
||||
try {
|
||||
@@ -193,7 +235,11 @@ function BillingManager() {
|
||||
const handleSaveChanges = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await api.post(`/billing`, { domains: billingData });
|
||||
// Убираем служебное поле _external перед сохранением
|
||||
const payload = billingData.map(({ _external, ...rest }) => rest);
|
||||
await api.post(`/billing`, { domains: payload });
|
||||
// После сохранения снимаем флаг _external со всех записей
|
||||
setBillingData(prev => prev.map(item => ({ ...item, _external: false })));
|
||||
setSuccess('Данные успешно сохранены');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
} catch (err) {
|
||||
@@ -393,8 +439,9 @@ function BillingManager() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Снимаем флаг _external при редактировании (запись теперь заполнена)
|
||||
setBillingData(prev => prev.map(item =>
|
||||
item.id === editingItem.id ? editingItem : item
|
||||
item.id === editingItem.id ? { ...editingItem, _external: false } : item
|
||||
));
|
||||
setShowEditModal(false);
|
||||
setSuccess('Элемент обновлен');
|
||||
@@ -494,7 +541,7 @@ function BillingManager() {
|
||||
actions={
|
||||
<PageHeaderActions
|
||||
loading={loading}
|
||||
onRefresh={fetchBillingData}
|
||||
onRefresh={() => { fetchBillingData(); fetchServers(); }}
|
||||
disableRefresh={loading}
|
||||
onExport={exportData}
|
||||
disableExport={loading}
|
||||
@@ -734,7 +781,14 @@ function BillingManager() {
|
||||
<td>
|
||||
<div className="d-flex align-items-center">
|
||||
<div>
|
||||
<div className="fw-semibold">{item.hostName}</div>
|
||||
<div className="fw-semibold">
|
||||
{item.hostName}
|
||||
{item._external && (
|
||||
<span className="badge bg-yellow-lt text-yellow ms-2" title="Импортирован из серверов, требует заполнения">
|
||||
не заполнен
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-muted small d-flex align-items-center gap-2">
|
||||
<span>{item.provider}</span>
|
||||
{linkedServer && (
|
||||
|
||||
Reference in New Issue
Block a user