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'; import { createNumberInputHandler, toNumber } from '../utils/inputHelpers'; // Icons const PlusIcon = () => ( ); const TrashIcon = () => ( ); const CheckIcon = () => ( ); const InfinityIcon = () => ( ); const CalendarIcon = () => ( ); const SunIcon = () => ( ); const RefreshIcon = () => ( ); 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 - matches bot fields const [name, setName] = useState(''); const [description, setDescription] = useState(''); const [isActive, setIsActive] = useState(true); 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 [selectedPromoGroups, setSelectedPromoGroups] = useState([]); const [dailyPriceKopeks, setDailyPriceKopeks] = useState(0); // Traffic topup const [trafficTopupEnabled, setTrafficTopupEnabled] = useState(false); const [maxTopupTrafficGb, setMaxTopupTrafficGb] = useState(0); const [trafficTopupPackages, setTrafficTopupPackages] = useState>({}); // New traffic package for adding const [newPackageGb, setNewPackageGb] = useState(10); const [newPackagePrice, setNewPackagePrice] = useState(100); // Track editing state for traffic package prices const [editingPackagePrices, setEditingPackagePrices] = useState>({}); // Traffic reset mode const [trafficResetMode, setTrafficResetMode] = useState(null); // New period for adding const [newPeriodDays, setNewPeriodDays] = useState(30); const [newPeriodPrice, setNewPeriodPrice] = useState(300); // Track editing state for period prices const [editingPeriodPrices, setEditingPeriodPrices] = useState>({}); const [activeTab, setActiveTab] = useState<'basic' | 'periods' | 'servers' | 'extra'>('basic'); // Fetch servers const { data: servers = [] } = useQuery({ queryKey: ['admin-tariffs-servers'], queryFn: () => tariffsApi.getAvailableServers(), }); // Fetch promo groups const { data: promoGroups = [] } = useQuery({ queryKey: ['admin-tariffs-promo-groups'], queryFn: () => tariffsApi.getAvailablePromoGroups(), }); // 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) => { setTariffType(data.is_daily ? 'daily' : 'period'); setName(data.name); setDescription(data.description || ''); setIsActive(data.is_active ?? true); 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 || []); setSelectedPromoGroups( data.promo_groups?.filter((pg) => pg.is_selected).map((pg) => pg.id) || [], ); 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); 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, is_active: isActive, traffic_limit_gb: toNumber(trafficLimitGb, 100), device_limit: toNumber(deviceLimit, 1), device_price_kopeks: toNumber(devicePriceKopeks) > 0 ? toNumber(devicePriceKopeks) : undefined, max_device_limit: toNumber(maxDeviceLimit) > 0 ? toNumber(maxDeviceLimit) : undefined, tier_level: toNumber(tierLevel, 1), period_prices: isDaily ? [] : periodPrices.filter((p) => p.price_kopeks >= 0), allowed_squads: selectedSquads, promo_group_ids: selectedPromoGroups.length > 0 ? selectedPromoGroups : undefined, traffic_topup_enabled: trafficTopupEnabled, traffic_topup_packages: trafficTopupPackages, max_topup_traffic_gb: toNumber(maxTopupTrafficGb), is_daily: isDaily, daily_price_kopeks: isDaily ? toNumber(dailyPriceKopeks) : 0, traffic_reset_mode: trafficResetMode, }; 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 togglePromoGroup = (groupId: number) => { setSelectedPromoGroups((prev) => prev.includes(groupId) ? prev.filter((id) => id !== groupId) : [...prev, groupId], ); }; const addPeriod = () => { const days = toNumber(newPeriodDays, 0); const price = toNumber(newPeriodPrice, 0); if (days > 0 && price > 0) { const exists = periodPrices.some((p) => p.days === days); if (!exists) { setPeriodPrices((prev) => [...prev, { days, price_kopeks: price * 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; // Validation like bot: name 2-50 chars, device_limit >= 1, tier_level 1-10 const isNameValid = name.length >= 2 && name.length <= 50; const isDeviceLimitValid = deviceLimit !== '' && toNumber(deviceLimit) >= 1; const isTierLevelValid = tierLevel !== '' && toNumber(tierLevel) >= 1 && toNumber(tierLevel) <= 10; const hasTrafficPackages = !trafficTopupEnabled || Object.keys(trafficTopupPackages).length > 0; const isValidPeriod = isNameValid && isDeviceLimitValid && isTierLevelValid && periodPrices.length > 0 && hasTrafficPackages; const isValidDaily = isNameValid && isDeviceLimitValid && isTierLevelValid && toNumber(dailyPriceKopeks) > 0 && hasTrafficPackages; const isValid = tariffType === 'period' ? isValidPeriod : tariffType === 'daily' ? isValidDaily : false; // Collect validation errors for display const validationErrors: string[] = []; if (!isNameValid) { if (name.length === 0) { validationErrors.push('nameRequired'); } else if (name.length < 2 || name.length > 50) { validationErrors.push('nameLength'); } } if (!isDeviceLimitValid) validationErrors.push('deviceLimitRequired'); if (!isTierLevelValid) validationErrors.push('tierLevelInvalid'); if (tariffType === 'period' && periodPrices.length === 0) { validationErrors.push('periodsRequired'); } if (tariffType === 'daily' && toNumber(dailyPriceKopeks) === 0) { validationErrors.push('dailyPriceRequired'); } if (trafficTopupEnabled && Object.keys(trafficTopupPackages).length === 0) { validationErrors.push('trafficPackagesRequired'); } // 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'; 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={`input ${!isNameValid && name.length > 0 ? 'border-error-500/50' : ''}`} placeholder={ isDaily ? t('admin.tariffs.nameExampleDaily') : t('admin.tariffs.nameExamplePeriod') } maxLength={50} />

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

{name.length > 0 && (name.length < 2 || name.length > 50) && (

{t('admin.tariffs.validation.nameLength')}

)}
{/* Description */}