mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
feat(admin): quick amounts editor in payment method settings
This commit is contained in:
@@ -1607,6 +1607,12 @@
|
|||||||
"subOptions": "Payment options",
|
"subOptions": "Payment options",
|
||||||
"minAmount": "Min amount (kopeks)",
|
"minAmount": "Min amount (kopeks)",
|
||||||
"maxAmount": "Max amount (kopeks)",
|
"maxAmount": "Max amount (kopeks)",
|
||||||
|
"quickAmounts": "Quick amounts (₽)",
|
||||||
|
"quickAmountsHint": "Quick top-up amount buttons. Empty — defaults are used: {{defaults}} ₽",
|
||||||
|
"quickAmountsPlaceholder": "Amount in rubles",
|
||||||
|
"quickAmountsAdd": "Add",
|
||||||
|
"quickAmountsInvalid": "Enter a positive amount in rubles",
|
||||||
|
"quickAmountsLimit": "No more than 10 amounts",
|
||||||
"conditions": "Display conditions",
|
"conditions": "Display conditions",
|
||||||
"userTypeFilter": "User type",
|
"userTypeFilter": "User type",
|
||||||
"userTypeAll": "All",
|
"userTypeAll": "All",
|
||||||
|
|||||||
@@ -1629,6 +1629,12 @@
|
|||||||
"subOptions": "Варианты оплаты",
|
"subOptions": "Варианты оплаты",
|
||||||
"minAmount": "Мин. сумма (коп.)",
|
"minAmount": "Мин. сумма (коп.)",
|
||||||
"maxAmount": "Макс. сумма (коп.)",
|
"maxAmount": "Макс. сумма (коп.)",
|
||||||
|
"quickAmounts": "Быстрые суммы (₽)",
|
||||||
|
"quickAmountsHint": "Кнопки быстрого выбора суммы пополнения. Пусто — используются значения по умолчанию: {{defaults}} ₽",
|
||||||
|
"quickAmountsPlaceholder": "Сумма в рублях",
|
||||||
|
"quickAmountsAdd": "Добавить",
|
||||||
|
"quickAmountsInvalid": "Введите положительную сумму в рублях",
|
||||||
|
"quickAmountsLimit": "Не более 10 сумм",
|
||||||
"conditions": "Условия отображения",
|
"conditions": "Условия отображения",
|
||||||
"userTypeFilter": "Тип пользователя",
|
"userTypeFilter": "Тип пользователя",
|
||||||
"userTypeAll": "Все",
|
"userTypeAll": "Все",
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ export default function AdminPaymentMethodEdit() {
|
|||||||
const [promoGroupFilterMode, setPromoGroupFilterMode] = useState<'all' | 'selected'>('all');
|
const [promoGroupFilterMode, setPromoGroupFilterMode] = useState<'all' | 'selected'>('all');
|
||||||
const [selectedPromoGroupIds, setSelectedPromoGroupIds] = useState<number[]>([]);
|
const [selectedPromoGroupIds, setSelectedPromoGroupIds] = useState<number[]>([]);
|
||||||
const [openUrlDirect, setOpenUrlDirect] = useState(false);
|
const [openUrlDirect, setOpenUrlDirect] = useState(false);
|
||||||
|
const [quickAmounts, setQuickAmounts] = useState<number[]>([]);
|
||||||
|
const [quickAmountInput, setQuickAmountInput] = useState('');
|
||||||
|
const [quickAmountsError, setQuickAmountsError] = useState<string | null>(null);
|
||||||
|
|
||||||
// Initialize state when config loads
|
// Initialize state when config loads
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -56,6 +59,7 @@ export default function AdminPaymentMethodEdit() {
|
|||||||
setSelectedPromoGroupIds(config.allowed_promo_group_ids);
|
setSelectedPromoGroupIds(config.allowed_promo_group_ids);
|
||||||
// ?? false — защита от stale-config (backend ещё не пришёл с миграцией)
|
// ?? false — защита от stale-config (backend ещё не пришёл с миграцией)
|
||||||
setOpenUrlDirect(config.open_url_direct ?? false);
|
setOpenUrlDirect(config.open_url_direct ?? false);
|
||||||
|
setQuickAmounts((config.quick_amounts ?? []).map((kopeks) => kopeks / 100));
|
||||||
}
|
}
|
||||||
}, [config]);
|
}, [config]);
|
||||||
|
|
||||||
@@ -104,6 +108,14 @@ export default function AdminPaymentMethodEdit() {
|
|||||||
data.reset_max_amount = true;
|
data.reset_max_amount = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (quickAmounts.length > 0) {
|
||||||
|
data.quick_amounts = [...quickAmounts]
|
||||||
|
.sort((a, b) => a - b)
|
||||||
|
.map((rubles) => Math.round(rubles * 100));
|
||||||
|
} else {
|
||||||
|
data.reset_quick_amounts = true;
|
||||||
|
}
|
||||||
|
|
||||||
updateMethodMutation.mutate(data);
|
updateMethodMutation.mutate(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -113,6 +125,29 @@ export default function AdminPaymentMethodEdit() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addQuickAmount = () => {
|
||||||
|
setQuickAmountsError(null);
|
||||||
|
const value = parseFloat(quickAmountInput.replace(',', '.'));
|
||||||
|
if (isNaN(value) || value <= 0) {
|
||||||
|
setQuickAmountsError(t('admin.paymentMethods.quickAmountsInvalid'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (quickAmounts.includes(value)) {
|
||||||
|
setQuickAmountInput('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (quickAmounts.length >= 10) {
|
||||||
|
setQuickAmountsError(t('admin.paymentMethods.quickAmountsLimit'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setQuickAmounts((prev) => [...prev, value].sort((a, b) => a - b));
|
||||||
|
setQuickAmountInput('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeQuickAmount = (value: number) => {
|
||||||
|
setQuickAmounts((prev) => prev.filter((amount) => amount !== value));
|
||||||
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-viewport flex items-center justify-center">
|
<div className="min-h-viewport flex items-center justify-center">
|
||||||
@@ -305,6 +340,52 @@ export default function AdminPaymentMethodEdit() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="mb-2 block text-sm font-medium text-dark-300">
|
||||||
|
{t('admin.paymentMethods.quickAmounts')}
|
||||||
|
</label>
|
||||||
|
{quickAmounts.length > 0 && (
|
||||||
|
<div className="mb-2 flex flex-wrap gap-2">
|
||||||
|
{quickAmounts.map((value) => (
|
||||||
|
<button
|
||||||
|
key={value}
|
||||||
|
type="button"
|
||||||
|
onClick={() => removeQuickAmount(value)}
|
||||||
|
className="flex items-center gap-1.5 rounded-xl border border-accent-500/30 bg-accent-500/10 px-3 py-1.5 text-sm font-medium text-accent-300 transition-colors hover:border-error-500/40 hover:bg-error-500/10 hover:text-error-400"
|
||||||
|
>
|
||||||
|
<span>{value} ₽</span>
|
||||||
|
<span className="text-base leading-none">×</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
value={quickAmountInput}
|
||||||
|
onChange={(e) => setQuickAmountInput(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
addQuickAmount();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder={t('admin.paymentMethods.quickAmountsPlaceholder')}
|
||||||
|
className="input flex-1"
|
||||||
|
/>
|
||||||
|
<button type="button" onClick={addQuickAmount} className="btn-secondary shrink-0">
|
||||||
|
{t('admin.paymentMethods.quickAmountsAdd')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{quickAmountsError && <p className="mt-1 text-xs text-error-400">{quickAmountsError}</p>}
|
||||||
|
<p className="mt-1 text-xs text-dark-500">
|
||||||
|
{t('admin.paymentMethods.quickAmountsHint', {
|
||||||
|
defaults: config.default_quick_amounts.map((kopeks) => kopeks / 100).join(', '),
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Display conditions */}
|
{/* Display conditions */}
|
||||||
<div className="border-t border-dark-700 pt-3">
|
<div className="border-t border-dark-700 pt-3">
|
||||||
<h3 className="mb-4 text-sm font-semibold text-dark-200">
|
<h3 className="mb-4 text-sm font-semibold text-dark-200">
|
||||||
|
|||||||
@@ -688,6 +688,8 @@ export interface PaymentMethodConfig {
|
|||||||
default_display_name: string;
|
default_display_name: string;
|
||||||
sub_options: Record<string, boolean> | null;
|
sub_options: Record<string, boolean> | null;
|
||||||
available_sub_options: PaymentMethodSubOptionInfo[] | null;
|
available_sub_options: PaymentMethodSubOptionInfo[] | null;
|
||||||
|
quick_amounts: number[] | null;
|
||||||
|
default_quick_amounts: number[];
|
||||||
min_amount_kopeks: number | null;
|
min_amount_kopeks: number | null;
|
||||||
max_amount_kopeks: number | null;
|
max_amount_kopeks: number | null;
|
||||||
default_min_amount_kopeks: number;
|
default_min_amount_kopeks: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user