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 [isDefault, setIsDefault] = useState(false); 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); setIsDefault(data.is_default ?? false); 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 | '') => { setPeriodDiscounts( periodDiscounts.map((item, i) => (i === index ? { ...item, [field]: value } : item)), ); }; const handleSubmit = () => { // Convert periodDiscounts array to Record const periodDiscountsRecord: Record = {}; periodDiscounts.forEach((pd) => { const days = pd.days === '' ? 0 : pd.days; const percent = pd.percent === '' ? 0 : pd.percent; if (days > 0 && percent >= 0) { periodDiscountsRecord[days] = 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: periodDiscountsRecord, apply_discounts_to_addons: applyToAddons, auto_assign_total_spent_kopeks: autoAssignVal > 0 ? Math.round(autoAssignVal * 100) : 0, is_default: isDefault, }; 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 ${name.length > 0 && name.trim().length === 0 ? 'border-error-500/50' : ''}`} placeholder={t('admin.promoGroups.form.namePlaceholder')} /> {name.length > 0 && name.trim().length === 0 && ( {t('admin.promoGroups.form.nameRequired')} )} {/* 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) => ( { const val = e.target.value; if (val === '') return updatePeriodDiscount(index, 'days', ''); const num = parseInt(val); if (!isNaN(num)) updatePeriodDiscount(index, 'days', num); }} className="input w-20" min={1} placeholder={t('admin.promoGroups.form.daysPlaceholder')} /> {t('admin.promoGroups.form.arrow')} { const val = e.target.value; if (val === '') return updatePeriodDiscount(index, 'percent', ''); const num = parseInt(val); if (!isNaN(num)) updatePeriodDiscount(index, 'percent', num); }} 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-11 rounded-full transition-colors ${ applyToAddons ? 'bg-accent-500' : 'bg-dark-600' }`} > {t('admin.promoGroups.form.applyToAddons')} {/* Default group */} setIsDefault(!isDefault)} className={`relative h-6 w-11 rounded-full transition-colors ${ isDefault ? 'bg-accent-500' : 'bg-dark-600' }`} > {t('admin.promoGroups.form.isDefault')} {/* Footer */} navigate('/admin/promo-groups')} className="btn-secondary"> {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.nameRequired')}
{t('admin.promoGroups.form.periodHint')}
{t('admin.promoGroups.form.noPeriods')}
{t('admin.promoGroups.form.autoAssignHint')}