Initial commit

This commit is contained in:
2025-11-20 23:21:05 +08:00
commit 13bc6db529
378 changed files with 54527 additions and 0 deletions

View File

@@ -0,0 +1,197 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Create Issue') }} (建立問題)
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.issues.store') }}" class="space-y-6">
@csrf
<!-- Title -->
<div>
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Title') }} <span class="text-red-500">*</span>
</label>
<input type="text" name="title" id="title" value="{{ old('title') }}" required maxlength="255"
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 @error('title') border-red-300 @enderror"
placeholder="{{ __('Brief summary of the issue') }}">
@error('title')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Description -->
<div>
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Description') }}
</label>
<textarea name="description" id="description" rows="5"
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"
placeholder="{{ __('Detailed description of the issue...') }}">{{ old('description') }}</textarea>
@error('description')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Issue Type and Priority -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="issue_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Issue Type') }} <span class="text-red-500">*</span>
</label>
<select name="issue_type" id="issue_type" required
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 @error('issue_type') border-red-300 @enderror">
<option value="">{{ __('Select type...') }}</option>
<option value="work_item" @selected(old('issue_type') === 'work_item')>{{ __('Work Item') }}</option>
<option value="project_task" @selected(old('issue_type') === 'project_task')>{{ __('Project Task') }}</option>
<option value="maintenance" @selected(old('issue_type') === 'maintenance')>{{ __('Maintenance') }}</option>
<option value="member_request" @selected(old('issue_type') === 'member_request')>{{ __('Member Request') }}</option>
</select>
@error('issue_type')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div>
<label for="priority" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Priority') }} <span class="text-red-500">*</span>
</label>
<select name="priority" id="priority" required
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 @error('priority') border-red-300 @enderror">
<option value="">{{ __('Select priority...') }}</option>
<option value="low" @selected(old('priority') === 'low')>{{ __('Low') }} </option>
<option value="medium" @selected(old('priority', 'medium') === 'medium')>{{ __('Medium') }} </option>
<option value="high" @selected(old('priority') === 'high')>{{ __('High') }} </option>
<option value="urgent" @selected(old('priority') === 'urgent')>{{ __('Urgent') }} </option>
</select>
@error('priority')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
</div>
<!-- Assignee and Due Date -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="assigned_to_user_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Assign To') }}
</label>
<select name="assigned_to_user_id" id="assigned_to_user_id"
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">
<option value="">{{ __('Unassigned') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}" @selected(old('assigned_to_user_id') == $user->id)>{{ $user->name }}</option>
@endforeach
</select>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('Optional: Assign to a team member') }}</p>
</div>
<div>
<label for="due_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Due Date') }}
</label>
<input type="date" name="due_date" id="due_date" value="{{ old('due_date') }}"
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">
@error('due_date')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
</div>
<!-- Estimated Hours -->
<div>
<label for="estimated_hours" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Estimated Hours') }}
</label>
<input type="number" name="estimated_hours" id="estimated_hours" value="{{ old('estimated_hours') }}" step="0.5" min="0"
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"
placeholder="0.0">
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('Estimated time to complete this issue') }}</p>
</div>
<!-- Member (for member requests) -->
<div>
<label for="member_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Related Member') }}
</label>
<select name="member_id" id="member_id"
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">
<option value="">{{ __('None') }}</option>
@foreach($members as $member)
<option value="{{ $member->id }}" @selected(old('member_id') == $member->id)>{{ $member->full_name }}</option>
@endforeach
</select>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('Link to a member for member requests') }}</p>
</div>
<!-- Parent Issue (for sub-tasks) -->
<div>
<label for="parent_issue_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Parent Issue') }}
</label>
<select name="parent_issue_id" id="parent_issue_id"
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">
<option value="">{{ __('None (top-level issue)') }}</option>
@foreach($openIssues as $parentIssue)
<option value="{{ $parentIssue->id }}" @selected(old('parent_issue_id') == $parentIssue->id)>
{{ $parentIssue->issue_number }} - {{ $parentIssue->title }}
</option>
@endforeach
</select>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('Make this a sub-task of another issue') }}</p>
</div>
<!-- Labels -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{{ __('Labels') }}
</label>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
@foreach($labels as $label)
<label class="inline-flex items-center">
<input type="checkbox" name="labels[]" value="{{ $label->id }}"
@checked(in_array($label->id, old('labels', [])))
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600">
<span class="ml-2 inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium"
style="background-color: {{ $label->color }}20; color: {{ $label->color }}">
{{ $label->name }}
</span>
</label>
@endforeach
</div>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">{{ __('Select one or more labels to categorize this issue') }}</p>
</div>
<!-- Actions -->
<div class="flex items-center justify-end gap-x-4 border-t border-gray-200 pt-6 dark:border-gray-700">
<a href="{{ route('admin.issues.index') }}"
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600 dark:hover:bg-gray-600">
{{ __('Cancel') }}
</a>
<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 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus:ring-offset-gray-800">
{{ __('Create Issue') }}
</button>
</div>
</form>
</div>
<!-- Help Section -->
<div class="mt-6 rounded-lg bg-blue-50 p-4 dark:bg-blue-900/30 border-l-4 border-blue-400">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd"/>
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200">{{ __('Creating Issues') }}</h3>
<div class="mt-2 text-sm text-blue-700 dark:text-blue-300">
<ul class="list-disc pl-5 space-y-1">
<li>{{ __('Use work items for general tasks and todos') }}</li>
<li>{{ __('Project tasks are for specific project milestones') }}</li>
<li>{{ __('Member requests track inquiries or requests from members') }}</li>
<li>{{ __('Assign issues to team members to track responsibility') }}</li>
<li>{{ __('Use labels to categorize and filter issues easily') }}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,184 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Edit Issue') }} - {{ $issue->issue_number }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.issues.update', $issue) }}" class="space-y-6">
@csrf
@method('PATCH')
<!-- Title -->
<div>
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Title') }} <span class="text-red-500">*</span>
</label>
<input type="text" name="title" id="title" value="{{ old('title', $issue->title) }}" required maxlength="255"
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 @error('title') border-red-300 @enderror"
placeholder="{{ __('Brief summary of the issue') }}">
@error('title')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Description -->
<div>
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Description') }}
</label>
<textarea name="description" id="description" rows="5"
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"
placeholder="{{ __('Detailed description of the issue...') }}">{{ old('description', $issue->description) }}</textarea>
@error('description')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Issue Type and Priority -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="issue_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Issue Type') }} <span class="text-red-500">*</span>
</label>
<select name="issue_type" id="issue_type" required
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 @error('issue_type') border-red-300 @enderror">
<option value="">{{ __('Select type...') }}</option>
<option value="work_item" @selected(old('issue_type', $issue->issue_type) === 'work_item')>{{ __('Work Item') }}</option>
<option value="project_task" @selected(old('issue_type', $issue->issue_type) === 'project_task')>{{ __('Project Task') }}</option>
<option value="maintenance" @selected(old('issue_type', $issue->issue_type) === 'maintenance')>{{ __('Maintenance') }}</option>
<option value="member_request" @selected(old('issue_type', $issue->issue_type) === 'member_request')>{{ __('Member Request') }}</option>
</select>
@error('issue_type')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div>
<label for="priority" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Priority') }} <span class="text-red-500">*</span>
</label>
<select name="priority" id="priority" required
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 @error('priority') border-red-300 @enderror">
<option value="">{{ __('Select priority...') }}</option>
<option value="low" @selected(old('priority', $issue->priority) === 'low')>{{ __('Low') }} </option>
<option value="medium" @selected(old('priority', $issue->priority) === 'medium')>{{ __('Medium') }} </option>
<option value="high" @selected(old('priority', $issue->priority) === 'high')>{{ __('High') }} </option>
<option value="urgent" @selected(old('priority', $issue->priority) === 'urgent')>{{ __('Urgent') }} </option>
</select>
@error('priority')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
</div>
<!-- Assignee and Reviewer -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="assigned_to_user_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Assign To') }}
</label>
<select name="assigned_to_user_id" id="assigned_to_user_id"
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">
<option value="">{{ __('Unassigned') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}" @selected(old('assigned_to_user_id', $issue->assigned_to_user_id) == $user->id)>{{ $user->name }}</option>
@endforeach
</select>
</div>
<div>
<label for="reviewer_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Reviewer') }}
</label>
<select name="reviewer_id" id="reviewer_id"
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">
<option value="">{{ __('None') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}" @selected(old('reviewer_id', $issue->reviewer_id) == $user->id)>{{ $user->name }}</option>
@endforeach
</select>
</div>
</div>
<!-- Due Date and Estimated Hours -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="due_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Due Date') }}
</label>
<input type="date" name="due_date" id="due_date" value="{{ old('due_date', $issue->due_date?->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">
@error('due_date')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div>
<label for="estimated_hours" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Estimated Hours') }}
</label>
<input type="number" name="estimated_hours" id="estimated_hours" value="{{ old('estimated_hours', $issue->estimated_hours) }}" step="0.5" min="0"
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>
<!-- Member (for member requests) -->
<div>
<label for="member_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Related Member') }}
</label>
<select name="member_id" id="member_id"
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">
<option value="">{{ __('None') }}</option>
@foreach($members as $member)
<option value="{{ $member->id }}" @selected(old('member_id', $issue->member_id) == $member->id)>{{ $member->full_name }}</option>
@endforeach
</select>
</div>
<!-- Parent Issue (for sub-tasks) -->
<div>
<label for="parent_issue_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Parent Issue') }}
</label>
<select name="parent_issue_id" id="parent_issue_id"
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">
<option value="">{{ __('None (top-level issue)') }}</option>
@foreach($openIssues as $parentIssue)
<option value="{{ $parentIssue->id }}" @selected(old('parent_issue_id', $issue->parent_issue_id) == $parentIssue->id)>
{{ $parentIssue->issue_number }} - {{ $parentIssue->title }}
</option>
@endforeach
</select>
</div>
<!-- Labels -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{{ __('Labels') }}
</label>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
@foreach($labels as $label)
<label class="inline-flex items-center">
<input type="checkbox" name="labels[]" value="{{ $label->id }}"
@checked(in_array($label->id, old('labels', $issue->labels->pluck('id')->toArray())))
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600">
<span class="ml-2 inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium"
style="background-color: {{ $label->color }}20; color: {{ $label->color }}">
{{ $label->name }}
</span>
</label>
@endforeach
</div>
</div>
<!-- Actions -->
<div class="flex items-center justify-end gap-x-4 border-t border-gray-200 pt-6 dark:border-gray-700">
<a href="{{ route('admin.issues.show', $issue) }}"
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600 dark:hover:bg-gray-600">
{{ __('Cancel') }}
</a>
<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 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus:ring-offset-gray-800">
{{ __('Update Issue') }}
</button>
</div>
</form>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,200 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Issues') }} (問題追蹤)
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
@if (session('status'))
<div class="mb-4 rounded-md bg-green-50 p-4 dark:bg-green-900/30 border-l-4 border-green-400" role="status" aria-live="polite">
<p class="text-sm text-green-800 dark:text-green-200">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="mb-4 rounded-md bg-red-50 p-4 dark:bg-red-900/30 border-l-4 border-red-400" role="alert">
<p class="text-sm text-red-800 dark:text-red-200">{{ session('error') }}</p>
</div>
@endif
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<!-- Header -->
<div class="sm:flex sm:items-center sm:justify-between mb-6">
<div>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('Issue Tracker') }}</h3>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">{{ __('Manage work items, tasks, and member requests') }}</p>
</div>
<div class="mt-4 sm:mt-0">
<a href="{{ route('admin.issues.create') }}" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus:ring-offset-gray-800">
<svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"/></svg>
{{ __('Create Issue') }}
</a>
</div>
</div>
<!-- Summary Stats -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-4 mb-6">
<div class="bg-blue-50 dark:bg-blue-900/30 rounded-lg p-4 border-l-4 border-blue-400">
<dt class="text-sm font-medium text-blue-800 dark:text-blue-200">{{ __('Total Open') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-blue-900 dark:text-blue-100">{{ $stats['total_open'] }}</dd>
</div>
<div class="bg-purple-50 dark:bg-purple-900/30 rounded-lg p-4 border-l-4 border-purple-400">
<dt class="text-sm font-medium text-purple-800 dark:text-purple-200">{{ __('Assigned to Me') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-purple-900 dark:text-purple-100">{{ $stats['assigned_to_me'] }}</dd>
</div>
<div class="bg-red-50 dark:bg-red-900/30 rounded-lg p-4 border-l-4 border-red-400">
<dt class="text-sm font-medium text-red-800 dark:text-red-200">{{ __('Overdue') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-red-900 dark:text-red-100">{{ $stats['overdue'] }}</dd>
</div>
<div class="bg-orange-50 dark:bg-orange-900/30 rounded-lg p-4 border-l-4 border-orange-400">
<dt class="text-sm font-medium text-orange-800 dark:text-orange-200">{{ __('High Priority') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-orange-900 dark:text-orange-100">{{ $stats['high_priority'] }}</dd>
</div>
</div>
<!-- Filters -->
<form method="GET" class="mb-6 space-y-4" role="search">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-4">
<div>
<label for="issue_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Type') }}</label>
<select name="issue_type" id="issue_type" 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">
<option value="">{{ __('All Types') }}</option>
<option value="work_item" @selected(request('issue_type') === 'work_item')>{{ __('Work Item') }}</option>
<option value="project_task" @selected(request('issue_type') === 'project_task')>{{ __('Project Task') }}</option>
<option value="maintenance" @selected(request('issue_type') === 'maintenance')>{{ __('Maintenance') }}</option>
<option value="member_request" @selected(request('issue_type') === 'member_request')>{{ __('Member Request') }}</option>
</select>
</div>
<div>
<label for="status" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Status') }}</label>
<select name="status" id="status" 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">
<option value="">{{ __('All Statuses') }}</option>
<option value="new" @selected(request('status') === 'new')>{{ __('New') }}</option>
<option value="assigned" @selected(request('status') === 'assigned')>{{ __('Assigned') }}</option>
<option value="in_progress" @selected(request('status') === 'in_progress')>{{ __('In Progress') }}</option>
<option value="review" @selected(request('status') === 'review')>{{ __('Review') }}</option>
<option value="closed" @selected(request('status') === 'closed')>{{ __('Closed') }}</option>
</select>
</div>
<div>
<label for="priority" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Priority') }}</label>
<select name="priority" id="priority" 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">
<option value="">{{ __('All Priorities') }}</option>
<option value="low" @selected(request('priority') === 'low')>{{ __('Low') }}</option>
<option value="medium" @selected(request('priority') === 'medium')>{{ __('Medium') }}</option>
<option value="high" @selected(request('priority') === 'high')>{{ __('High') }}</option>
<option value="urgent" @selected(request('priority') === 'urgent')>{{ __('Urgent') }}</option>
</select>
</div>
<div>
<label for="assigned_to" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Assignee') }}</label>
<select name="assigned_to" id="assigned_to" 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">
<option value="">{{ __('All Assignees') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}" @selected(request('assigned_to') == $user->id)>{{ $user->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="flex gap-4">
<div class="flex-1">
<label for="search" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Search') }}</label>
<input type="text" name="search" id="search" value="{{ request('search') }}" placeholder="{{ __('Issue number, title, or description...') }}" 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 items-end gap-2">
<button type="submit" class="inline-flex justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600 dark:hover:bg-gray-600">{{ __('Filter') }}</button>
<label class="inline-flex items-center">
<input type="checkbox" name="show_closed" value="1" @checked(request('show_closed') === '1') class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600">
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ __('Show closed') }}</span>
</label>
</div>
</div>
</form>
<!-- Issues Table -->
<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">
<caption class="sr-only">{{ __('List of issues with their current status and assignment') }}</caption>
<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">{{ __('Type') }}</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">{{ __('Due Date') }}</th>
<th scope="col" class="relative py-3.5 pl-3 pr-4"><span class="sr-only">{{ __('Actions') }}</span></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
@forelse($issues as $issue)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="py-4 pl-4 pr-3 text-sm">
<div class="font-medium text-gray-900 dark:text-gray-100">
<a href="{{ route('admin.issues.show', $issue) }}" class="hover:underline">{{ $issue->issue_number }}</a>
</div>
<div class="text-gray-500 dark:text-gray-400 line-clamp-1">{{ $issue->title }}</div>
@if($issue->labels->count() > 0)
<div class="mt-1 flex gap-1 flex-wrap">
@foreach($issue->labels as $label)
<span class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium" style="background-color: {{ $label->color }}20; color: {{ $label->color }}">
{{ $label->name }}
</span>
@endforeach
</div>
@endif
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $issue->issue_type_label }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
<x-issue.status-badge :status="$issue->status" :label="$issue->status_label" />
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
<x-issue.priority-badge :priority="$issue->priority" :label="$issue->priority_label" />
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $issue->assignee?->name ?? __('Unassigned') }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
@if($issue->due_date)
<span class="{{ $issue->is_overdue ? 'text-red-600 dark:text-red-400 font-semibold' : '' }}">
{{ $issue->due_date->format('Y-m-d') }}
@if($issue->is_overdue)
<span class="text-xs">({{ __('Overdue') }})</span>
@endif
</span>
@else
<span class="text-gray-400"></span>
@endif
</td>
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium">
<a href="{{ route('admin.issues.show', $issue) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">{{ __('View') }}</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
<p>{{ __('No issues found') }}</p>
<div class="mt-4">
<a href="{{ route('admin.issues.create') }}" class="inline-flex items-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">
+ {{ __('Create First Issue') }}
</a>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Pagination -->
@if($issues->hasPages())
<div class="mt-6">{{ $issues->links() }}</div>
@endif
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,371 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ $issue->issue_number }} - {{ $issue->title }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4 dark:bg-green-900/30 border-l-4 border-green-400" role="status">
<p class="text-sm text-green-800 dark:text-green-200">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="rounded-md bg-red-50 p-4 dark:bg-red-900/30 border-l-4 border-red-400" role="alert">
<p class="text-sm text-red-800 dark:text-red-200">{{ session('error') }}</p>
</div>
@endif
<!-- Main Details -->
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<div class="sm:flex sm:items-start sm:justify-between mb-6">
<div class="flex-1">
<div class="flex items-center gap-3 mb-2">
<h3 class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ $issue->title }}</h3>
<x-issue.status-badge :status="$issue->status" :label="$issue->status_label" />
<x-issue.priority-badge :priority="$issue->priority" :label="$issue->priority_label" />
</div>
<div class="flex items-center gap-4 text-sm text-gray-500 dark:text-gray-400">
<span>{{ $issue->issue_type_label }}</span>
<span>"</span>
<span>{{ __('Created by') }} {{ $issue->creator->name }}</span>
<span>"</span>
<span>{{ $issue->created_at->diffForHumans() }}</span>
</div>
</div>
<div class="mt-4 sm:mt-0 flex gap-2">
@if(!$issue->isClosed() || Auth::user()->is_admin)
<a href="{{ route('admin.issues.edit', $issue) }}" class="inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600">
{{ __('Edit') }}
</a>
@endif
@if(Auth::user()->is_admin)
<form method="POST" action="{{ route('admin.issues.destroy', $issue) }}" onsubmit="return confirm('{{ __('Are you sure?') }}')">
@csrf
@method('DELETE')
<button type="submit" class="inline-flex items-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 dark:bg-red-500 dark:hover:bg-red-400">
{{ __('Delete') }}
</button>
</form>
@endif
</div>
</div>
<!-- Labels -->
@if($issue->labels->count() > 0)
<div class="mb-6 flex gap-2 flex-wrap">
@foreach($issue->labels as $label)
<span class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium"
style="background-color: {{ $label->color }}; color: {{ $label->text_color }}">
{{ $label->name }}
</span>
@endforeach
</div>
@endif
<!-- Description -->
<div class="prose dark:prose-invert max-w-none mb-6">
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">{{ __('Description') }}</h4>
<div class="text-gray-700 dark:text-gray-300 whitespace-pre-wrap">{{ $issue->description ?: __('No description provided') }}</div>
</div>
<!-- Details Grid -->
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2 border-t border-gray-200 dark:border-gray-700 pt-6">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Assigned To') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $issue->assignee?->name ?? __('Unassigned') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Reviewer') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $issue->reviewer?->name ?? __('None') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Due Date') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
@if($issue->due_date)
<span class="{{ $issue->is_overdue ? 'text-red-600 dark:text-red-400 font-semibold' : '' }}">
{{ $issue->due_date->format('Y-m-d') }}
@if($issue->is_overdue)
({{ __('Overdue by :days days', ['days' => abs($issue->days_until_due)]) }})
@elseif($issue->days_until_due !== null && $issue->days_until_due >= 0)
({{ __(':days days left', ['days' => $issue->days_until_due]) }})
@endif
</span>
@else
<span class="text-gray-400">{{ __('No due date') }}</span>
@endif
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Time Tracking') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
{{ number_format($issue->actual_hours, 1) }}h
@if($issue->estimated_hours)
/ {{ number_format($issue->estimated_hours, 1) }}h {{ __('estimated') }}
@endif
</dd>
</div>
@if($issue->member)
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Related Member') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $issue->member->full_name }}</dd>
</div>
@endif
@if($issue->parentIssue)
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Parent Issue') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<a href="{{ route('admin.issues.show', $issue->parentIssue) }}" class="text-indigo-600 hover:underline dark:text-indigo-400">
{{ $issue->parentIssue->issue_number }} - {{ $issue->parentIssue->title }}
</a>
</dd>
</div>
@endif
</dl>
<!-- Sub-tasks -->
@if($issue->subTasks->count() > 0)
<div class="mt-6 border-t border-gray-200 dark:border-gray-700 pt-6">
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">{{ __('Sub-tasks') }} ({{ $issue->subTasks->count() }})</h4>
<ul class="space-y-2">
@foreach($issue->subTasks as $subTask)
<li class="flex items-center gap-2">
<x-issue.status-badge :status="$subTask->status" />
<a href="{{ route('admin.issues.show', $subTask) }}" class="text-indigo-600 hover:underline dark:text-indigo-400">
{{ $subTask->issue_number }} - {{ $subTask->title }}
</a>
</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Left Column: Comments, Attachments, Time Logs -->
<div class="lg:col-span-2 space-y-6">
<!-- Workflow Actions -->
@if(!$issue->isClosed())
<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">{{ __('Actions') }}</h3>
<div class="flex flex-wrap gap-2">
<!-- Update Status -->
<form method="POST" action="{{ route('admin.issues.update-status', $issue) }}" class="inline-flex gap-2">
@csrf
@method('PATCH')
<select name="status" class="rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="new" @selected($issue->status === 'new')>{{ __('New') }}</option>
<option value="assigned" @selected($issue->status === 'assigned')>{{ __('Assigned') }}</option>
<option value="in_progress" @selected($issue->status === 'in_progress')>{{ __('In Progress') }}</option>
<option value="review" @selected($issue->status === 'review')>{{ __('Review') }}</option>
<option value="closed" @selected($issue->status === 'closed')>{{ __('Closed') }}</option>
</select>
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400">
{{ __('Update Status') }}
</button>
</form>
<!-- Assign -->
<form method="POST" action="{{ route('admin.issues.assign', $issue) }}" class="inline-flex gap-2">
@csrf
<select name="assigned_to_user_id" class="rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('Unassigned') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}" @selected($issue->assigned_to_user_id == $user->id)>{{ $user->name }}</option>
@endforeach
</select>
<button type="submit" class="inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600">
{{ __('Assign') }}
</button>
</form>
</div>
</div>
@endif
<!-- Comments -->
<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">
{{ __('Comments') }} ({{ $issue->comments->count() }})
</h3>
<!-- Comments List -->
<div class="space-y-4 mb-6">
@forelse($issue->comments as $comment)
<div class="border-l-2 {{ $comment->is_internal ? 'border-orange-400 bg-orange-50 dark:bg-orange-900/20' : 'border-gray-300 dark:border-gray-600' }} pl-4 py-2">
<div class="flex items-center gap-2 mb-1">
<span class="font-medium text-sm text-gray-900 dark:text-gray-100">{{ $comment->user->name }}</span>
<span class="text-xs text-gray-500 dark:text-gray-400">{{ $comment->created_at->diffForHumans() }}</span>
@if($comment->is_internal)
<span class="text-xs bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 px-2 py-0.5 rounded">{{ __('Internal') }}</span>
@endif
</div>
<p class="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-wrap">{{ $comment->comment_text }}</p>
</div>
@empty
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">{{ __('No comments yet') }}</p>
@endforelse
</div>
<!-- Add Comment Form -->
<form method="POST" action="{{ route('admin.issues.comments.store', $issue) }}" class="border-t border-gray-200 dark:border-gray-700 pt-4">
@csrf
<textarea name="comment_text" rows="3" required
class="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 mb-2"
placeholder="{{ __('Add a comment...') }}"></textarea>
<div class="flex items-center justify-between">
<label class="inline-flex items-center">
<input type="checkbox" name="is_internal" value="1" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600">
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ __('Internal comment') }}</span>
</label>
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500">
{{ __('Add Comment') }}
</button>
</div>
</form>
</div>
<!-- Attachments -->
<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">
{{ __('Attachments') }} ({{ $issue->attachments->count() }})
</h3>
<div class="space-y-2 mb-6">
@forelse($issue->attachments as $attachment)
<div class="flex items-center justify-between p-2 bg-gray-50 dark:bg-gray-900 rounded">
<div class="flex items-center gap-2">
<svg class="h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z" clip-rule="evenodd"/>
</svg>
<div>
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ $attachment->file_name }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ $attachment->file_size_human }} " {{ $attachment->user->name }} " {{ $attachment->created_at->diffForHumans() }}</p>
</div>
</div>
<div class="flex gap-2">
<a href="{{ route('admin.issues.attachments.download', $attachment) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 text-sm">{{ __('Download') }}</a>
@if(Auth::user()->is_admin)
<form method="POST" action="{{ route('admin.issues.attachments.destroy', $attachment) }}" onsubmit="return confirm('{{ __('Delete this attachment?') }}')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 text-sm">{{ __('Delete') }}</button>
</form>
@endif
</div>
</div>
@empty
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">{{ __('No attachments') }}</p>
@endforelse
</div>
<!-- Upload Form -->
<form method="POST" action="{{ route('admin.issues.attachments.store', $issue) }}" enctype="multipart/form-data" class="border-t border-gray-200 dark:border-gray-700 pt-4">
@csrf
<div class="flex items-center gap-2">
<input type="file" name="file" required class="block w-full text-sm text-gray-900 dark:text-gray-100 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100 dark:file:bg-indigo-900 dark:file:text-indigo-200">
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500">
{{ __('Upload') }}
</button>
</div>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ __('Max size: 10MB') }}</p>
</form>
</div>
<!-- Time Logs -->
<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') }} ({{ number_format($issue->total_time_logged, 1) }}h total)
</h3>
<div class="space-y-2 mb-6">
@forelse($issue->timeLogs->sortByDesc('logged_at') as $timeLog)
<div class="flex items-center justify-between p-2 bg-gray-50 dark:bg-gray-900 rounded">
<div>
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ number_format($timeLog->hours, 2) }}h - {{ $timeLog->user->name }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ $timeLog->logged_at->format('Y-m-d') }} - {{ $timeLog->description }}</p>
</div>
</div>
@empty
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">{{ __('No time logged yet') }}</p>
@endforelse
</div>
<!-- Log Time Form -->
<form method="POST" action="{{ route('admin.issues.time-logs.store', $issue) }}" class="border-t border-gray-200 dark:border-gray-700 pt-4 grid grid-cols-2 gap-2">
@csrf
<input type="number" name="hours" step="0.25" min="0.25" placeholder="{{ __('Hours') }}" required
class="rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<input type="date" name="logged_at" value="{{ now()->format('Y-m-d') }}" required
class="rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<input type="text" name="description" placeholder="{{ __('What did you do?') }}"
class="col-span-2 rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<button type="submit" class="col-span-2 inline-flex justify-center items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500">
{{ __('Log Time') }}
</button>
</form>
</div>
</div>
<!-- Right Column: Timeline & Watchers -->
<div class="space-y-6">
<!-- Timeline -->
<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">{{ __('Progress') }}</h3>
<x-issue.timeline :issue="$issue" />
<div class="mt-4">
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-700 dark:text-gray-300">{{ __('Completion') }}</span>
<span class="font-medium text-gray-900 dark:text-gray-100">{{ $issue->progress_percentage }}%</span>
</div>
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div class="bg-indigo-600 dark:bg-indigo-500 h-2 rounded-full transition-all" style="width: {{ $issue->progress_percentage }}%"></div>
</div>
</div>
</div>
<!-- Watchers -->
<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">
{{ __('Watchers') }} ({{ $issue->watchers->count() }})
</h3>
<ul class="space-y-2 mb-4">
@foreach($issue->watchers as $watcher)
<li class="flex items-center justify-between">
<span class="text-sm text-gray-900 dark:text-gray-100">{{ $watcher->name }}</span>
@if($watcher->id !== $issue->created_by_user_id)
<form method="POST" action="{{ route('admin.issues.watchers.destroy', $issue) }}">
@csrf
@method('DELETE')
<input type="hidden" name="user_id" value="{{ $watcher->id }}">
<button type="submit" class="text-xs text-red-600 hover:text-red-900 dark:text-red-400">{{ __('Remove') }}</button>
</form>
@endif
</li>
@endforeach
</ul>
<!-- Add Watcher -->
<form method="POST" action="{{ route('admin.issues.watchers.store', $issue) }}" class="border-t border-gray-200 dark:border-gray-700 pt-4">
@csrf
<div class="flex gap-2">
<select name="user_id" required class="flex-1 rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('Add watcher...') }}</option>
@foreach($users->whereNotIn('id', $issue->watchers->pluck('id')) as $user)
<option value="{{ $user->id }}">{{ $user->name }}</option>
@endforeach
</select>
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500">
{{ __('Add') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>