import { useState, useCallback } from 'react'; import { useNavigate, useParams } from 'react-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { promocodesApi, PromoGroup, PromoGroupCreateRequest, PromoGroupUpdateRequest, } from '../api/promocodes'; import { AdminBackButton } from '../components/admin'; // Icons const PlusIcon = () => ( ); const TrashIcon = () => ( ); const RefreshIcon = () => ( ); interface PeriodDiscount { days: number; percent: number; } export default function AdminPromoGroupCreate() { const { t } = useTranslation(); const navigate = useNavigate(); const { id } = useParams<{ id: string }>(); const queryClient = useQueryClient(); const isEdit = !!id; // Form state const [name, setName] = useState(''); const [serverDiscount, setServerDiscount] = useState(0); const [trafficDiscount, setTrafficDiscount] = useState(0); const [deviceDiscount, setDeviceDiscount] = useState(0); const [applyToAddons, setApplyToAddons] = useState(true); const [autoAssignSpent, setAutoAssignSpent] = useState(0); const [periodDiscounts, setPeriodDiscounts] = useState([]); // Fetch promo group for editing const { isLoading: isLoadingGroup } = useQuery({ queryKey: ['admin-promo-group', id], queryFn: () => promocodesApi.getPromoGroup(Number(id)), enabled: isEdit, staleTime: 0, gcTime: 0, refetchOnMount: true, refetchOnWindowFocus: false, select: useCallback((data: PromoGroup) => { setName(data.name); setServerDiscount(data.server_discount_percent || 0); setTrafficDiscount(data.traffic_discount_percent || 0); setDeviceDiscount(data.device_discount_percent || 0); setApplyToAddons(data.apply_discounts_to_addons ?? true); setAutoAssignSpent( data.auto_assign_total_spent_kopeks ? data.auto_assign_total_spent_kopeks / 100 : 0, ); if (data.period_discounts && typeof data.period_discounts === 'object') { setPeriodDiscounts( Object.entries(data.period_discounts).map(([days, percent]) => ({ days: parseInt(days), percent: typeof percent === 'number' ? percent : 0, })), ); } return data; }, []), }); // Create mutation const createMutation = useMutation({ mutationFn: promocodesApi.createPromoGroup, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-promo-groups'] }); navigate('/admin/promo-groups'); }, }); // Update mutation const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: number; data: PromoGroupUpdateRequest }) => promocodesApi.updatePromoGroup(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-promo-groups'] }); navigate('/admin/promo-groups'); }, }); const addPeriodDiscount = () => { setPeriodDiscounts([...periodDiscounts, { days: 30, percent: 0 }]); }; const removePeriodDiscount = (index: number) => { setPeriodDiscounts(periodDiscounts.filter((_, i) => i !== index)); }; const updatePeriodDiscount = (index: number, field: 'days' | 'percent', value: number) => { const updated = [...periodDiscounts]; updated[index][field] = value; setPeriodDiscounts(updated); }; const handleSubmit = () => { // Convert periodDiscounts array to Record const periodDiscountsRecord: Record = {}; periodDiscounts.forEach((pd) => { if (pd.days > 0 && pd.percent > 0) { periodDiscountsRecord[pd.days] = pd.percent; } }); const serverVal = serverDiscount === '' ? 0 : serverDiscount; const trafficVal = trafficDiscount === '' ? 0 : trafficDiscount; const deviceVal = deviceDiscount === '' ? 0 : deviceDiscount; const autoAssignVal = autoAssignSpent === '' ? 0 : autoAssignSpent; const data: PromoGroupCreateRequest | PromoGroupUpdateRequest = { name, server_discount_percent: serverVal, traffic_discount_percent: trafficVal, device_discount_percent: deviceVal, period_discounts: Object.keys(periodDiscountsRecord).length > 0 ? periodDiscountsRecord : undefined, apply_discounts_to_addons: applyToAddons, auto_assign_total_spent_kopeks: autoAssignVal > 0 ? Math.round(autoAssignVal * 100) : null, }; if (isEdit) { updateMutation.mutate({ id: Number(id), data }); } else { createMutation.mutate(data as PromoGroupCreateRequest); } }; const isLoading = createMutation.isPending || updateMutation.isPending; const isValid = name.trim().length > 0; // Loading state if (isEdit && isLoadingGroup) { return ( ); } return ( {/* Header */} {isEdit ? t('admin.promoGroups.editTitle') : t('admin.promoGroups.createTitle')} {t('admin.promoGroups.subtitle')} {/* Form */} {/* Name */} {t('admin.promoGroups.form.name')} * setName(e.target.value)} className="input" placeholder={t('admin.promoGroups.form.namePlaceholder')} /> {/* Category Discounts */} {t('admin.promoGroups.form.categoryDiscounts')} {t('admin.promoGroups.servers')}: { const val = e.target.value; if (val === '') { setServerDiscount(''); } else { setServerDiscount(Math.min(100, Math.max(0, parseInt(val) || 0))); } }} className="input w-20" min={0} max={100} placeholder="0" /> % {t('admin.promoGroups.traffic')}: { const val = e.target.value; if (val === '') { setTrafficDiscount(''); } else { setTrafficDiscount(Math.min(100, Math.max(0, parseInt(val) || 0))); } }} className="input w-20" min={0} max={100} placeholder="0" /> % {t('admin.promoGroups.devices')}: { const val = e.target.value; if (val === '') { setDeviceDiscount(''); } else { setDeviceDiscount(Math.min(100, Math.max(0, parseInt(val) || 0))); } }} className="input w-20" min={0} max={100} placeholder="0" /> % {/* Period Discounts */} {t('admin.promoGroups.form.periodDiscounts')} {t('admin.promoGroups.form.add')} {t('admin.promoGroups.form.periodHint')} {periodDiscounts.length === 0 ? ( {t('admin.promoGroups.form.noPeriods')} ) : ( {periodDiscounts.map((pd, index) => ( updatePeriodDiscount( index, 'days', Math.max(1, parseInt(e.target.value) || 1), ) } className="input w-20" min={1} placeholder={t('admin.promoGroups.form.daysPlaceholder')} /> {t('admin.promoGroups.form.arrow')} updatePeriodDiscount( index, 'percent', Math.min(100, Math.max(0, parseInt(e.target.value) || 0)), ) } className="input w-20" min={0} max={100} placeholder="%" /> % removePeriodDiscount(index)} className="p-1 text-dark-400 transition-colors hover:text-error-400" > ))} )} {/* Auto-assign */} {t('admin.promoGroups.form.autoAssign')} { const val = e.target.value; if (val === '') { setAutoAssignSpent(''); } else { setAutoAssignSpent(Math.max(0, parseFloat(val) || 0)); } }} className="input w-32" min={0} placeholder="0" /> {t('admin.promoGroups.form.rub')} {t('admin.promoGroups.form.autoAssignHint')} {/* Apply to addons */} setApplyToAddons(!applyToAddons)} className={`relative h-6 w-10 rounded-full transition-colors ${ applyToAddons ? 'bg-accent-500' : 'bg-dark-600' }`} > {t('admin.promoGroups.form.applyToAddons')} {/* Footer */} navigate('/admin/promo-groups')} className="px-4 py-2 text-dark-300 transition-colors hover:text-dark-100" > {t('admin.promoGroups.form.cancel')} {isLoading && } {isLoading ? t('admin.promoGroups.form.saving') : t('admin.promoGroups.form.save')} ); }
{t('admin.promoGroups.subtitle')}
{t('admin.promoGroups.form.periodHint')}
{t('admin.promoGroups.form.noPeriods')}
{t('admin.promoGroups.form.autoAssignHint')}