import { useState, useCallback, useEffect } from 'react'; import { useNavigate } from 'react-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { tariffsApi, TariffListItem } from '../api/tariffs'; import { useDestructiveConfirm, useNotify } from '@/platform'; import { usePlatform } from '../platform/hooks/usePlatform'; import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, type DragEndEvent, } from '@dnd-kit/core'; import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; // Icons const PlusIcon = () => ( ); const EditIcon = () => ( ); const TrashIcon = () => ( ); const CheckIcon = () => ( ); const XIcon = () => ( ); const GiftIcon = () => ( ); const BackIcon = () => ( ); const GripIcon = () => ( ); const SaveIcon = () => ( ); // ============ Sortable Tariff Card ============ interface SortableTariffCardProps { tariff: TariffListItem; onEdit: () => void; onDelete: () => void; onToggle: () => void; onToggleTrial: () => void; } function SortableTariffCard({ tariff, onEdit, onDelete, onToggle, onToggleTrial, }: SortableTariffCardProps) { const { t } = useTranslation(); const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: tariff.id, }); const style: React.CSSProperties = { transform: CSS.Transform.toString(transform), transition, zIndex: isDragging ? 50 : undefined, position: isDragging ? 'relative' : undefined, }; return (
{/* Drag handle */} {/* Content */}

{tariff.name}

{tariff.is_daily ? ( {t('admin.tariffs.dailyType')} ) : ( {t('admin.tariffs.periodType')} )} {tariff.is_trial_available && ( {t('admin.tariffs.trial')} )} {!tariff.is_active && ( {t('admin.tariffs.inactive')} )}
{tariff.is_daily && tariff.daily_price_kopeks > 0 && ( {(tariff.daily_price_kopeks / 100).toFixed(2)} {t('admin.tariffs.currencyPerDay')} )} {tariff.traffic_limit_gb === 0 ? t('admin.tariffs.unlimited') : `${tariff.traffic_limit_gb} GB`} {t('admin.tariffs.devices', { count: tariff.device_limit })} {t('admin.tariffs.servers', { count: tariff.servers_count })} {t('admin.tariffs.subscriptions', { count: tariff.subscriptions_count })}
{/* Actions */}
); } // ============ Main Page ============ export default function AdminTariffs() { const { t } = useTranslation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const confirmDelete = useDestructiveConfirm(); const notify = useNotify(); const { capabilities } = usePlatform(); const [localTariffs, setLocalTariffs] = useState([]); const [orderChanged, setOrderChanged] = useState(false); // Queries const { data: tariffsData, isLoading } = useQuery({ queryKey: ['admin-tariffs'], queryFn: () => tariffsApi.getTariffs(true), }); // Sync fetched data to local state useEffect(() => { if (tariffsData?.tariffs && !orderChanged) { setLocalTariffs(tariffsData.tariffs); } }, [tariffsData, orderChanged]); // Save order mutation const saveOrderMutation = useMutation({ mutationFn: (tariffIds: number[]) => tariffsApi.updateOrder(tariffIds), onSuccess: () => { setOrderChanged(false); queryClient.invalidateQueries({ queryKey: ['admin-tariffs'] }); notify.success(t('admin.tariffs.orderSaved')); }, }); // Mutations const deleteMutation = useMutation({ mutationFn: tariffsApi.deleteTariff, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-tariffs'] }); notify.success(t('admin.tariffs.deleteSuccess')); }, }); const handleDelete = async (tariff: TariffListItem) => { const confirmText = tariff.subscriptions_count > 0 ? t('admin.tariffs.confirmDeleteWithSubscriptions', { count: tariff.subscriptions_count, }) : t('admin.tariffs.confirmDeleteText'); const confirmed = await confirmDelete( confirmText, t('common.delete'), t('admin.tariffs.confirmDelete'), ); if (confirmed) { deleteMutation.mutate(tariff.id); } }; const toggleMutation = useMutation({ mutationFn: tariffsApi.toggleTariff, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-tariffs'] }); }, }); const toggleTrialMutation = useMutation({ mutationFn: tariffsApi.toggleTrial, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-tariffs'] }); }, }); // DnD sensors const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }), ); const handleDragEnd = useCallback((event: DragEndEvent) => { const { active, over } = event; if (over && active.id !== over.id) { setLocalTariffs((prev) => { const oldIndex = prev.findIndex((t) => t.id === active.id); const newIndex = prev.findIndex((t) => t.id === over.id); if (oldIndex === -1 || newIndex === -1) return prev; return arrayMove(prev, oldIndex, newIndex); }); setOrderChanged(true); } }, []); const handleSaveOrder = () => { saveOrderMutation.mutate(localTariffs.map((t) => t.id)); }; return (
{/* Header */}
{/* Show back button only on web, not in Telegram Mini App */} {!capabilities.hasBackButton && ( )}

{t('admin.tariffs.title')}

{t('admin.tariffs.subtitle')}

{orderChanged && ( )}
{/* Drag hint */}
{t('admin.tariffs.dragToReorder')}
{/* Tariffs List */} {isLoading ? (
) : localTariffs.length === 0 ? (

{t('admin.tariffs.noTariffs')}

) : ( t.id)} strategy={verticalListSortingStrategy} >
{localTariffs.map((tariff) => ( navigate(`/admin/tariffs/${tariff.id}/edit`)} onDelete={() => handleDelete(tariff)} onToggle={() => toggleMutation.mutate(tariff.id)} onToggleTrial={() => toggleTrialMutation.mutate(tariff.id)} /> ))}
)}
); }