import { useState, useCallback } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { tariffsApi, TariffDetail, TariffCreateRequest, TariffUpdateRequest, PeriodPrice, ServerInfo, } from '../api/tariffs'; import { AdminBackButton } from '../components/admin'; // Icons const PlusIcon = () => ( ); const TrashIcon = () => ( ); const CheckIcon = () => ( ); const InfinityIcon = () => ( ); const CalendarIcon = () => ( ); const SunIcon = () => ( ); type TariffType = 'period' | 'daily' | null; export default function AdminTariffCreate() { const { t } = useTranslation(); const navigate = useNavigate(); const { id } = useParams<{ id: string }>(); const queryClient = useQueryClient(); const isEdit = !!id; // Step: null = type selection, 'period' or 'daily' = form const [tariffType, setTariffType] = useState(null); // Form state const [name, setName] = useState(''); const [description, setDescription] = useState(''); const [trafficLimitGb, setTrafficLimitGb] = useState(100); const [deviceLimit, setDeviceLimit] = useState(1); const [devicePriceKopeks, setDevicePriceKopeks] = useState(0); const [maxDeviceLimit, setMaxDeviceLimit] = useState(0); const [tierLevel, setTierLevel] = useState(1); const [periodPrices, setPeriodPrices] = useState([]); const [selectedSquads, setSelectedSquads] = useState([]); const [dailyPriceKopeks, setDailyPriceKopeks] = useState(0); // Traffic topup const [trafficTopupEnabled, setTrafficTopupEnabled] = useState(false); const [maxTopupTrafficGb, setMaxTopupTrafficGb] = useState(0); const [trafficTopupPackages, setTrafficTopupPackages] = useState>({}); // Traffic reset mode const [trafficResetMode, setTrafficResetMode] = useState(null); // Custom days (period tariff) const [customDaysEnabled, setCustomDaysEnabled] = useState(false); const [pricePerDayKopeks, setPricePerDayKopeks] = useState(0); const [minDays, setMinDays] = useState(1); const [maxDays, setMaxDays] = useState(365); // Custom traffic (period tariff) const [customTrafficEnabled, setCustomTrafficEnabled] = useState(false); const [trafficPricePerGbKopeks, setTrafficPricePerGbKopeks] = useState(0); const [minTrafficGb, setMinTrafficGb] = useState(1); const [maxTrafficGb, setMaxTrafficGb] = useState(1000); // New period for adding const [newPeriodDays, setNewPeriodDays] = useState(30); const [newPeriodPrice, setNewPeriodPrice] = useState(300); const [activeTab, setActiveTab] = useState<'basic' | 'periods' | 'servers' | 'extra'>('basic'); // Fetch servers const { data: servers = [] } = useQuery({ queryKey: ['admin-tariffs-servers'], queryFn: () => tariffsApi.getAvailableServers(), }); // Fetch tariff for editing const { isLoading: isLoadingTariff } = useQuery({ queryKey: ['admin-tariff', id], queryFn: () => tariffsApi.getTariff(Number(id)), enabled: isEdit, staleTime: 0, gcTime: 0, refetchOnMount: true, refetchOnWindowFocus: false, select: useCallback((data: TariffDetail) => { // Set form values from tariff setTariffType(data.is_daily ? 'daily' : 'period'); setName(data.name); setDescription(data.description || ''); setTrafficLimitGb(data.traffic_limit_gb ?? 100); setDeviceLimit(data.device_limit || 1); setDevicePriceKopeks(data.device_price_kopeks || 0); setMaxDeviceLimit(data.max_device_limit || 0); setTierLevel(data.tier_level || 1); setPeriodPrices(data.period_prices?.length ? data.period_prices : []); setSelectedSquads(data.allowed_squads || []); setDailyPriceKopeks(data.daily_price_kopeks || 0); setTrafficTopupEnabled(data.traffic_topup_enabled || false); setMaxTopupTrafficGb(data.max_topup_traffic_gb || 0); setTrafficTopupPackages(data.traffic_topup_packages || {}); setTrafficResetMode(data.traffic_reset_mode || null); setCustomDaysEnabled(data.custom_days_enabled || false); setPricePerDayKopeks(data.price_per_day_kopeks || 0); setMinDays(data.min_days || 1); setMaxDays(data.max_days || 365); setCustomTrafficEnabled(data.custom_traffic_enabled || false); setTrafficPricePerGbKopeks(data.traffic_price_per_gb_kopeks || 0); setMinTrafficGb(data.min_traffic_gb || 1); setMaxTrafficGb(data.max_traffic_gb || 1000); return data; }, []), }); // Create mutation const createMutation = useMutation({ mutationFn: tariffsApi.createTariff, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-tariffs'] }); navigate('/admin/tariffs'); }, }); // Update mutation const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: number; data: TariffUpdateRequest }) => tariffsApi.updateTariff(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-tariffs'] }); navigate('/admin/tariffs'); }, }); const handleSubmit = () => { const isDaily = tariffType === 'daily'; const data: TariffCreateRequest | TariffUpdateRequest = { name, description: description || undefined, traffic_limit_gb: trafficLimitGb, device_limit: deviceLimit, device_price_kopeks: devicePriceKopeks > 0 ? devicePriceKopeks : undefined, max_device_limit: maxDeviceLimit > 0 ? maxDeviceLimit : undefined, tier_level: tierLevel, period_prices: isDaily ? [] : periodPrices.filter((p) => p.price_kopeks >= 0), allowed_squads: selectedSquads, traffic_topup_enabled: trafficTopupEnabled, traffic_topup_packages: trafficTopupPackages, max_topup_traffic_gb: maxTopupTrafficGb, is_daily: isDaily, daily_price_kopeks: isDaily ? dailyPriceKopeks : 0, traffic_reset_mode: trafficResetMode, }; // Add period tariff specific fields if (!isDaily) { data.custom_days_enabled = customDaysEnabled; data.price_per_day_kopeks = pricePerDayKopeks; data.min_days = minDays; data.max_days = maxDays; data.custom_traffic_enabled = customTrafficEnabled; data.traffic_price_per_gb_kopeks = trafficPricePerGbKopeks; data.min_traffic_gb = minTrafficGb; data.max_traffic_gb = maxTrafficGb; } if (isEdit) { updateMutation.mutate({ id: Number(id), data }); } else { createMutation.mutate(data as TariffCreateRequest); } }; const toggleServer = (uuid: string) => { setSelectedSquads((prev) => prev.includes(uuid) ? prev.filter((s) => s !== uuid) : [...prev, uuid], ); }; const addPeriod = () => { if (newPeriodDays > 0 && newPeriodPrice > 0) { const exists = periodPrices.some((p) => p.days === newPeriodDays); if (!exists) { setPeriodPrices((prev) => [...prev, { days: newPeriodDays, price_kopeks: newPeriodPrice * 100 }].sort( (a, b) => a.days - b.days, ), ); setNewPeriodDays(30); setNewPeriodPrice(300); } } }; const removePeriod = (days: number) => { setPeriodPrices((prev) => prev.filter((p) => p.days !== days)); }; const updatePeriodPrice = (days: number, priceRubles: number) => { setPeriodPrices((prev) => prev.map((p) => (p.days === days ? { ...p, price_kopeks: priceRubles * 100 } : p)), ); }; const isLoading = createMutation.isPending || updateMutation.isPending; const isValidPeriod = name && (periodPrices.length > 0 || customDaysEnabled); const isValidDaily = name && dailyPriceKopeks > 0; const isValid = tariffType === 'period' ? isValidPeriod : tariffType === 'daily' ? isValidDaily : false; // Loading state if (isEdit && isLoadingTariff) { return (
); } // Type selection step (only for creation) if (!isEdit && tariffType === null) { return (

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

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

); } const isDaily = tariffType === 'daily'; const accentColor = isDaily ? 'warning' : 'accent'; return (
{/* Header */}
{isDaily ? : }

{isEdit ? t('admin.tariffs.editTitle') : isDaily ? t('admin.tariffs.newDailyTitle') : t('admin.tariffs.newPeriodTitle')}

{isDaily ? t('admin.tariffs.dailyDeduction') : t('admin.tariffs.periodPayment')}

{/* Tabs */}
{(isDaily ? (['basic', 'servers', 'extra'] as const) : (['basic', 'periods', 'servers', 'extra'] as const) ).map((tab) => ( ))}
{/* Content */}
{activeTab === 'basic' && (
{/* Name */}
setName(e.target.value)} className="w-full rounded-lg border border-dark-600 bg-dark-700 px-3 py-2 text-dark-100 focus:border-accent-500 focus:outline-none" placeholder={ isDaily ? t('admin.tariffs.nameExampleDaily') : t('admin.tariffs.nameExamplePeriod') } />
{/* Description */}