mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 10:03:46 +00:00
fix(admin-wheel): unify statistics rendering + fill config UI gaps
Statistics tab now uses the shared StatCard + BreakdownList (ranked bars + share %) and the currency-aware formatter — matching the rest of the admin stats instead of ad-hoc 'card p-4 text-center' / text-3xl tiles — and gains proper loading, error and empty states. Also surface backend-supported config the form couldn't reach (forcing DB edits): - promo_validity_days (model + schema-bounded 1-365) is now editable in Settings. - per-prize manual_probability override (0-1) now has an input in the prize form.
This commit is contained in:
@@ -1756,7 +1756,8 @@
|
|||||||
"dailyLimit": "Daily Spin Limit (0 = unlimited)",
|
"dailyLimit": "Daily Spin Limit (0 = unlimited)",
|
||||||
"minSubDays": "Min subscription days for day payment",
|
"minSubDays": "Min subscription days for day payment",
|
||||||
"promocodes": "Promocodes",
|
"promocodes": "Promocodes",
|
||||||
"promoPrefix": "Promo code prefix"
|
"promoPrefix": "Promo code prefix",
|
||||||
|
"promoValidityDays": "Promo code validity (days)"
|
||||||
},
|
},
|
||||||
"starsNotEnabledGlobally": "Star payments are not enabled in Payment Methods. Enable \"Telegram Stars\" in the Payment Methods section.",
|
"starsNotEnabledGlobally": "Star payments are not enabled in Payment Methods. Enable \"Telegram Stars\" in the Payment Methods section.",
|
||||||
"prizes": {
|
"prizes": {
|
||||||
@@ -1783,7 +1784,9 @@
|
|||||||
"emoji": "Emoji",
|
"emoji": "Emoji",
|
||||||
"color": "Color",
|
"color": "Color",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"worth": "Worth"
|
"worth": "Worth",
|
||||||
|
"probability": "Manual probability (0–1)",
|
||||||
|
"probabilityHint": "Empty = auto by RTP"
|
||||||
},
|
},
|
||||||
"promo": {
|
"promo": {
|
||||||
"title": "Promo Code Settings",
|
"title": "Promo Code Settings",
|
||||||
@@ -1801,7 +1804,9 @@
|
|||||||
"times": "times",
|
"times": "times",
|
||||||
"times_one": "{{count}} time",
|
"times_one": "{{count}} time",
|
||||||
"times_other": "{{count}} times",
|
"times_other": "{{count}} times",
|
||||||
"topWins": "Top Wins"
|
"topWins": "Top Wins",
|
||||||
|
"loadError": "Failed to load statistics",
|
||||||
|
"empty": "No spins yet"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"broadcasts": {
|
"broadcasts": {
|
||||||
|
|||||||
@@ -1432,7 +1432,8 @@
|
|||||||
"promoPrefix": "پیشوند کد تخفیف",
|
"promoPrefix": "پیشوند کد تخفیف",
|
||||||
"spinCost": "هزینه چرخش",
|
"spinCost": "هزینه چرخش",
|
||||||
"limitsAndRtp": "محدودیتها و RTP",
|
"limitsAndRtp": "محدودیتها و RTP",
|
||||||
"promocodes": "کدهای تخفیف"
|
"promocodes": "کدهای تخفیف",
|
||||||
|
"promoValidityDays": "اعتبار کد تخفیف (روز)"
|
||||||
},
|
},
|
||||||
"starsNotEnabledGlobally": "پرداخت با ستاره در روشهای پرداخت فعال نیست. «Telegram Stars» را در بخش «روشهای پرداخت» فعال کنید.",
|
"starsNotEnabledGlobally": "پرداخت با ستاره در روشهای پرداخت فعال نیست. «Telegram Stars» را در بخش «روشهای پرداخت» فعال کنید.",
|
||||||
"prizes": {
|
"prizes": {
|
||||||
@@ -1459,7 +1460,9 @@
|
|||||||
"emoji": "ایموجی",
|
"emoji": "ایموجی",
|
||||||
"color": "رنگ",
|
"color": "رنگ",
|
||||||
"active": "فعال",
|
"active": "فعال",
|
||||||
"worth": "ارزش"
|
"worth": "ارزش",
|
||||||
|
"probability": "احتمال دستی (۰–۱)",
|
||||||
|
"probabilityHint": "خالی = خودکار بر اساس RTP"
|
||||||
},
|
},
|
||||||
"promo": {
|
"promo": {
|
||||||
"title": "تنظیمات کد تخفیف",
|
"title": "تنظیمات کد تخفیف",
|
||||||
@@ -1476,7 +1479,9 @@
|
|||||||
"prizeDistribution": "توزیع جوایز",
|
"prizeDistribution": "توزیع جوایز",
|
||||||
"times": "بار",
|
"times": "بار",
|
||||||
"times_other": "{{count}} بار",
|
"times_other": "{{count}} بار",
|
||||||
"topWins": "برترین برندهها"
|
"topWins": "برترین برندهها",
|
||||||
|
"loadError": "بارگذاری آمار ناموفق بود",
|
||||||
|
"empty": "هنوز چرخشی انجام نشده"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"broadcasts": {
|
"broadcasts": {
|
||||||
|
|||||||
@@ -1782,7 +1782,8 @@
|
|||||||
"dailyLimit": "Дневной лимит вращений (0 = безлимит)",
|
"dailyLimit": "Дневной лимит вращений (0 = безлимит)",
|
||||||
"minSubDays": "Мин. дней подписки для оплаты днями",
|
"minSubDays": "Мин. дней подписки для оплаты днями",
|
||||||
"promocodes": "Промокоды",
|
"promocodes": "Промокоды",
|
||||||
"promoPrefix": "Префикс промокодов"
|
"promoPrefix": "Префикс промокодов",
|
||||||
|
"promoValidityDays": "Срок действия промокода (дней)"
|
||||||
},
|
},
|
||||||
"starsNotEnabledGlobally": "Оплата звёздами не включена в платёжных методах. Включите метод «Telegram Stars» в разделе «Платёжные методы».",
|
"starsNotEnabledGlobally": "Оплата звёздами не включена в платёжных методах. Включите метод «Telegram Stars» в разделе «Платёжные методы».",
|
||||||
"prizes": {
|
"prizes": {
|
||||||
@@ -1809,7 +1810,9 @@
|
|||||||
"emoji": "Эмодзи",
|
"emoji": "Эмодзи",
|
||||||
"color": "Цвет",
|
"color": "Цвет",
|
||||||
"active": "Активен",
|
"active": "Активен",
|
||||||
"worth": "Стоимость"
|
"worth": "Стоимость",
|
||||||
|
"probability": "Ручная вероятность (0–1)",
|
||||||
|
"probabilityHint": "Пусто — авто по RTP"
|
||||||
},
|
},
|
||||||
"promo": {
|
"promo": {
|
||||||
"title": "Настройки промокода",
|
"title": "Настройки промокода",
|
||||||
@@ -1828,7 +1831,9 @@
|
|||||||
"times_one": "{{count}} раз",
|
"times_one": "{{count}} раз",
|
||||||
"times_few": "{{count}} раза",
|
"times_few": "{{count}} раза",
|
||||||
"times_many": "{{count}} раз",
|
"times_many": "{{count}} раз",
|
||||||
"topWins": "Топ выигрышей"
|
"topWins": "Топ выигрышей",
|
||||||
|
"loadError": "Не удалось загрузить статистику",
|
||||||
|
"empty": "Пока нет вращений"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"broadcasts": {
|
"broadcasts": {
|
||||||
|
|||||||
@@ -1509,7 +1509,8 @@
|
|||||||
"promoPrefix": "优惠码前缀",
|
"promoPrefix": "优惠码前缀",
|
||||||
"spinCost": "旋转费用",
|
"spinCost": "旋转费用",
|
||||||
"limitsAndRtp": "限制和RTP",
|
"limitsAndRtp": "限制和RTP",
|
||||||
"promocodes": "促销码"
|
"promocodes": "促销码",
|
||||||
|
"promoValidityDays": "促销码有效期(天)"
|
||||||
},
|
},
|
||||||
"starsNotEnabledGlobally": "星星支付未在支付方式中启用。请在「支付方式」中启用「Telegram Stars」。",
|
"starsNotEnabledGlobally": "星星支付未在支付方式中启用。请在「支付方式」中启用「Telegram Stars」。",
|
||||||
"prizes": {
|
"prizes": {
|
||||||
@@ -1536,7 +1537,9 @@
|
|||||||
"emoji": "表情",
|
"emoji": "表情",
|
||||||
"color": "颜色",
|
"color": "颜色",
|
||||||
"active": "启用",
|
"active": "启用",
|
||||||
"worth": "价值"
|
"worth": "价值",
|
||||||
|
"probability": "手动概率 (0–1)",
|
||||||
|
"probabilityHint": "留空 = 按 RTP 自动"
|
||||||
},
|
},
|
||||||
"promo": {
|
"promo": {
|
||||||
"title": "优惠码设置",
|
"title": "优惠码设置",
|
||||||
@@ -1553,7 +1556,9 @@
|
|||||||
"prizeDistribution": "奖品分布",
|
"prizeDistribution": "奖品分布",
|
||||||
"times": "次",
|
"times": "次",
|
||||||
"times_other": "{{count}} 次",
|
"times_other": "{{count}} 次",
|
||||||
"topWins": "最高奖品"
|
"topWins": "最高奖品",
|
||||||
|
"loadError": "无法加载统计数据",
|
||||||
|
"empty": "暂无抽奖记录"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"broadcasts": {
|
"broadcasts": {
|
||||||
|
|||||||
@@ -40,8 +40,13 @@ import {
|
|||||||
StarIcon,
|
StarIcon,
|
||||||
TicketIcon,
|
TicketIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
|
WalletIcon,
|
||||||
|
WheelIcon,
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
} from '@/components/icons';
|
} from '@/components/icons';
|
||||||
|
import { StatCard } from '@/components/stats';
|
||||||
|
import { BreakdownList } from '@/components/sales-stats/BreakdownList';
|
||||||
|
import { useCurrency } from '@/hooks/useCurrency';
|
||||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||||
import { toNumber } from '../utils/inputHelpers';
|
import { toNumber } from '../utils/inputHelpers';
|
||||||
|
|
||||||
@@ -166,6 +171,7 @@ export default function AdminWheel() {
|
|||||||
const confirmDelete = useDestructiveConfirm();
|
const confirmDelete = useDestructiveConfirm();
|
||||||
const { capabilities } = usePlatform();
|
const { capabilities } = usePlatform();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
|
const { formatWithCurrency } = useCurrency();
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<Tab>('settings');
|
const [activeTab, setActiveTab] = useState<Tab>('settings');
|
||||||
const [expandedPrizeId, setExpandedPrizeId] = useState<number | null>(null);
|
const [expandedPrizeId, setExpandedPrizeId] = useState<number | null>(null);
|
||||||
@@ -184,6 +190,7 @@ export default function AdminWheel() {
|
|||||||
daily_spin_limit: number | '';
|
daily_spin_limit: number | '';
|
||||||
min_subscription_days_for_day_payment: number | '';
|
min_subscription_days_for_day_payment: number | '';
|
||||||
promo_prefix: string;
|
promo_prefix: string;
|
||||||
|
promo_validity_days: number | '';
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
// Fetch config
|
// Fetch config
|
||||||
@@ -193,7 +200,11 @@ export default function AdminWheel() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Fetch statistics
|
// Fetch statistics
|
||||||
const { data: stats } = useQuery({
|
const {
|
||||||
|
data: stats,
|
||||||
|
isLoading: statsLoading,
|
||||||
|
error: statsError,
|
||||||
|
} = useQuery({
|
||||||
queryKey: ['admin-wheel-stats'],
|
queryKey: ['admin-wheel-stats'],
|
||||||
queryFn: () => adminWheelApi.getStatistics(),
|
queryFn: () => adminWheelApi.getStatistics(),
|
||||||
enabled: activeTab === 'statistics',
|
enabled: activeTab === 'statistics',
|
||||||
@@ -214,6 +225,7 @@ export default function AdminWheel() {
|
|||||||
daily_spin_limit: config.daily_spin_limit,
|
daily_spin_limit: config.daily_spin_limit,
|
||||||
min_subscription_days_for_day_payment: config.min_subscription_days_for_day_payment,
|
min_subscription_days_for_day_payment: config.min_subscription_days_for_day_payment,
|
||||||
promo_prefix: config.promo_prefix,
|
promo_prefix: config.promo_prefix,
|
||||||
|
promo_validity_days: config.promo_validity_days,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -232,7 +244,8 @@ export default function AdminWheel() {
|
|||||||
settingsForm.daily_spin_limit !== config.daily_spin_limit ||
|
settingsForm.daily_spin_limit !== config.daily_spin_limit ||
|
||||||
settingsForm.min_subscription_days_for_day_payment !==
|
settingsForm.min_subscription_days_for_day_payment !==
|
||||||
config.min_subscription_days_for_day_payment ||
|
config.min_subscription_days_for_day_payment ||
|
||||||
settingsForm.promo_prefix !== config.promo_prefix);
|
settingsForm.promo_prefix !== config.promo_prefix ||
|
||||||
|
settingsForm.promo_validity_days !== config.promo_validity_days);
|
||||||
|
|
||||||
// Update config mutation
|
// Update config mutation
|
||||||
const updateConfigMutation = useMutation({
|
const updateConfigMutation = useMutation({
|
||||||
@@ -669,6 +682,26 @@ export default function AdminWheel() {
|
|||||||
className="input w-full"
|
className="input w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="mb-2 block text-sm font-medium text-dark-300">
|
||||||
|
{t('admin.wheel.settings.promoValidityDays')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={settingsForm?.promo_validity_days ?? config.promo_validity_days}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
setSettingsForm((prev) =>
|
||||||
|
prev
|
||||||
|
? { ...prev, promo_validity_days: val === '' ? '' : parseInt(val) || 0 }
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
min={1}
|
||||||
|
max={365}
|
||||||
|
className="input w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -702,6 +735,10 @@ export default function AdminWheel() {
|
|||||||
settingsForm.min_subscription_days_for_day_payment,
|
settingsForm.min_subscription_days_for_day_payment,
|
||||||
config.min_subscription_days_for_day_payment,
|
config.min_subscription_days_for_day_payment,
|
||||||
),
|
),
|
||||||
|
promo_validity_days: toNumber(
|
||||||
|
settingsForm.promo_validity_days,
|
||||||
|
config.promo_validity_days,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disabled={updateConfigMutation.isPending}
|
disabled={updateConfigMutation.isPending}
|
||||||
@@ -833,79 +870,82 @@ export default function AdminWheel() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Statistics Tab */}
|
{/* Statistics Tab */}
|
||||||
{activeTab === 'statistics' && stats && (
|
{activeTab === 'statistics' && statsLoading && (
|
||||||
|
<div className="flex justify-center py-12">
|
||||||
|
<div className="h-8 w-8 animate-spin rounded-full border-2 border-accent-500 border-t-transparent" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'statistics' && !statsLoading && statsError && (
|
||||||
|
<div className="bento-card py-8 text-center text-error-400">
|
||||||
|
{t('admin.wheel.statistics.loadError')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'statistics' &&
|
||||||
|
!statsLoading &&
|
||||||
|
!statsError &&
|
||||||
|
stats &&
|
||||||
|
stats.total_spins === 0 && (
|
||||||
|
<div className="bento-card py-12 text-center text-dark-400">
|
||||||
|
{t('admin.wheel.statistics.empty')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'statistics' && stats && stats.total_spins > 0 && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Stats cards */}
|
{/* Headline metrics — shared StatCard, like the rest of the admin stats */}
|
||||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||||
<div className="card p-4 text-center">
|
<StatCard
|
||||||
<div className="text-3xl font-bold text-accent-400">{stats.total_spins}</div>
|
label={t('admin.wheel.statistics.totalSpins')}
|
||||||
<div className="text-sm text-dark-400">{t('admin.wheel.statistics.totalSpins')}</div>
|
value={stats.total_spins}
|
||||||
</div>
|
icon={<WheelIcon className="h-5 w-5" />}
|
||||||
<div className="card p-4 text-center">
|
tone="accent"
|
||||||
<div className="text-3xl font-bold text-success-400">
|
/>
|
||||||
{(stats.total_revenue_kopeks / 100).toFixed(0)}₽
|
<StatCard
|
||||||
</div>
|
label={t('admin.wheel.statistics.revenue')}
|
||||||
<div className="text-sm text-dark-400">{t('admin.wheel.statistics.revenue')}</div>
|
value={formatWithCurrency(stats.total_revenue_kopeks / 100, 0)}
|
||||||
</div>
|
icon={<WalletIcon className="h-5 w-5" />}
|
||||||
<div className="card p-4 text-center">
|
tone="success"
|
||||||
<div className="text-3xl font-bold text-warning-400">
|
/>
|
||||||
{(stats.total_payout_kopeks / 100).toFixed(0)}₽
|
<StatCard
|
||||||
</div>
|
label={t('admin.wheel.statistics.payouts')}
|
||||||
<div className="text-sm text-dark-400">{t('admin.wheel.statistics.payouts')}</div>
|
value={formatWithCurrency(stats.total_payout_kopeks / 100, 0)}
|
||||||
</div>
|
icon={<GiftIcon className="h-5 w-5" />}
|
||||||
<div className="card p-4 text-center">
|
tone="warning"
|
||||||
<div
|
/>
|
||||||
className={`text-3xl font-bold ${
|
<StatCard
|
||||||
stats.actual_rtp_percent <= stats.configured_rtp_percent
|
label={`${t('admin.wheel.statistics.actualRtp')} · ${t('admin.wheel.statistics.targetRtp')} ${stats.configured_rtp_percent}%`}
|
||||||
? 'text-success-400'
|
value={`${stats.actual_rtp_percent.toFixed(1)}%`}
|
||||||
: 'text-error-400'
|
icon={<ChartIcon className="h-5 w-5" />}
|
||||||
}`}
|
tone={stats.actual_rtp_percent <= stats.configured_rtp_percent ? 'success' : 'error'}
|
||||||
>
|
/>
|
||||||
{stats.actual_rtp_percent.toFixed(1)}%
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-dark-400">
|
|
||||||
{t('admin.wheel.statistics.actualRtp')} ({t('admin.wheel.statistics.targetRtp')}:{' '}
|
|
||||||
{stats.configured_rtp_percent}%)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Prize distribution */}
|
{/* Prize distribution — shared BreakdownList (ranked bars + share %) */}
|
||||||
{stats.prizes_distribution.length > 0 && (
|
{stats.prizes_distribution.length > 0 && (
|
||||||
<div className="card p-4">
|
<BreakdownList
|
||||||
<h3 className="mb-3 font-semibold text-dark-100">
|
title={t('admin.wheel.statistics.prizeDistribution')}
|
||||||
{t('admin.wheel.statistics.prizeDistribution')}
|
items={stats.prizes_distribution.map((prize) => ({
|
||||||
</h3>
|
key: prize.display_name,
|
||||||
<div className="space-y-2">
|
label: prize.display_name,
|
||||||
{stats.prizes_distribution.map((prize, i) => (
|
value: prize.count,
|
||||||
<div key={i} className="flex items-center justify-between">
|
}))}
|
||||||
<span className="text-dark-300">{prize.display_name}</span>
|
valueFormatter={(v) => t('admin.wheel.statistics.times', { count: v })}
|
||||||
<span className="text-dark-100">
|
/>
|
||||||
{t('admin.wheel.statistics.times', { count: prize.count })}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Top wins */}
|
{/* Top wins — same BreakdownList, ranked by win value */}
|
||||||
{stats.top_wins.length > 0 && (
|
{stats.top_wins.length > 0 && (
|
||||||
<div className="card p-4">
|
<BreakdownList
|
||||||
<h3 className="mb-3 font-semibold text-dark-100">
|
title={t('admin.wheel.statistics.topWins')}
|
||||||
{t('admin.wheel.statistics.topWins')}
|
items={stats.top_wins.slice(0, 8).map((win, i) => ({
|
||||||
</h3>
|
key: `${win.user_id}-${i}`,
|
||||||
<div className="space-y-2">
|
label: `${win.username || `#${win.user_id}`} · ${win.prize_display_name}`,
|
||||||
{stats.top_wins.slice(0, 5).map((win, i) => (
|
value: win.prize_value_kopeks / 100,
|
||||||
<div key={i} className="flex items-center justify-between">
|
}))}
|
||||||
<span className="text-dark-300">{win.username || `User #${win.user_id}`}</span>
|
valueFormatter={(v) => formatWithCurrency(v, 0)}
|
||||||
<span className="text-dark-100">
|
/>
|
||||||
{win.prize_display_name} ({(win.prize_value_kopeks / 100).toFixed(0)}₽)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1097,6 +1137,33 @@ function InlinePrizeForm({
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Manual probability override (optional; empty = auto by RTP) */}
|
||||||
|
<div>
|
||||||
|
<label className="mb-2 block text-sm font-medium text-dark-300">
|
||||||
|
{t('admin.wheel.prizes.fields.probability')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={formData.manual_probability ?? ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
manual_probability:
|
||||||
|
val === '' ? null : Math.min(1, Math.max(0, parseFloat(val) || 0)),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
placeholder="0.00 – 1.00"
|
||||||
|
className="input w-full sm:w-1/2"
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-xs text-dark-500">
|
||||||
|
{t('admin.wheel.prizes.fields.probabilityHint')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Promocode settings */}
|
{/* Promocode settings */}
|
||||||
{formData.prize_type === 'promocode' && (
|
{formData.prize_type === 'promocode' && (
|
||||||
<div className="space-y-3 rounded-lg bg-dark-700/50 p-3">
|
<div className="space-y-3 rounded-lg bg-dark-700/50 p-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user