mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
feat(admin): статус и отмена СБП-автопродления в карточке юзера
This commit is contained in:
@@ -130,6 +130,7 @@ export interface SubscriptionTabProps {
|
|||||||
onAddTraffic: (gb: number) => Promise<void>;
|
onAddTraffic: (gb: number) => Promise<void>;
|
||||||
onRemoveTraffic: (purchaseId: number) => Promise<void>;
|
onRemoveTraffic: (purchaseId: number) => Promise<void>;
|
||||||
onResetDevices: () => Promise<void>;
|
onResetDevices: () => Promise<void>;
|
||||||
|
onCancelSbpRecurring: () => Promise<void>;
|
||||||
onDeleteDevice: (hwid: string) => Promise<void>;
|
onDeleteDevice: (hwid: string) => Promise<void>;
|
||||||
onRenameDevice: (hwid: string) => Promise<void>;
|
onRenameDevice: (hwid: string) => Promise<void>;
|
||||||
onLoadDevices: () => Promise<void>;
|
onLoadDevices: () => Promise<void>;
|
||||||
@@ -193,6 +194,7 @@ export function SubscriptionTab(props: SubscriptionTabProps) {
|
|||||||
onAddTraffic,
|
onAddTraffic,
|
||||||
onRemoveTraffic,
|
onRemoveTraffic,
|
||||||
onResetDevices,
|
onResetDevices,
|
||||||
|
onCancelSbpRecurring,
|
||||||
onDeleteDevice,
|
onDeleteDevice,
|
||||||
onRenameDevice,
|
onRenameDevice,
|
||||||
onLoadDevices,
|
onLoadDevices,
|
||||||
@@ -226,6 +228,14 @@ export function SubscriptionTab(props: SubscriptionTabProps) {
|
|||||||
{sub.tariff_name || `#${sub.id}`}
|
{sub.tariff_name || `#${sub.id}`}
|
||||||
</span>
|
</span>
|
||||||
<StatusBadge status={sub.status} />
|
<StatusBadge status={sub.status} />
|
||||||
|
{sub.sbp_recurring_status && (
|
||||||
|
<span
|
||||||
|
title={t('admin.users.detail.subscription.sbpTitle')}
|
||||||
|
className="rounded-full bg-accent-500/15 px-2 py-0.5 text-[10px] font-medium text-accent-400"
|
||||||
|
>
|
||||||
|
SBP
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ChevronRightIcon className="h-4 w-4 text-dark-500" />
|
<ChevronRightIcon className="h-4 w-4 text-dark-500" />
|
||||||
</div>
|
</div>
|
||||||
@@ -380,6 +390,41 @@ export function SubscriptionTab(props: SubscriptionTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* SBP (Platega) recurring auto-payment — status comes from the admin
|
||||||
|
detail response; cancel is idempotent on the backend. */}
|
||||||
|
{selectedSub.sbp_recurring_status && (
|
||||||
|
<div className="rounded-xl bg-dark-800/50 p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-medium text-dark-200">
|
||||||
|
{t('admin.users.detail.subscription.sbpTitle')}
|
||||||
|
</div>
|
||||||
|
<div className="mt-0.5 text-xs text-dark-400">
|
||||||
|
{t(
|
||||||
|
`admin.users.detail.subscription.sbpStatus_${selectedSub.sbp_recurring_status}`,
|
||||||
|
selectedSub.sbp_recurring_status,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{hasPermission('users:subscription') && (
|
||||||
|
<button
|
||||||
|
onClick={() => onInlineConfirm('cancelSbpRecurring', onCancelSbpRecurring)}
|
||||||
|
disabled={actionLoading}
|
||||||
|
className={`rounded-lg px-3 py-2 text-sm font-medium transition-all disabled:opacity-50 ${
|
||||||
|
confirmingAction === 'cancelSbpRecurring'
|
||||||
|
? 'bg-warning-500 text-white'
|
||||||
|
: 'bg-warning-500/15 text-warning-400 hover:bg-warning-500/25'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{confirmingAction === 'cancelSbpRecurring'
|
||||||
|
? t('admin.users.detail.actions.areYouSure')
|
||||||
|
: t('admin.users.detail.subscription.sbpCancel')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Traffic Packages */}
|
{/* Traffic Packages */}
|
||||||
{selectedSub.traffic_purchases && selectedSub.traffic_purchases.length > 0 && (
|
{selectedSub.traffic_purchases && selectedSub.traffic_purchases.length > 0 && (
|
||||||
<div className="rounded-xl bg-dark-800/50 p-4">
|
<div className="rounded-xl bg-dark-800/50 p-4">
|
||||||
|
|||||||
@@ -648,6 +648,20 @@ export default function AdminUserDetail() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCancelSbpRecurring = async () => {
|
||||||
|
if (!userId || !selectedSub?.sbp_recurring_id) return;
|
||||||
|
setActionLoading(true);
|
||||||
|
try {
|
||||||
|
await adminUsersApi.cancelSbpRecurring(userId, selectedSub.id);
|
||||||
|
notify.success(t('admin.users.detail.subscription.sbpCancelled'), t('common.success'));
|
||||||
|
await loadUser();
|
||||||
|
} catch {
|
||||||
|
notify.error(t('admin.users.userActions.error'), t('common.error'));
|
||||||
|
} finally {
|
||||||
|
setActionLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleDisableUser = async () => {
|
const handleDisableUser = async () => {
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
setActionLoading(true);
|
setActionLoading(true);
|
||||||
@@ -713,7 +727,7 @@ export default function AdminUserDetail() {
|
|||||||
const k = 1024;
|
const k = 1024;
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
|
||||||
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
|
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
|
||||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyToClipboard = async (text: string) => {
|
const copyToClipboard = async (text: string) => {
|
||||||
@@ -859,6 +873,7 @@ export default function AdminUserDetail() {
|
|||||||
<SubscriptionTab
|
<SubscriptionTab
|
||||||
userSubscriptions={userSubscriptions}
|
userSubscriptions={userSubscriptions}
|
||||||
selectedSub={selectedSub}
|
selectedSub={selectedSub}
|
||||||
|
onCancelSbpRecurring={handleCancelSbpRecurring}
|
||||||
activeSubscriptionId={activeSubscriptionId}
|
activeSubscriptionId={activeSubscriptionId}
|
||||||
onActiveSubscriptionChange={setActiveSubscriptionId}
|
onActiveSubscriptionChange={setActiveSubscriptionId}
|
||||||
subscriptionDetailView={subscriptionDetailView}
|
subscriptionDetailView={subscriptionDetailView}
|
||||||
|
|||||||
Reference in New Issue
Block a user