Files
usher-manage-stack/app/Http/Controllers/IssueReportsController.php
Gbanyan bcff65cf67 Fix audit logs and issue reports pages, rename Issues to Tasks
修復稽核日誌與任務報表頁面,並將「問題」改名為「任務」

## Changes 變更內容

### Bug Fixes 錯誤修復
1. Fixed audit logs page 500 error
   - Added missing $auditableTypes variable to controller
   - Changed $events to $actions in view
   - Added description and ip_address columns to audit_logs table
   - Updated AuditLog model fillable array

2. Fixed issue reports page SQLite compatibility errors
   - Replaced MySQL NOW() function with Laravel now() helper
   - Replaced TIMESTAMPDIFF() with PHP-based date calculation
   - Fixed request->date() default value handling

### Feature Changes 功能變更
3. Renamed "Issues" terminology to "Tasks" throughout the system
   - Updated navigation menus (Admin: Issues → Admin: Tasks)
   - Updated all issue-related views to use task terminology
   - Changed Chinese labels from "問題" to "任務"
   - Updated dashboard, issue tracker, and reports pages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 10:47:04 +08:00

138 lines
5.0 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Issue;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class IssueReportsController extends Controller
{
public function index(Request $request)
{
// Date range filter (default: last 30 days)
$startDate = $request->date('start_date') ?? now()->subDays(30);
$endDate = $request->date('end_date') ?? now();
// Overview Statistics
$stats = [
'total_issues' => Issue::count(),
'open_issues' => Issue::open()->count(),
'closed_issues' => Issue::closed()->count(),
'overdue_issues' => Issue::overdue()->count(),
];
// Issues by Status
$issuesByStatus = Issue::select('status', DB::raw('count(*) as count'))
->groupBy('status')
->get()
->mapWithKeys(fn($item) => [$item->status => $item->count]);
// Issues by Priority
$issuesByPriority = Issue::select('priority', DB::raw('count(*) as count'))
->groupBy('priority')
->get()
->mapWithKeys(fn($item) => [$item->priority => $item->count]);
// Issues by Type
$issuesByType = Issue::select('issue_type', DB::raw('count(*) as count'))
->groupBy('issue_type')
->get()
->mapWithKeys(fn($item) => [$item->issue_type => $item->count]);
// Issues Created Over Time (last 30 days)
$issuesCreatedOverTime = Issue::select(
DB::raw('DATE(created_at) as date'),
DB::raw('count(*) as count')
)
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date')
->orderBy('date')
->get();
// Issues Closed Over Time (last 30 days)
$issuesClosedOverTime = Issue::select(
DB::raw('DATE(closed_at) as date'),
DB::raw('count(*) as count')
)
->whereNotNull('closed_at')
->whereBetween('closed_at', [$startDate, $endDate])
->groupBy('date')
->orderBy('date')
->get();
// Assignee Performance
$now = now();
$assigneePerformance = User::select('users.id', 'users.name')
->leftJoin('issues', 'users.id', '=', 'issues.assigned_to_user_id')
->selectRaw('count(issues.id) as total_assigned')
->selectRaw('sum(case when issues.status = ? then 1 else 0 end) as completed', [Issue::STATUS_CLOSED])
->selectRaw('sum(case when issues.due_date < ? and issues.status != ? then 1 else 0 end) as overdue', [$now, Issue::STATUS_CLOSED])
->groupBy('users.id', 'users.name')
->having('total_assigned', '>', 0)
->orderByDesc('total_assigned')
->limit(10)
->get()
->map(function ($user) {
$user->completion_rate = $user->total_assigned > 0
? round(($user->completed / $user->total_assigned) * 100, 1)
: 0;
return $user;
});
// Time Tracking Metrics
$timeTrackingMetrics = Issue::selectRaw('
sum(estimated_hours) as total_estimated,
sum(actual_hours) as total_actual,
avg(estimated_hours) as avg_estimated,
avg(actual_hours) as avg_actual
')
->whereNotNull('estimated_hours')
->first();
// Top Labels Used
$topLabels = DB::table('issue_labels')
->select('issue_labels.id', 'issue_labels.name', 'issue_labels.color', DB::raw('count(issue_label_pivot.issue_id) as usage_count'))
->leftJoin('issue_label_pivot', 'issue_labels.id', '=', 'issue_label_pivot.issue_label_id')
->groupBy('issue_labels.id', 'issue_labels.name', 'issue_labels.color')
->having('usage_count', '>', 0)
->orderByDesc('usage_count')
->limit(10)
->get();
// Average Resolution Time (days)
$closedIssues = Issue::whereNotNull('closed_at')
->select('created_at', 'closed_at')
->get();
$avgResolutionTime = $closedIssues->isNotEmpty()
? $closedIssues->avg(function ($issue) {
return $issue->created_at->diffInDays($issue->closed_at);
})
: null;
// Recent Activity (last 10 issues)
$recentIssues = Issue::with(['creator', 'assignee'])
->latest()
->limit(10)
->get();
return view('admin.issue-reports.index', compact(
'stats',
'issuesByStatus',
'issuesByPriority',
'issuesByType',
'issuesCreatedOverTime',
'issuesClosedOverTime',
'assigneePerformance',
'timeTrackingMetrics',
'topLabels',
'avgResolutionTime',
'recentIssues',
'startDate',
'endDate'
));
}
}