|
66 | 66 | </template> |
67 | 67 |
|
68 | 68 | <template #cell-usage="{ row }"> |
69 | | - <div class="space-y-1 min-w-[200px]"> |
70 | | - <div v-if="row.group?.daily_limit_usd" class="flex items-center gap-2"> |
71 | | - <span class="text-xs text-gray-500 w-12">{{ t('admin.subscriptions.daily') }}</span> |
72 | | - <div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-2"> |
73 | | - <div |
74 | | - class="h-2 rounded-full transition-all" |
75 | | - :class="getProgressClass(row.daily_usage_usd, row.group?.daily_limit_usd)" |
76 | | - :style="{ width: getProgressWidth(row.daily_usage_usd, row.group?.daily_limit_usd) }" |
77 | | - ></div> |
| 69 | + <div class="space-y-2 min-w-[280px]"> |
| 70 | + <!-- Daily Usage --> |
| 71 | + <div v-if="row.group?.daily_limit_usd" class="usage-row"> |
| 72 | + <div class="flex items-center gap-2"> |
| 73 | + <span class="usage-label">{{ t('admin.subscriptions.daily') }}</span> |
| 74 | + <div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-1.5"> |
| 75 | + <div |
| 76 | + class="h-1.5 rounded-full transition-all" |
| 77 | + :class="getProgressClass(row.daily_usage_usd, row.group?.daily_limit_usd)" |
| 78 | + :style="{ width: getProgressWidth(row.daily_usage_usd, row.group?.daily_limit_usd) }" |
| 79 | + ></div> |
| 80 | + </div> |
| 81 | + <span class="usage-amount"> |
| 82 | + ${{ row.daily_usage_usd?.toFixed(2) || '0.00' }} |
| 83 | + <span class="text-gray-400">/</span> |
| 84 | + ${{ row.group?.daily_limit_usd?.toFixed(2) }} |
| 85 | + </span> |
| 86 | + </div> |
| 87 | + <div class="reset-info" v-if="row.daily_window_start"> |
| 88 | + <svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> |
| 89 | + <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /> |
| 90 | + </svg> |
| 91 | + <span>{{ formatResetTime(row.daily_window_start, 'daily') }}</span> |
78 | 92 | </div> |
79 | | - <span class="text-xs text-gray-500 w-20 text-right"> |
80 | | - ${{ row.daily_usage_usd?.toFixed(2) || '0.00' }} / ${{ row.group?.daily_limit_usd?.toFixed(2) }} |
81 | | - </span> |
82 | 93 | </div> |
83 | | - <div v-if="row.group?.weekly_limit_usd" class="flex items-center gap-2"> |
84 | | - <span class="text-xs text-gray-500 w-12">{{ t('admin.subscriptions.weekly') }}</span> |
85 | | - <div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-2"> |
86 | | - <div |
87 | | - class="h-2 rounded-full transition-all" |
88 | | - :class="getProgressClass(row.weekly_usage_usd, row.group?.weekly_limit_usd)" |
89 | | - :style="{ width: getProgressWidth(row.weekly_usage_usd, row.group?.weekly_limit_usd) }" |
90 | | - ></div> |
| 94 | + |
| 95 | + <!-- Weekly Usage --> |
| 96 | + <div v-if="row.group?.weekly_limit_usd" class="usage-row"> |
| 97 | + <div class="flex items-center gap-2"> |
| 98 | + <span class="usage-label">{{ t('admin.subscriptions.weekly') }}</span> |
| 99 | + <div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-1.5"> |
| 100 | + <div |
| 101 | + class="h-1.5 rounded-full transition-all" |
| 102 | + :class="getProgressClass(row.weekly_usage_usd, row.group?.weekly_limit_usd)" |
| 103 | + :style="{ width: getProgressWidth(row.weekly_usage_usd, row.group?.weekly_limit_usd) }" |
| 104 | + ></div> |
| 105 | + </div> |
| 106 | + <span class="usage-amount"> |
| 107 | + ${{ row.weekly_usage_usd?.toFixed(2) || '0.00' }} |
| 108 | + <span class="text-gray-400">/</span> |
| 109 | + ${{ row.group?.weekly_limit_usd?.toFixed(2) }} |
| 110 | + </span> |
| 111 | + </div> |
| 112 | + <div class="reset-info" v-if="row.weekly_window_start"> |
| 113 | + <svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> |
| 114 | + <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /> |
| 115 | + </svg> |
| 116 | + <span>{{ formatResetTime(row.weekly_window_start, 'weekly') }}</span> |
91 | 117 | </div> |
92 | | - <span class="text-xs text-gray-500 w-20 text-right"> |
93 | | - ${{ row.weekly_usage_usd?.toFixed(2) || '0.00' }} / ${{ row.group?.weekly_limit_usd?.toFixed(2) }} |
94 | | - </span> |
95 | 118 | </div> |
96 | | - <div v-if="row.group?.monthly_limit_usd" class="flex items-center gap-2"> |
97 | | - <span class="text-xs text-gray-500 w-12">{{ t('admin.subscriptions.monthly') }}</span> |
98 | | - <div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-2"> |
99 | | - <div |
100 | | - class="h-2 rounded-full transition-all" |
101 | | - :class="getProgressClass(row.monthly_usage_usd, row.group?.monthly_limit_usd)" |
102 | | - :style="{ width: getProgressWidth(row.monthly_usage_usd, row.group?.monthly_limit_usd) }" |
103 | | - ></div> |
| 119 | + |
| 120 | + <!-- Monthly Usage --> |
| 121 | + <div v-if="row.group?.monthly_limit_usd" class="usage-row"> |
| 122 | + <div class="flex items-center gap-2"> |
| 123 | + <span class="usage-label">{{ t('admin.subscriptions.monthly') }}</span> |
| 124 | + <div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-1.5"> |
| 125 | + <div |
| 126 | + class="h-1.5 rounded-full transition-all" |
| 127 | + :class="getProgressClass(row.monthly_usage_usd, row.group?.monthly_limit_usd)" |
| 128 | + :style="{ width: getProgressWidth(row.monthly_usage_usd, row.group?.monthly_limit_usd) }" |
| 129 | + ></div> |
| 130 | + </div> |
| 131 | + <span class="usage-amount"> |
| 132 | + ${{ row.monthly_usage_usd?.toFixed(2) || '0.00' }} |
| 133 | + <span class="text-gray-400">/</span> |
| 134 | + ${{ row.group?.monthly_limit_usd?.toFixed(2) }} |
| 135 | + </span> |
| 136 | + </div> |
| 137 | + <div class="reset-info" v-if="row.monthly_window_start"> |
| 138 | + <svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> |
| 139 | + <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /> |
| 140 | + </svg> |
| 141 | + <span>{{ formatResetTime(row.monthly_window_start, 'monthly') }}</span> |
104 | 142 | </div> |
105 | | - <span class="text-xs text-gray-500 w-20 text-right"> |
106 | | - ${{ row.monthly_usage_usd?.toFixed(2) || '0.00' }} / ${{ row.group?.monthly_limit_usd?.toFixed(2) }} |
107 | | - </span> |
108 | 143 | </div> |
| 144 | + |
| 145 | + <!-- No Limits --> |
109 | 146 | <div v-if="!row.group?.daily_limit_usd && !row.group?.weekly_limit_usd && !row.group?.monthly_limit_usd" class="text-xs text-gray-500"> |
110 | 147 | {{ t('admin.subscriptions.noLimits') }} |
111 | 148 | </div> |
@@ -553,9 +590,63 @@ const getProgressClass = (used: number, limit: number | null): string => { |
553 | 590 | return 'bg-green-500' |
554 | 591 | } |
555 | 592 |
|
| 593 | +// Format reset time based on window start and period type |
| 594 | +const formatResetTime = (windowStart: string, period: 'daily' | 'weekly' | 'monthly'): string => { |
| 595 | + const start = new Date(windowStart) |
| 596 | + const now = new Date() |
| 597 | +
|
| 598 | + // Calculate reset time based on period |
| 599 | + let resetTime: Date |
| 600 | + switch (period) { |
| 601 | + case 'daily': |
| 602 | + resetTime = new Date(start.getTime() + 24 * 60 * 60 * 1000) |
| 603 | + break |
| 604 | + case 'weekly': |
| 605 | + resetTime = new Date(start.getTime() + 7 * 24 * 60 * 60 * 1000) |
| 606 | + break |
| 607 | + case 'monthly': |
| 608 | + resetTime = new Date(start.getTime() + 30 * 24 * 60 * 60 * 1000) |
| 609 | + break |
| 610 | + } |
| 611 | +
|
| 612 | + const diffMs = resetTime.getTime() - now.getTime() |
| 613 | + if (diffMs <= 0) return t('admin.subscriptions.resetNow') |
| 614 | +
|
| 615 | + const diffSeconds = Math.floor(diffMs / 1000) |
| 616 | + const days = Math.floor(diffSeconds / 86400) |
| 617 | + const hours = Math.floor((diffSeconds % 86400) / 3600) |
| 618 | + const minutes = Math.floor((diffSeconds % 3600) / 60) |
| 619 | +
|
| 620 | + if (days > 0) { |
| 621 | + return t('admin.subscriptions.resetInDaysHours', { days, hours }) |
| 622 | + } else if (hours > 0) { |
| 623 | + return t('admin.subscriptions.resetInHoursMinutes', { hours, minutes }) |
| 624 | + } else { |
| 625 | + return t('admin.subscriptions.resetInMinutes', { minutes }) |
| 626 | + } |
| 627 | +} |
| 628 | +
|
556 | 629 | onMounted(() => { |
557 | 630 | loadSubscriptions() |
558 | 631 | loadGroups() |
559 | 632 | loadUsers() |
560 | 633 | }) |
561 | 634 | </script> |
| 635 | + |
| 636 | +<style scoped> |
| 637 | +.usage-row { |
| 638 | + @apply space-y-1; |
| 639 | +} |
| 640 | +
|
| 641 | +.usage-label { |
| 642 | + @apply text-xs font-medium text-gray-500 dark:text-gray-400 w-10 flex-shrink-0; |
| 643 | +} |
| 644 | +
|
| 645 | +.usage-amount { |
| 646 | + @apply text-xs text-gray-600 dark:text-gray-300 tabular-nums whitespace-nowrap; |
| 647 | +} |
| 648 | +
|
| 649 | +.reset-info { |
| 650 | + @apply flex items-center gap-1 text-[10px] text-blue-600 dark:text-blue-400 pl-12; |
| 651 | +} |
| 652 | +</style> |
0 commit comments