Files
no-copy-admin-panel-frontend/src/app/ui/tariff-management/tariff-management.tsx
T

271 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useTariffsData } from '@/app/hooks/react-query/useTariffsData';
import { useEffect, useState } from 'react';
import { convertBytes } from '@/app/lib/convertBytes';
import { IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft } from '@/app/ui/icons/icons';
import ModalWindow from '@/app/components/modalWindow';
import { useQueryClient } from '@tanstack/react-query';
import { EditingTariff, TariffEditModal } from './TariffEditModal';
import { TariffCreateModal } from './TariffCreateModal';
import { TariffDeleteModal } from './TariffDeleteModal';
export default function TariffManagment() {
const [currentPage, setCurrentPage] = useState(0);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [editingTariff, setEditingTariff] = useState<EditingTariff | null>(null);
const [deletingTariff, setDeletingTariff] = useState<{ id: number; name: string } | null>(null);
const { data: tariffData, isLoading, isError, isFetching, error } = useTariffsData(currentPage);
const queryClient = useQueryClient();
useEffect(() => {
console.log(tariffData);
}, [tariffData])
useEffect(() => {
if (tariffData && tariffData.page !== currentPage) {
setCurrentPage(tariffData.page);
}
}, [tariffData, currentPage]);
const handlePageChange = (newPage: number) => {
if (tariffData && newPage >= 0 && newPage < tariffData.total_pages) {
setCurrentPage(newPage);
}
};
const handleEditClick = (tariff: any) => {
setEditingTariff({
id: tariff.id,
name: tariff.name,
type: tariff.type,
price: tariff.price,
tokens: tariff.tokens,
maxFilesCount: tariff.maxFilesCount,
diskSize: tariff.diskSize,
maxUsers: tariff.maxUsers,
description: tariff.description,
tariffTerm: tariff.tariffTerm
});
setIsModalOpen(true);
};
const handleDeleteClick = (tariff: any) => {
setDeletingTariff({
id: tariff.id,
name: tariff.name || tariff.type
});
setIsDeleteModalOpen(true);
};
const formatPrice = (price: number) => {
return new Intl.NumberFormat('ru-RU').format(price);
};
const getDisplayName = (tariff: any) => {
if (tariff.name) {
return tariff.name.toUpperCase().replace(/_/g, ' ');
}
return tariff.type.replace(/_/g, ' ');
};
if (isLoading) {
return (
<div className="admin-content">
<div className="content-header">
<h3>Управление тарифными планами</h3>
</div>
<div className="content-body">
<div className="loading-spinner">Загрузка тарифов...</div>
</div>
</div>
);
}
if (isError) {
return (
<div className="admin-content">
<div className="content-header">
<h3>Управление тарифными планами</h3>
</div>
<div className="content-body">
<div className="error-message">
Ошибка загрузки тарифов: {error?.message || 'Неизвестная ошибка'}
</div>
</div>
</div>
);
}
return (
<div className="admin-content">
<div className="content-header">
<h3>Управление тарифными планами</h3>
<div className="content-header-actions">
<button
className="btn btn-primary"
onClick={() => setIsCreateModalOpen(true)}
>
<span></span>
<span>Создать тариф</span>
</button>
</div>
</div>
<div className="content-body" id="plans-container">
<div className="stats-grid">
{tariffData?.tariffs?.map((tariff) => (
<div key={tariff.id} className="stat-card">
<div className="stat-card-header">
<span className="stat-card-title">{getDisplayName(tariff)}</span>
{tariff.tariffTerm && (
<span className="tariff-term-badge">{tariff.tariffTerm}</span>
)}
</div>
<div className="stat-card-value">{formatPrice(tariff.price)}</div>
<div className="stat-card-label">
{tariff.tokens > 0 && `${tariff.tokens.toLocaleString()} токенов`}
{tariff.diskSize > 0 && ` • ${convertBytes(tariff.diskSize)}`}
{tariff.maxFilesCount > 0 && ` • ${tariff.maxFilesCount} файлов`}
</div>
{tariff.description && (
<div className="stat-card-description">{tariff.description}</div>
)}
<div className="stat-card-footer">
<button
className="btn btn-outline btn-sm"
onClick={() => handleEditClick(tariff)}
>
Редактировать
</button>
<button
className="btn btn-outline btn-sm btn-danger"
onClick={() => handleDeleteClick(tariff)}
>
Удалить
</button>
</div>
</div>
))}
</div>
{/* Пагинация */}
{tariffData && tariffData.total_pages > 0 && (
<div className="tanstak-table-pagination">
<div className="pagination-info">
<span className="pagination-info-pages">
Страница{' '}
<strong>
{currentPage + 1} из {tariffData.total_pages}
</strong>
</span>
<span className="pagination-info-files">
| Показано {tariffData.tariffs?.length || 0} из {tariffData.total_elements}
</span>
</div>
<div className="pagination-controls">
<button
className="arrow"
onClick={() => handlePageChange(0)}
disabled={currentPage === 0 || isFetching}
>
<IconDoubleArrowLeft />
</button>
<button
className="arrow"
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 0 || isFetching}
>
<IconArrowLeft />
</button>
<div className="pagination-controls-pages">
{(() => {
const totalPages = tariffData.total_pages;
if (!totalPages || totalPages === 0) return null;
const maxVisiblePages = 5;
const visiblePagesCount = Math.min(maxVisiblePages, totalPages);
let startPage = currentPage - Math.floor(maxVisiblePages / 2);
startPage = Math.max(0, Math.min(startPage, totalPages - visiblePagesCount));
return Array.from({ length: visiblePagesCount }, (_, i) => {
const pageIndex = startPage + i;
return (
<button
key={pageIndex}
className={currentPage === pageIndex ? 'current' : 'other'}
onClick={() => handlePageChange(pageIndex)}
disabled={isFetching}
>
{pageIndex + 1}
</button>
);
});
})()}
</div>
<button
className="arrow"
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === tariffData.total_pages - 1 || isFetching}
>
<IconArrowRight />
</button>
<button
className="arrow"
onClick={() => handlePageChange(tariffData.total_pages - 1)}
disabled={currentPage === tariffData.total_pages - 1 || isFetching}
>
<IconDoubleArrowRight />
</button>
</div>
</div>
)}
</div>
{/* Модальное окно редактирования тарифа */}
<ModalWindow state={isModalOpen} callBack={setIsModalOpen}>
{editingTariff && (
<TariffEditModal
tariff={editingTariff}
onClose={() => setIsModalOpen(false)}
onSuccess={() => {
queryClient.invalidateQueries({ queryKey: ['tariffsData'] });
setIsModalOpen(false);
}}
/>
)}
</ModalWindow>
{/* Модальное окно создания тарифа */}
<ModalWindow state={isCreateModalOpen} callBack={setIsCreateModalOpen}>
<TariffCreateModal
onClose={() => setIsCreateModalOpen(false)}
onSuccess={() => {
queryClient.invalidateQueries({ queryKey: ['tariffsData'] });
setIsCreateModalOpen(false);
}}
/>
</ModalWindow>
{/* Модальное окно удаления тарифа */}
<ModalWindow state={isDeleteModalOpen} callBack={setIsDeleteModalOpen}>
{deletingTariff && (
<TariffDeleteModal
tariffId={deletingTariff.id}
tariffName={deletingTariff.name}
onClose={() => setIsDeleteModalOpen(false)}
onSuccess={() => {
queryClient.invalidateQueries({ queryKey: ['tariffsData'] });
setIsDeleteModalOpen(false);
setDeletingTariff(null);
}}
/>
)}
</ModalWindow>
</div>
)
}