Features: - Implement two fee types: entrance fee and annual fee (both NT$1,000) - Add 50% discount for disability certificate holders - Add disability certificate upload in member profile - Integrate disability verification into cashier approval workflow - Add membership fee settings in system admin Document permissions: - Fix hard-coded role logic in Document model - Use permission-based authorization instead of role checks Additional features: - Add announcements, general ledger, and trial balance modules - Add income management and accounting entries - Add comprehensive test suite with factories - Update UI translations to Traditional Chinese 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
280 lines
19 KiB
PHP
280 lines
19 KiB
PHP
<x-app-layout>
|
||
<x-slot name="header">
|
||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||
任務報告與分析
|
||
</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 dark:bg-gray-800 shadow sm:rounded-lg 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">
|
||
開始日期
|
||
</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">
|
||
結束日期
|
||
</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">
|
||
套用篩選
|
||
</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">總任務數</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">開啟的任務</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">已結案任務</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">逾期任務</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">
|
||
{{-- Tasks by Status --}}
|
||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">依狀態的任務</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 dark:bg-gray-700 rounded-full h-2">
|
||
@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>
|
||
|
||
{{-- Tasks by Priority --}}
|
||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">依優先級的任務</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 dark:bg-gray-700 rounded-full h-2">
|
||
@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>
|
||
|
||
{{-- Tasks by Type --}}
|
||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">依類型的任務</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 dark:bg-gray-700 rounded-full h-2">
|
||
@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 dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">時間追蹤指標</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">總預估時數</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">總實際時數</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">平均預估時數</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">平均實際時數</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">
|
||
差異:
|
||
<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 dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">平均解決時間</h3>
|
||
<p class="text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($avgResolutionTime, 1) }} 天</p>
|
||
</div>
|
||
@endif
|
||
|
||
{{-- Assignee Performance --}}
|
||
@if($assigneePerformance->isNotEmpty())
|
||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">指派對象表現(前10名)</h3>
|
||
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg">
|
||
<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">指派對象</th>
|
||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">總指派數</th>
|
||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">已完成</th>
|
||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">逾期</th>
|
||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">完成率</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white 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 dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">最常用標籤</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 }} 使用</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 Tasks --}}
|
||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">最近的任務</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">任務</th>
|
||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">狀態</th>
|
||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">優先級</th>
|
||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">指派對象</th>
|
||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">已建立</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> |