gps_system/resources/views/livewire/subscription-management.blade.php
sackey 6b878bb0a0
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
Initial commit
2025-09-12 16:19:56 +00:00

349 lines
20 KiB
PHP

<div class="space-y-6" wire:key="subscription-management">
{{-- Header --}}
<div class="flex items-center justify-between">
<div>
<flux:heading size="lg">Subscription Management</flux:heading>
<flux:subheading>Manage billing, plans, and subscriptions</flux:subheading>
</div>
<flux:button wire:click="$set('showCreateSubscriptionModal', true)" variant="primary" size="sm" icon="plus">
Create Subscription
</flux:button>
</div>
{{-- Revenue Stats --}}
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<flux:card>
<div class="text-center">
<div class="text-2xl font-bold text-green-600">${{ number_format($this->stats['monthly_revenue'], 2) }}</div>
<div class="text-sm text-gray-600">Monthly Revenue</div>
<div class="text-xs text-gray-500">{{ $this->stats['revenue_change'] > 0 ? '+' : '' }}{{ $this->stats['revenue_change'] }}% vs last month</div>
</div>
</flux:card>
<flux:card>
<div class="text-center">
<div class="text-2xl font-bold text-blue-600">{{ $this->stats['active_subscriptions'] }}</div>
<div class="text-sm text-gray-600">Active Subscriptions</div>
<div class="text-xs text-gray-500">{{ $this->stats['new_subscriptions'] }} new this month</div>
</div>
</flux:card>
<flux:card>
<div class="text-center">
<div class="text-2xl font-bold text-yellow-600">{{ $this->stats['expiring_soon'] }}</div>
<div class="text-sm text-gray-600">Expiring Soon</div>
<div class="text-xs text-gray-500">Next 30 days</div>
</div>
</flux:card>
<flux:card>
<div class="text-center">
<div class="text-2xl font-bold text-purple-600">{{ number_format($this->stats['avg_subscription_value'], 2) }}</div>
<div class="text-sm text-gray-600">Avg Value</div>
<div class="text-xs text-gray-500">Per subscription</div>
</div>
</flux:card>
</div>
{{-- Filters --}}
<flux:card>
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
<flux:input wire:model.live="filters.search" placeholder="Search subscriptions..." icon="magnifying-glass" />
<flux:select wire:model.live="filters.status">
<option value="">All Status</option>
<option value="active">Active</option>
<option value="cancelled">Cancelled</option>
<option value="expired">Expired</option>
<option value="trial">Trial</option>
</flux:select>
<flux:select wire:model.live="filters.plan">
<option value="">All Plans</option>
<option value="basic">Basic</option>
<option value="pro">Professional</option>
<option value="enterprise">Enterprise</option>
<option value="custom">Custom</option>
</flux:select>
<flux:select wire:model.live="filters.billing_cycle">
<option value="">All Cycles</option>
<option value="monthly">Monthly</option>
<option value="yearly">Yearly</option>
<option value="lifetime">Lifetime</option>
</flux:select>
<flux:select wire:model.live="filters.payment_status">
<option value="">All Payments</option>
<option value="paid">Paid</option>
<option value="pending">Pending</option>
<option value="failed">Failed</option>
</flux:select>
</div>
</flux:card>
{{-- Subscriptions Table --}}
<flux:card>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Customer
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Plan
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Status
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Usage
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Billing
</th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($subscriptions as $subscription)
<tr>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10">
<div class="h-10 w-10 bg-blue-100 rounded-full flex items-center justify-center">
<span class="text-sm font-medium text-blue-700">
{{ strtoupper(substr($subscription->user->name, 0, 1)) }}
</span>
</div>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">{{ $subscription->user->name }}</div>
<div class="text-sm text-gray-500">{{ $subscription->user->email }}</div>
@if($subscription->user->company)
<div class="text-xs text-gray-400">{{ $subscription->user->company }}</div>
@endif
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">{{ ucfirst($subscription->plan) }}</div>
<div class="text-sm text-gray-500">${{ number_format($subscription->amount, 2) }} / {{ $subscription->billing_cycle }}</div>
<div class="text-xs text-gray-400">{{ $subscription->device_limit }} device{{ $subscription->device_limit === 1 ? '' : 's' }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full
@if($subscription->status === 'active') bg-green-100 text-green-800
@elseif($subscription->status === 'trial') bg-blue-100 text-blue-800
@elseif($subscription->status === 'cancelled') bg-red-100 text-red-800
@else bg-gray-100 text-gray-800 @endif">
{{ ucfirst($subscription->status) }}
</span>
@if($subscription->ends_at)
<div class="text-xs text-gray-500 mt-1">
{{ $subscription->ends_at->isPast() ? 'Expired' : 'Ends' }}: {{ $subscription->ends_at->format('M j, Y') }}
</div>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap">
@php
$deviceCount = $subscription->user->devices()->count();
$usagePercent = $subscription->device_limit > 0 ? ($deviceCount / $subscription->device_limit) * 100 : 0;
@endphp
<div class="text-sm text-gray-900">{{ $deviceCount }} / {{ $subscription->device_limit }} devices</div>
<div class="w-full bg-gray-200 rounded-full h-2 mt-1">
<div class="h-2 rounded-full {{ $usagePercent > 90 ? 'bg-red-500' : ($usagePercent > 70 ? 'bg-yellow-500' : 'bg-green-500') }}"
style="width: {{ min($usagePercent, 100) }}%"></div>
</div>
<div class="text-xs text-gray-500">{{ number_format($usagePercent, 1) }}% used</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">
@if($subscription->stripe_id)
<div class="flex items-center space-x-1">
<flux:icon.credit-card class="w-4 h-4 text-gray-400" />
<span>Stripe</span>
</div>
@else
<span class="text-gray-500">Manual</span>
@endif
</div>
@if($subscription->next_billing_date)
<div class="text-xs text-gray-500">Next: {{ $subscription->next_billing_date->format('M j, Y') }}</div>
@endif
@if($subscription->payment_status)
<div class="text-xs {{ $subscription->payment_status === 'paid' ? 'text-green-600' : ($subscription->payment_status === 'failed' ? 'text-red-600' : 'text-yellow-600') }}">
{{ ucfirst($subscription->payment_status) }}
</div>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
<flux:button wire:click="editSubscription({{ $subscription->id }})" variant="outline" size="xs">
Edit
</flux:button>
@if($subscription->status === 'active')
<flux:button wire:click="cancelSubscription({{ $subscription->id }})" variant="danger" size="xs">
Cancel
</flux:button>
@elseif($subscription->status === 'cancelled')
<flux:button wire:click="renewSubscription({{ $subscription->id }})" variant="primary" size="xs">
Renew
</flux:button>
@endif
<flux:button wire:click="viewBilling({{ $subscription->id }})" variant="ghost" size="xs">
Billing
</flux:button>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-6 py-4 text-center text-gray-500">
No subscriptions found matching your criteria.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- Pagination --}}
<div class="mt-4">
{{ $subscriptions->links() }}
</div>
</flux:card>
{{-- Create/Edit Subscription Modal --}}
<flux:modal name="subscription-form" x-show="$wire.showCreateSubscriptionModal || $wire.showEditSubscriptionModal" class="md:max-w-lg">
<form wire:submit="{{ $editingSubscription ? 'updateSubscription' : 'createSubscription' }}">
<div class="space-y-6">
<div>
<flux:heading size="lg">{{ $editingSubscription ? 'Edit Subscription' : 'Create Subscription' }}</flux:heading>
</div>
<div class="space-y-4">
@if(!$editingSubscription)
<flux:select wire:model="subscriptionForm.user_id" label="Customer" required>
<option value="">Select Customer</option>
@foreach($users as $user)
<option value="{{ $user->id }}">{{ $user->name }} ({{ $user->email }})</option>
@endforeach
</flux:select>
@endif
<div class="grid grid-cols-2 gap-4">
<flux:select wire:model.live="subscriptionForm.plan" label="Plan" required>
<option value="">Select Plan</option>
<option value="basic">Basic</option>
<option value="pro">Professional</option>
<option value="enterprise">Enterprise</option>
<option value="custom">Custom</option>
</flux:select>
<flux:select wire:model="subscriptionForm.billing_cycle" label="Billing Cycle" required>
<option value="">Select Cycle</option>
<option value="monthly">Monthly</option>
<option value="yearly">Yearly</option>
<option value="lifetime">Lifetime</option>
</flux:select>
</div>
<div class="grid grid-cols-2 gap-4">
<flux:input wire:model="subscriptionForm.amount" label="Amount ($)" type="number" step="0.01" required />
<flux:input wire:model="subscriptionForm.device_limit" label="Device Limit" type="number" required />
</div>
<flux:select wire:model="subscriptionForm.status" label="Status">
<option value="active">Active</option>
<option value="trial">Trial</option>
<option value="cancelled">Cancelled</option>
<option value="expired">Expired</option>
</flux:select>
<div class="grid grid-cols-2 gap-4">
<flux:input wire:model="subscriptionForm.starts_at" label="Start Date" type="date" required />
<flux:input wire:model="subscriptionForm.ends_at" label="End Date" type="date" />
</div>
@if($subscriptionForm['billing_cycle'] !== 'lifetime')
<flux:input wire:model="subscriptionForm.next_billing_date" label="Next Billing Date" type="date" />
@endif
<flux:input wire:model="subscriptionForm.stripe_id" label="Stripe Subscription ID" placeholder="Optional - for Stripe integration" />
<flux:textarea wire:model="subscriptionForm.notes" label="Notes" rows="3" />
</div>
<div class="flex justify-end space-x-2">
<flux:button type="button" variant="ghost" wire:click="closeSubscriptionModal">Cancel</flux:button>
<flux:button type="submit" variant="primary">
{{ $editingSubscription ? 'Update' : 'Create' }}
</flux:button>
</div>
</div>
</form>
</flux:modal>
{{-- Billing History Modal --}}
<flux:modal name="billing-history" x-show="$wire.showBillingModal" class="md:max-w-2xl">
@if($selectedSubscription)
<div class="space-y-6">
<div>
<flux:heading size="lg">Billing History</flux:heading>
<flux:subheading>{{ $selectedSubscription->user->name }} - {{ ucfirst($selectedSubscription->plan) }} Plan</flux:subheading>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Date</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Amount</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Invoice</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($billingHistory as $payment)
<tr>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
{{ $payment->created_at->format('M j, Y') }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
${{ number_format($payment->amount, 2) }}
</td>
<td class="px-4 py-4 whitespace-nowrap">
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full
{{ $payment->status === 'paid' ? 'bg-green-100 text-green-800' :
($payment->status === 'failed' ? 'bg-red-100 text-red-800' : 'bg-yellow-100 text-yellow-800') }}">
{{ ucfirst($payment->status) }}
</span>
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
@if($payment->invoice_url)
<flux:button href="{{ $payment->invoice_url }}" target="_blank" variant="outline" size="xs">
View
</flux:button>
@else
<span class="text-gray-400">N/A</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-4 py-4 text-center text-gray-500">
No billing history available.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="flex justify-end">
<flux:button wire:click="closeBillingModal" variant="outline">Close</flux:button>
</div>
</div>
@endif
</flux:modal>
</div>