diff --git a/src/components/admin/bulkActions/SubscriptionSubRow.tsx b/src/components/admin/bulkActions/SubscriptionSubRow.tsx new file mode 100644 index 0000000..c03cae3 --- /dev/null +++ b/src/components/admin/bulkActions/SubscriptionSubRow.tsx @@ -0,0 +1,192 @@ +import { useTranslation } from 'react-i18next'; +import { cn } from '@/lib/utils'; +import type { UserListItemSubscription } from '../../../api/adminUsers'; + +// ────────────────────────────────────────────────────────────────── +// SubscriptionSubRow + StatusBadge +// +// SubscriptionSubRow: the expanded per-user secondary rendered +// under a user row when the operator drills into multi-subscription +// users. Shows tariff, status, days remaining, traffic bar, devices. +// +// StatusBadge: the pill used both inside the sub-row and in the +// primary user-table column (subscription_status). Co-located here +// because both consumers want the same visual contract. +// ────────────────────────────────────────────────────────────────── + +export interface SubscriptionSubRowProps { + subscription: UserListItemSubscription; + isSelected: boolean; + onToggleSelect: () => void; + isMultiTariff: boolean; +} + +export function SubscriptionSubRow({ + subscription, + isSelected, + onToggleSelect, + isMultiTariff, +}: SubscriptionSubRowProps) { + const { t } = useTranslation(); + + const days = subscription.days_remaining; + const daysColor = + days === 0 ? 'text-error-400' : days <= 7 ? 'text-warning-400' : 'text-success-400'; + + const trafficPercent = + subscription.traffic_limit_gb > 0 + ? Math.min(100, (subscription.traffic_used_gb / subscription.traffic_limit_gb) * 100) + : 0; + const trafficBarColor = + trafficPercent >= 90 + ? 'bg-error-400' + : trafficPercent >= 70 + ? 'bg-warning-400' + : 'bg-accent-400'; + + return ( + + + {isMultiTariff && ( +
+ +
+ )} + + + +
+
+ + + {subscription.tariff_name || '—'} + +
+ + + + + {days} + + {t('admin.bulkActions.daysUnit')} + + + +
+ + {subscription.traffic_used_gb.toFixed(1)} {t('admin.bulkActions.trafficOf')}{' '} + {subscription.traffic_limit_gb} {t('admin.bulkActions.trafficGbUnit')} + +
+
+
+
+ + + + + + {subscription.device_limit} + +
+ + + ); +} + +export function StatusBadge({ status }: { status: string | null }) { + const { t } = useTranslation(); + + const config: Record = { + active: { + class: 'border-success-500/30 bg-success-500/15 text-success-400', + labelKey: 'admin.bulkActions.statuses.active', + }, + expired: { + class: 'border-error-500/30 bg-error-500/15 text-error-400', + labelKey: 'admin.bulkActions.statuses.expired', + }, + trial: { + class: 'border-warning-500/30 bg-warning-500/15 text-warning-400', + labelKey: 'admin.bulkActions.statuses.trial', + }, + limited: { + class: 'border-warning-500/30 bg-warning-500/15 text-warning-400', + labelKey: 'admin.bulkActions.statuses.limited', + }, + disabled: { + class: 'border-dark-500/30 bg-dark-500/15 text-dark-400', + labelKey: 'admin.bulkActions.statuses.disabled', + }, + }; + + const c = config[status || ''] || config.disabled; + + return ( + + {t(c.labelKey, status || '')} + + ); +} diff --git a/src/pages/AdminBulkActions.tsx b/src/pages/AdminBulkActions.tsx index 8e40275..66aba4c 100644 --- a/src/pages/AdminBulkActions.tsx +++ b/src/pages/AdminBulkActions.tsx @@ -31,6 +31,7 @@ import { MultiSelectDropdown, type MultiSelectOption, } from '@/components/admin/bulkActions/MultiSelectDropdown'; +import { SubscriptionSubRow, StatusBadge } from '@/components/admin/bulkActions/SubscriptionSubRow'; // ============ Types ============ @@ -115,193 +116,7 @@ const ChevronExpandIcon = ({ expanded }: { expanded: boolean }) => ( // (SUBSCRIPTION_LEVEL_ACTIONS + isSubscriptionLevelAction moved into ./bulkActions/actionTargets.ts) -// ============ Subscription sub-row ============ - -interface SubscriptionSubRowProps { - subscription: UserListItemSubscription; - isSelected: boolean; - onToggleSelect: () => void; - isMultiTariff: boolean; -} - -function SubscriptionSubRow({ - subscription, - isSelected, - onToggleSelect, - isMultiTariff, -}: SubscriptionSubRowProps) { - const { t } = useTranslation(); - - const days = subscription.days_remaining; - const daysColor = - days === 0 ? 'text-error-400' : days <= 7 ? 'text-warning-400' : 'text-success-400'; - - const trafficPercent = - subscription.traffic_limit_gb > 0 - ? Math.min(100, (subscription.traffic_used_gb / subscription.traffic_limit_gb) * 100) - : 0; - const trafficBarColor = - trafficPercent >= 90 - ? 'bg-error-400' - : trafficPercent >= 70 - ? 'bg-warning-400' - : 'bg-accent-400'; - - return ( - - {/* Checkbox cell */} - - {isMultiTariff && ( -
- -
- )} - - - {/* Subscription info — spans remaining columns */} - -
- {/* Tariff name */} -
- - - {subscription.tariff_name || '—'} - -
- - {/* Status */} - - - {/* Days remaining */} - - {days} - - {t('admin.bulkActions.daysUnit')} - - - - {/* Traffic progress */} -
- - {subscription.traffic_used_gb.toFixed(1)} {t('admin.bulkActions.trafficOf')}{' '} - {subscription.traffic_limit_gb} {t('admin.bulkActions.trafficGbUnit')} - -
-
-
-
- - {/* Devices */} - - - - - {subscription.device_limit} - -
- - - ); -} - -// ============ Status badge ============ - -function StatusBadge({ status }: { status: string | null }) { - const { t } = useTranslation(); - - const config: Record = { - active: { - class: 'border-success-500/30 bg-success-500/15 text-success-400', - labelKey: 'admin.bulkActions.statuses.active', - }, - expired: { - class: 'border-error-500/30 bg-error-500/15 text-error-400', - labelKey: 'admin.bulkActions.statuses.expired', - }, - trial: { - class: 'border-warning-500/30 bg-warning-500/15 text-warning-400', - labelKey: 'admin.bulkActions.statuses.trial', - }, - limited: { - class: 'border-warning-500/30 bg-warning-500/15 text-warning-400', - labelKey: 'admin.bulkActions.statuses.limited', - }, - disabled: { - class: 'border-dark-500/30 bg-dark-500/15 text-dark-400', - labelKey: 'admin.bulkActions.statuses.disabled', - }, - }; - - const c = config[status || ''] || config.disabled; - - return ( - - {t(c.labelKey, status || '')} - - ); -} +// (SubscriptionSubRow + StatusBadge moved into ./bulkActions/SubscriptionSubRow.tsx) // ============ Progress Bar ============