Files
usher-manage-stack/resources/views/admin/issue-reports/index.blade.php
2025-11-20 23:21:05 +08:00

281 lines
20 KiB
PHP

<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Issue Reports & Analytics') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
{{-- Date Range Filter --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<form method="GET" action="{{ route('admin.issue-reports.index') }}" class="flex flex-wrap gap-4 items-end">
<div class="flex-1 min-w-[200px]">
<label for="start_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Start Date') }}
</label>
<input type="date" name="start_date" id="start_date" value="{{ $startDate->format('Y-m-d') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
<div class="flex-1 min-w-[200px]">
<label for="end_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('End Date') }}
</label>
<input type="date" name="end_date" id="end_date" value="{{ $endDate->format('Y-m-d') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
<button type="submit" class="inline-flex justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400">
{{ __('Apply Filter') }}
</button>
</form>
</div>
{{-- Summary Statistics --}}
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 border-l-4 border-blue-400">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Total Issues') }}</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($stats['total_issues']) }}</dd>
</div>
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 border-l-4 border-green-400">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Open Issues') }}</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($stats['open_issues']) }}</dd>
</div>
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 border-l-4 border-gray-400">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Closed Issues') }}</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($stats['closed_issues']) }}</dd>
</div>
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 border-l-4 border-red-400">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Overdue Issues') }}</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($stats['overdue_issues']) }}</dd>
</div>
</div>
{{-- Charts Section --}}
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
{{-- Issues by Status --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Issues by Status') }}</h3>
<div class="space-y-2">
@foreach(['new', 'assigned', 'in_progress', 'review', 'closed'] as $status)
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">{{ ucfirst(str_replace('_', ' ', $status)) }}</span>
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $issuesByStatus[$status] ?? 0 }}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
@php
$percentage = $stats['total_issues'] > 0 ? (($issuesByStatus[$status] ?? 0) / $stats['total_issues']) * 100 : 0;
@endphp
<div class="bg-indigo-600 h-2 rounded-full" style="width: {{ $percentage }}%"></div>
</div>
@endforeach
</div>
</div>
{{-- Issues by Priority --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Issues by Priority') }}</h3>
<div class="space-y-2">
@foreach(['low', 'medium', 'high', 'urgent'] as $priority)
@php
$colors = ['low' => 'green', 'medium' => 'yellow', 'high' => 'orange', 'urgent' => 'red'];
@endphp
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">{{ ucfirst($priority) }}</span>
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $issuesByPriority[$priority] ?? 0 }}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
@php
$percentage = $stats['total_issues'] > 0 ? (($issuesByPriority[$priority] ?? 0) / $stats['total_issues']) * 100 : 0;
@endphp
<div class="bg-{{ $colors[$priority] }}-600 h-2 rounded-full" style="width: {{ $percentage }}%"></div>
</div>
@endforeach
</div>
</div>
{{-- Issues by Type --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Issues by Type') }}</h3>
<div class="space-y-2">
@foreach(['work_item', 'project_task', 'maintenance', 'member_request'] as $type)
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">{{ ucfirst(str_replace('_', ' ', $type)) }}</span>
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $issuesByType[$type] ?? 0 }}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
@php
$percentage = $stats['total_issues'] > 0 ? (($issuesByType[$type] ?? 0) / $stats['total_issues']) * 100 : 0;
@endphp
<div class="bg-blue-600 h-2 rounded-full" style="width: {{ $percentage }}%"></div>
</div>
@endforeach
</div>
</div>
</div>
{{-- Time Tracking Metrics --}}
@if($timeTrackingMetrics && $timeTrackingMetrics->total_estimated > 0)
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Time Tracking Metrics') }}</h3>
<div class="grid grid-cols-1 sm:grid-cols-4 gap-4">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Total Estimated Hours') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($timeTrackingMetrics->total_estimated, 1) }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Total Actual Hours') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($timeTrackingMetrics->total_actual, 1) }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Avg Estimated Hours') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($timeTrackingMetrics->avg_estimated, 1) }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Avg Actual Hours') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($timeTrackingMetrics->avg_actual, 1) }}</dd>
</div>
</div>
@php
$variance = $timeTrackingMetrics->total_actual - $timeTrackingMetrics->total_estimated;
$variancePercentage = $timeTrackingMetrics->total_estimated > 0 ? ($variance / $timeTrackingMetrics->total_estimated) * 100 : 0;
@endphp
<div class="mt-4">
<p class="text-sm text-gray-600 dark:text-gray-400">
{{ __('Variance') }}:
<span class="font-semibold {{ $variance > 0 ? 'text-red-600 dark:text-red-400' : 'text-green-600 dark:text-green-400' }}">
{{ $variance > 0 ? '+' : '' }}{{ number_format($variance, 1) }} hours ({{ number_format($variancePercentage, 1) }}%)
</span>
</p>
</div>
</div>
@endif
{{-- Average Resolution Time --}}
@if($avgResolutionTime)
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">{{ __('Average Resolution Time') }}</h3>
<p class="text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($avgResolutionTime, 1) }} {{ __('days') }}</p>
</div>
@endif
{{-- Assignee Performance --}}
@if($assigneePerformance->isNotEmpty())
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Assignee Performance (Top 10)') }}</h3>
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg dark:ring-gray-700">
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-600">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Assignee') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Total Assigned') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Completed') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Overdue') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Completion Rate') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
@foreach($assigneePerformance as $user)
<tr>
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $user->name }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $user->total_assigned }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $user->completed }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
<span class="{{ $user->overdue > 0 ? 'text-red-600 dark:text-red-400' : '' }}">
{{ $user->overdue }}
</span>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
<div class="flex items-center gap-2">
<div class="flex-1 bg-gray-200 rounded-full h-2 dark:bg-gray-700 min-w-[60px]">
<div class="bg-green-600 h-2 rounded-full" style="width: {{ $user->completion_rate }}%"></div>
</div>
<span class="text-sm font-medium">{{ $user->completion_rate }}%</span>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif
{{-- Top Labels Used --}}
@if($topLabels->isNotEmpty())
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Top Labels Used') }}</h3>
<div class="space-y-3">
@foreach($topLabels as $label)
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<span class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium"
style="background-color: {{ $label->color }}; color: {{ \App\Models\IssueLabel::find($label->id)->text_color ?? '#000000' }}">
{{ $label->name }}
</span>
</div>
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $label->usage_count }} {{ __('uses') }}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
@php
$maxUsage = $topLabels->max('usage_count');
$percentage = $maxUsage > 0 ? ($label->usage_count / $maxUsage) * 100 : 0;
@endphp
<div class="h-2 rounded-full" style="width: {{ $percentage }}%; background-color: {{ $label->color }}"></div>
</div>
@endforeach
</div>
</div>
@endif
{{-- Recent Issues --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Recent Issues') }}</h3>
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg dark:ring-gray-700">
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-600">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Issue') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Status') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Priority') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Assignee') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Created') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
@foreach($recentIssues as $issue)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="py-4 pl-4 pr-3 text-sm">
<a href="{{ route('admin.issues.show', $issue) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
{{ $issue->issue_number }}: {{ Str::limit($issue->title, 50) }}
</a>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
<x-issue.status-badge :status="$issue->status" />
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
<x-issue.priority-badge :priority="$issue->priority" />
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $issue->assignee?->name ?? '—' }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
{{ $issue->created_at->format('Y-m-d') }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</x-app-layout>