feat(coupons): лимит на пользователя и удаление партии в кабинете

Парная фронтовая часть к бэкенду (бот: 82609441).

- Поле «Лимит на пользователя» в форме создания партии: 0 — без
  ограничения, для раздач и конкурсов ставится 1. Значение показывается в
  карточке партии, если задано.
- Кнопка «Удалить партию» с подтверждением: отдельно от отзыва, который
  лишь гасит ссылки. Если в партии есть погашенные купоны, подтверждение
  предупреждает о потере истории активаций (выданные подписки не
  отзываются). После удаления — возврат к списку.
- API: max_per_user в типах партии и запросе создания, deleteBatch.

Локали ru/en — раздел admin.coupons в zh/fa отсутствует целиком (купоны там
никогда не переводились), поэтому новые ключи добавлены в том же объёме.
This commit is contained in:
Fringg
2026-07-29 22:02:07 +03:00
parent c9afea7a50
commit 390da33914
5 changed files with 129 additions and 7 deletions

View File

@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { createNumberInputHandler } from '../utils/inputHelpers';
import { couponsApi, CouponBatchCreated } from '../api/coupons';
import { couponsApi, type CouponBatchCreated } from '../api/coupons';
import { tariffsApi } from '../api/tariffs';
import { usePlatform } from '../platform/hooks/usePlatform';
import { copyToClipboard } from '../utils/clipboard';
@@ -33,6 +33,7 @@ export default function AdminCouponCreate() {
const [couponsCount, setCouponsCount] = useState<number | ''>(50);
const [priceRubles, setPriceRubles] = useState<number | ''>('');
const [validDays, setValidDays] = useState<number | ''>('');
const [maxPerUser, setMaxPerUser] = useState<number | ''>('');
const [validationErrors, setValidationErrors] = useState<string[]>([]);
const [serverError, setServerError] = useState<string | null>(null);
@@ -77,6 +78,7 @@ export default function AdminCouponCreate() {
coupons_count: Number(couponsCount),
wholesale_price_kopeks: priceRubles === '' ? 0 : Math.round(Number(priceRubles) * 100),
valid_days: validDays === '' ? 0 : Number(validDays),
max_per_user: maxPerUser === '' ? 0 : Number(maxPerUser),
});
};
@@ -275,6 +277,21 @@ export default function AdminCouponCreate() {
/>
<p className="mt-1 text-xs text-dark-500">{t('admin.coupons.form.validDaysHint')}</p>
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-dark-300">
{t('admin.coupons.form.maxPerUser')}
</label>
<input
type="number"
value={maxPerUser}
onChange={createNumberInputHandler(setMaxPerUser, 0, 500)}
min={0}
max={500}
placeholder="0"
className="input w-full"
/>
<p className="mt-1 text-xs text-dark-500">{t('admin.coupons.form.maxPerUserHint')}</p>
</div>
</div>
{/* Actions */}