chore: cleanup dead code + format with Pint
- Remove unused FeaturesController (empty stubs, no routes) - Remove ConvertSpatialFile CLI command (unused; service used in LayerManager) - Remove MigrateGeojsonToFeatures CLI command (one-shot migration, not referenced) - Remove .claude/worktrees/ (11 old agent worktrees from June) - Apply Laravel Pint formatting across 219 files (style only, no functional changes) Tests: 101 passing (319 assertions) API routes: unchanged (8 routes intact)
This commit is contained in:
@@ -12,11 +12,15 @@ use Livewire\Component;
|
||||
class IssueChecklistManager extends Component
|
||||
{
|
||||
public Project $project;
|
||||
|
||||
public $templates = [];
|
||||
|
||||
public bool $showForm = false;
|
||||
|
||||
public $editingId = null;
|
||||
|
||||
public string $name = '';
|
||||
|
||||
public array $items = [''];
|
||||
|
||||
public function mount(Project $project)
|
||||
@@ -29,6 +33,7 @@ class IssueChecklistManager extends Component
|
||||
private function canAccessProject(): bool
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
return $user->can('manage all')
|
||||
|| $this->project->users()->where('user_id', $user->id)->exists();
|
||||
}
|
||||
@@ -74,14 +79,15 @@ class IssueChecklistManager extends Component
|
||||
public function save(): void
|
||||
{
|
||||
$this->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'items' => 'required|array',
|
||||
'name' => 'required|string|max:255',
|
||||
'items' => 'required|array',
|
||||
'items.*' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$items = array_values(array_filter(array_map('trim', $this->items), fn ($v) => $v !== ''));
|
||||
if (empty($items)) {
|
||||
$this->addError('items', 'Añade al menos una tarea.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ namespace App\Livewire\Issues;
|
||||
|
||||
use App\Models\Issue;
|
||||
use App\Models\IssueChecklistTemplate;
|
||||
use App\Models\IssueComment;
|
||||
use App\Models\IssueTask;
|
||||
use App\Models\Media;
|
||||
use App\Models\Project;
|
||||
use App\Notifications\IssueCommentedNotification;
|
||||
use App\Notifications\IssueStatusChangedNotification;
|
||||
use App\Notifications\IssueTaskAssignedNotification;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Component;
|
||||
@@ -22,19 +22,24 @@ class IssueDetail extends Component
|
||||
use WithFileUploads;
|
||||
|
||||
public Project $project;
|
||||
|
||||
public Issue $issue;
|
||||
|
||||
// New task form
|
||||
public string $newTaskTitle = '';
|
||||
|
||||
public $newTaskAssignee = '';
|
||||
|
||||
public $newTaskDue = '';
|
||||
|
||||
// Checklist templates
|
||||
public $checklistTemplates = [];
|
||||
|
||||
public $applyTemplateId = '';
|
||||
|
||||
// New comment form
|
||||
public string $newComment = '';
|
||||
|
||||
public $commentPhoto = null; // single optional photo on a comment
|
||||
|
||||
// Issue-level photos
|
||||
@@ -61,6 +66,7 @@ class IssueDetail extends Component
|
||||
private function canAccessProject(): bool
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
return $user->can('manage all')
|
||||
|| $this->project->users()->where('user_id', $user->id)->exists();
|
||||
}
|
||||
@@ -97,17 +103,17 @@ class IssueDetail extends Component
|
||||
{
|
||||
abort_unless($this->canEdit(), 403);
|
||||
$this->validate([
|
||||
'newTaskTitle' => 'required|string|max:255',
|
||||
'newTaskTitle' => 'required|string|max:255',
|
||||
'newTaskAssignee' => 'nullable|exists:users,id',
|
||||
'newTaskDue' => 'nullable|date',
|
||||
'newTaskDue' => 'nullable|date',
|
||||
]);
|
||||
|
||||
$task = $this->issue->tasks()->create([
|
||||
'title' => $this->newTaskTitle,
|
||||
'title' => $this->newTaskTitle,
|
||||
'assigned_to' => $this->newTaskAssignee ?: null,
|
||||
'due_date' => $this->newTaskDue ?: null,
|
||||
'order' => ((int) $this->issue->tasks()->max('order')) + 1,
|
||||
'uuid' => (string) \Illuminate\Support\Str::uuid(),
|
||||
'due_date' => $this->newTaskDue ?: null,
|
||||
'order' => ((int) $this->issue->tasks()->max('order')) + 1,
|
||||
'uuid' => (string) Str::uuid(),
|
||||
]);
|
||||
|
||||
// Notify the assignee (unless they assigned it to themselves).
|
||||
@@ -134,7 +140,7 @@ class IssueDetail extends Component
|
||||
$this->issue->tasks()->create([
|
||||
'title' => $title,
|
||||
'order' => ++$order,
|
||||
'uuid' => (string) \Illuminate\Support\Str::uuid(),
|
||||
'uuid' => (string) Str::uuid(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -170,7 +176,7 @@ class IssueDetail extends Component
|
||||
{
|
||||
// Anyone who can view the issue (and is a member) may comment / report progress.
|
||||
$this->validate([
|
||||
'newComment' => 'nullable|string|max:5000',
|
||||
'newComment' => 'nullable|string|max:5000',
|
||||
'commentPhoto' => 'nullable|image|max:20480', // 20 MB
|
||||
]);
|
||||
|
||||
@@ -182,8 +188,8 @@ class IssueDetail extends Component
|
||||
|
||||
$comment = $this->issue->comments()->create([
|
||||
'user_id' => Auth::id(),
|
||||
'body' => trim($this->newComment) ?: '(foto)',
|
||||
'uuid' => (string) \Illuminate\Support\Str::uuid(),
|
||||
'body' => trim($this->newComment) ?: '(foto)',
|
||||
'uuid' => (string) Str::uuid(),
|
||||
]);
|
||||
|
||||
if ($this->commentPhoto) {
|
||||
@@ -215,7 +221,7 @@ class IssueDetail extends Component
|
||||
|
||||
public function deleteMedia($mediaId): void
|
||||
{
|
||||
$media = \App\Models\Media::findOrFail($mediaId);
|
||||
$media = Media::findOrFail($mediaId);
|
||||
$user = Auth::user();
|
||||
abort_unless($user->can('delete media') || $media->uploaded_by === $user->id, 403);
|
||||
$media->delete();
|
||||
@@ -230,14 +236,14 @@ class IssueDetail extends Component
|
||||
$path = $file->store("uploads/issues/{$this->issue->id}/{$entity}", 'public');
|
||||
|
||||
$parent->media()->create([
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'file_path' => $path,
|
||||
'file_type' => $mime,
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'file_path' => $path,
|
||||
'file_type' => $mime,
|
||||
'file_extension' => $file->getClientOriginalExtension(),
|
||||
'file_size' => $file->getSize(),
|
||||
'category' => str_starts_with($mime, 'image/') ? 'image' : 'document',
|
||||
'uploaded_by' => Auth::id(),
|
||||
'uuid' => (string) \Illuminate\Support\Str::uuid(),
|
||||
'file_size' => $file->getSize(),
|
||||
'category' => str_starts_with($mime, 'image/') ? 'image' : 'document',
|
||||
'uploaded_by' => Auth::id(),
|
||||
'uuid' => (string) Str::uuid(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -256,8 +262,8 @@ class IssueDetail extends Component
|
||||
{
|
||||
abort_unless($this->canEdit(), 403);
|
||||
$this->issue->update([
|
||||
'status' => 'resolved',
|
||||
'resolved_at' => $this->issue->resolved_at ?? now(),
|
||||
'status' => 'resolved',
|
||||
'resolved_at' => $this->issue->resolved_at ?? now(),
|
||||
'resolution_notes' => $this->resolutionNotes ?: null,
|
||||
]);
|
||||
$this->notifyStakeholders(new IssueStatusChangedNotification($this->issue, 'resolved'));
|
||||
@@ -269,7 +275,7 @@ class IssueDetail extends Component
|
||||
{
|
||||
abort_unless($this->canEdit(), 403);
|
||||
$this->issue->update([
|
||||
'status' => 'closed',
|
||||
'status' => 'closed',
|
||||
'resolved_at' => $this->issue->resolved_at ?? now(),
|
||||
]);
|
||||
$this->notifyStakeholders(new IssueStatusChangedNotification($this->issue, 'closed'));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Issues;
|
||||
|
||||
use App\Models\Feature;
|
||||
use App\Models\Issue;
|
||||
use App\Models\Project;
|
||||
use App\Notifications\IssueAssignedNotification;
|
||||
@@ -14,22 +15,31 @@ use Livewire\Component;
|
||||
class IssueForm extends Component
|
||||
{
|
||||
public Project $project;
|
||||
|
||||
public ?Issue $issue = null; // null = create, set = edit
|
||||
|
||||
public $projectUsers = [];
|
||||
|
||||
// Form fields
|
||||
public $title = '';
|
||||
|
||||
public $description = '';
|
||||
|
||||
public $status = 'open';
|
||||
|
||||
public $priority = 'medium';
|
||||
|
||||
public $type = 'defect';
|
||||
|
||||
public $assignedTo = '';
|
||||
|
||||
public $resolutionNotes = '';
|
||||
|
||||
// Optional context (e.g. when reporting from a map feature)
|
||||
public $featureId = null;
|
||||
|
||||
public $inspectionId = null;
|
||||
|
||||
public $featureName = null; // shown when the issue is pre-linked to a map element
|
||||
|
||||
public function mount(Project $project, ?Issue $issue = null)
|
||||
@@ -44,22 +54,22 @@ class IssueForm extends Component
|
||||
$this->projectUsers = $project->users()->orderBy('name')->get();
|
||||
|
||||
if ($issue) {
|
||||
$this->issue = $issue;
|
||||
$this->title = $issue->title;
|
||||
$this->description = $issue->description ?? '';
|
||||
$this->status = $issue->status;
|
||||
$this->priority = $issue->priority;
|
||||
$this->type = $issue->type ?? 'defect';
|
||||
$this->assignedTo = $issue->assigned_to ?? '';
|
||||
$this->issue = $issue;
|
||||
$this->title = $issue->title;
|
||||
$this->description = $issue->description ?? '';
|
||||
$this->status = $issue->status;
|
||||
$this->priority = $issue->priority;
|
||||
$this->type = $issue->type ?? 'defect';
|
||||
$this->assignedTo = $issue->assigned_to ?? '';
|
||||
$this->resolutionNotes = $issue->resolution_notes ?? '';
|
||||
$this->featureId = $issue->feature_id;
|
||||
$this->inspectionId = $issue->inspection_id;
|
||||
$this->featureName = $issue->feature?->name;
|
||||
$this->featureId = $issue->feature_id;
|
||||
$this->inspectionId = $issue->inspection_id;
|
||||
$this->featureName = $issue->feature?->name;
|
||||
} elseif ($featureId = request()->integer('feature')) {
|
||||
// Pre-link to a map element when reporting from the project map.
|
||||
$feature = \App\Models\Feature::with('layer.phase')->find($featureId);
|
||||
$feature = Feature::with('layer.phase')->find($featureId);
|
||||
if ($feature && $feature->layer?->phase?->project_id === $project->id) {
|
||||
$this->featureId = $feature->id;
|
||||
$this->featureId = $feature->id;
|
||||
$this->featureName = $feature->name;
|
||||
}
|
||||
}
|
||||
@@ -68,6 +78,7 @@ class IssueForm extends Component
|
||||
private function canAccessProject(): bool
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
return $user->can('manage all')
|
||||
|| $this->project->users()->where('user_id', $user->id)->exists();
|
||||
}
|
||||
@@ -75,12 +86,12 @@ class IssueForm extends Component
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'required|in:' . implode(',', Issue::STATUSES),
|
||||
'priority' => 'required|in:' . implode(',', Issue::PRIORITIES),
|
||||
'type' => 'required|in:' . implode(',', Issue::TYPES),
|
||||
'assignedTo' => 'nullable|exists:users,id',
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'required|in:'.implode(',', Issue::STATUSES),
|
||||
'priority' => 'required|in:'.implode(',', Issue::PRIORITIES),
|
||||
'type' => 'required|in:'.implode(',', Issue::TYPES),
|
||||
'assignedTo' => 'nullable|exists:users,id',
|
||||
'resolutionNotes' => 'nullable|string',
|
||||
];
|
||||
}
|
||||
@@ -91,14 +102,14 @@ class IssueForm extends Component
|
||||
$this->validate();
|
||||
|
||||
$payload = [
|
||||
'title' => $this->title,
|
||||
'description' => $this->description,
|
||||
'status' => $this->status,
|
||||
'priority' => $this->priority,
|
||||
'type' => $this->type,
|
||||
'feature_id' => $this->featureId,
|
||||
'inspection_id' => $this->inspectionId,
|
||||
'assigned_to' => $this->assignedTo ?: null,
|
||||
'title' => $this->title,
|
||||
'description' => $this->description,
|
||||
'status' => $this->status,
|
||||
'priority' => $this->priority,
|
||||
'type' => $this->type,
|
||||
'feature_id' => $this->featureId,
|
||||
'inspection_id' => $this->inspectionId,
|
||||
'assigned_to' => $this->assignedTo ?: null,
|
||||
'resolution_notes' => $this->resolutionNotes ?: null,
|
||||
];
|
||||
|
||||
@@ -120,7 +131,7 @@ class IssueForm extends Component
|
||||
}
|
||||
} else {
|
||||
$issue = Issue::create(array_merge($payload, [
|
||||
'project_id' => $this->project->id,
|
||||
'project_id' => $this->project->id,
|
||||
'reported_by' => Auth::id(),
|
||||
]));
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
namespace App\Livewire\Issues;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Issue;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\On;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use App\Models\Project;
|
||||
use App\Models\Issue;
|
||||
use Livewire\Component;
|
||||
|
||||
#[Layout('layouts.app')]
|
||||
class IssueManager extends Component
|
||||
@@ -24,6 +24,7 @@ class IssueManager extends Component
|
||||
private function canAccessProject(): bool
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
return $user->can('manage all')
|
||||
|| $this->project->users()->where('user_id', $user->id)->exists();
|
||||
}
|
||||
@@ -44,11 +45,11 @@ class IssueManager extends Component
|
||||
->pluck('c', 'status');
|
||||
|
||||
return view('livewire.issues.issue-manager', [
|
||||
'countOpen' => (int) ($counts['open'] ?? 0),
|
||||
'countOpen' => (int) ($counts['open'] ?? 0),
|
||||
'countInReview' => (int) ($counts['in_review'] ?? 0),
|
||||
'countResolved' => (int) ($counts['resolved'] ?? 0),
|
||||
'countClosed' => (int) ($counts['closed'] ?? 0),
|
||||
'countTotal' => (int) $counts->sum(),
|
||||
'countClosed' => (int) ($counts['closed'] ?? 0),
|
||||
'countTotal' => (int) $counts->sum(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Livewire\Issues;
|
||||
|
||||
use App\Models\Issue;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -19,9 +20,9 @@ class IssueTable extends DataTableComponent
|
||||
public function configure(): void
|
||||
{
|
||||
$this->setPrimaryKey('id')
|
||||
->setDefaultSort('created_at', 'desc')
|
||||
->setSortingPillsEnabled(false)
|
||||
->setAdditionalSelects(['issues.id as id', 'issues.created_at as created_at']);
|
||||
->setDefaultSort('created_at', 'desc')
|
||||
->setSortingPillsEnabled(false)
|
||||
->setAdditionalSelects(['issues.id as id', 'issues.created_at as created_at']);
|
||||
}
|
||||
|
||||
public function builder(): Builder
|
||||
@@ -31,7 +32,7 @@ class IssueTable extends DataTableComponent
|
||||
abort_unless(
|
||||
$user->can('view issues') && (
|
||||
$user->can('manage all')
|
||||
|| \App\Models\Project::whereKey($this->projectId)->whereHas('users', fn ($q) => $q->where('user_id', $user->id))->exists()
|
||||
|| Project::whereKey($this->projectId)->whereHas('users', fn ($q) => $q->where('user_id', $user->id))->exists()
|
||||
),
|
||||
403
|
||||
);
|
||||
@@ -53,119 +54,129 @@ class IssueTable extends DataTableComponent
|
||||
{
|
||||
return [
|
||||
Column::make('Prioridad', 'priority')
|
||||
->sortable()
|
||||
->format(function ($value, $row) {
|
||||
$label = ['low' => 'Bajo', 'medium' => 'Medio', 'high' => 'Alto', 'critical' => 'Crítico'][$value] ?? ucfirst($value);
|
||||
$textColor = in_array($value, ['critical', 'high']) ? '#fff' : '#1f2937';
|
||||
return '<span class="badge badge-sm font-semibold" style="background-color:'.$row->priority_color.';color:'.$textColor.';border-color:transparent;">'.$label.'</span>';
|
||||
})
|
||||
->html(),
|
||||
->sortable()
|
||||
->format(function ($value, $row) {
|
||||
$label = ['low' => 'Bajo', 'medium' => 'Medio', 'high' => 'Alto', 'critical' => 'Crítico'][$value] ?? ucfirst($value);
|
||||
$textColor = in_array($value, ['critical', 'high']) ? '#fff' : '#1f2937';
|
||||
|
||||
return '<span class="badge badge-sm font-semibold" style="background-color:'.$row->priority_color.';color:'.$textColor.';border-color:transparent;">'.$label.'</span>';
|
||||
})
|
||||
->html(),
|
||||
|
||||
Column::make('Título', 'title')
|
||||
->sortable()
|
||||
->searchable()
|
||||
->format(function ($value, $row) {
|
||||
$url = route('projects.issues.show', [$this->projectId, $row->id]);
|
||||
$html = '<a href="'.$url.'" wire:navigate class="font-medium text-sm link link-hover">'.e($value).'</a>';
|
||||
if ($row->description) {
|
||||
$html .= '<div class="text-xs text-base-content/50 truncate max-w-xs">'.e(Str::limit($row->description, 60)).'</div>';
|
||||
}
|
||||
$meta = [];
|
||||
if ($row->reporter) $meta[] = 'Reportado por '.e($row->reporter->name);
|
||||
if ($row->comments_count) $meta[] = '💬 '.$row->comments_count;
|
||||
if ($row->media_count) $meta[] = '📷 '.$row->media_count;
|
||||
if ($meta) {
|
||||
$html .= '<div class="text-xs text-base-content/40 mt-0.5">'.implode(' · ', $meta).'</div>';
|
||||
}
|
||||
if ($row->tasks_count) {
|
||||
$pct = (int) round($row->tasks_done_count / $row->tasks_count * 100);
|
||||
$html .= '<div class="flex items-center gap-2 mt-1 max-w-xs">
|
||||
->sortable()
|
||||
->searchable()
|
||||
->format(function ($value, $row) {
|
||||
$url = route('projects.issues.show', [$this->projectId, $row->id]);
|
||||
$html = '<a href="'.$url.'" wire:navigate class="font-medium text-sm link link-hover">'.e($value).'</a>';
|
||||
if ($row->description) {
|
||||
$html .= '<div class="text-xs text-base-content/50 truncate max-w-xs">'.e(Str::limit($row->description, 60)).'</div>';
|
||||
}
|
||||
$meta = [];
|
||||
if ($row->reporter) {
|
||||
$meta[] = 'Reportado por '.e($row->reporter->name);
|
||||
}
|
||||
if ($row->comments_count) {
|
||||
$meta[] = '💬 '.$row->comments_count;
|
||||
}
|
||||
if ($row->media_count) {
|
||||
$meta[] = '📷 '.$row->media_count;
|
||||
}
|
||||
if ($meta) {
|
||||
$html .= '<div class="text-xs text-base-content/40 mt-0.5">'.implode(' · ', $meta).'</div>';
|
||||
}
|
||||
if ($row->tasks_count) {
|
||||
$pct = (int) round($row->tasks_done_count / $row->tasks_count * 100);
|
||||
$html .= '<div class="flex items-center gap-2 mt-1 max-w-xs">
|
||||
<progress class="progress progress-success w-24 h-1.5" value="'.$pct.'" max="100"></progress>
|
||||
<span class="text-xs text-base-content/50">'.$row->tasks_done_count.'/'.$row->tasks_count.' tareas</span>
|
||||
</div>';
|
||||
}
|
||||
if ($row->overdue_tasks_count) {
|
||||
$html .= '<div class="mt-1"><span class="badge badge-error badge-sm gap-1">⏰ '.$row->overdue_tasks_count.' vencida'.($row->overdue_tasks_count > 1 ? 's' : '').'</span></div>';
|
||||
}
|
||||
return $html;
|
||||
})
|
||||
->html(),
|
||||
}
|
||||
if ($row->overdue_tasks_count) {
|
||||
$html .= '<div class="mt-1"><span class="badge badge-error badge-sm gap-1">⏰ '.$row->overdue_tasks_count.' vencida'.($row->overdue_tasks_count > 1 ? 's' : '').'</span></div>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
})
|
||||
->html(),
|
||||
|
||||
Column::make('Tipo', 'type')
|
||||
->sortable()
|
||||
->format(fn ($value, $row) =>
|
||||
'<span class="badge badge-sm" style="background-color:'.$row->type_color.';color:#fff;border-color:transparent;">'.e($row->type_label).'</span>')
|
||||
->html(),
|
||||
->sortable()
|
||||
->format(fn ($value, $row) => '<span class="badge badge-sm" style="background-color:'.$row->type_color.';color:#fff;border-color:transparent;">'.e($row->type_label).'</span>')
|
||||
->html(),
|
||||
|
||||
Column::make('Feature')
|
||||
->label(fn ($row) => $row->feature
|
||||
? '<span class="badge badge-outline badge-sm">'.e($row->feature->name).'</span>'
|
||||
: '<span class="text-base-content/30 text-xs">—</span>')
|
||||
->html(),
|
||||
->label(fn ($row) => $row->feature
|
||||
? '<span class="badge badge-outline badge-sm">'.e($row->feature->name).'</span>'
|
||||
: '<span class="text-base-content/30 text-xs">—</span>')
|
||||
->html(),
|
||||
|
||||
Column::make('Estado', 'status')
|
||||
->sortable()
|
||||
->format(function ($value, $row) {
|
||||
$label = ['open' => 'Abierto', 'in_review' => 'En revisión', 'resolved' => 'Resuelto', 'closed' => 'Cerrado'][$value] ?? ucfirst($value);
|
||||
return '<span class="badge badge-sm" style="background-color:'.$row->status_color.';color:#fff;border-color:transparent;">'.$label.'</span>';
|
||||
})
|
||||
->html(),
|
||||
->sortable()
|
||||
->format(function ($value, $row) {
|
||||
$label = ['open' => 'Abierto', 'in_review' => 'En revisión', 'resolved' => 'Resuelto', 'closed' => 'Cerrado'][$value] ?? ucfirst($value);
|
||||
|
||||
return '<span class="badge badge-sm" style="background-color:'.$row->status_color.';color:#fff;border-color:transparent;">'.$label.'</span>';
|
||||
})
|
||||
->html(),
|
||||
|
||||
Column::make('Asignado a')
|
||||
->label(fn ($row) => $row->assignee
|
||||
? '<span class="text-sm">'.e($row->assignee->name).'</span>'
|
||||
: '<span class="text-base-content/30 text-xs">Sin asignar</span>')
|
||||
->html(),
|
||||
->label(fn ($row) => $row->assignee
|
||||
? '<span class="text-sm">'.e($row->assignee->name).'</span>'
|
||||
: '<span class="text-base-content/30 text-xs">Sin asignar</span>')
|
||||
->html(),
|
||||
|
||||
Column::make('Fecha', 'created_at')
|
||||
->sortable()
|
||||
->format(function ($value, $row) {
|
||||
$html = $row->created_at->format('d/m/Y');
|
||||
if ($row->resolved_at) {
|
||||
$html .= '<div class="text-success text-xs">Res. '.$row->resolved_at->format('d/m/Y').'</div>';
|
||||
}
|
||||
return $html;
|
||||
})
|
||||
->html(),
|
||||
->sortable()
|
||||
->format(function ($value, $row) {
|
||||
$html = $row->created_at->format('d/m/Y');
|
||||
if ($row->resolved_at) {
|
||||
$html .= '<div class="text-success text-xs">Res. '.$row->resolved_at->format('d/m/Y').'</div>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
})
|
||||
->html(),
|
||||
|
||||
Column::make('Acciones')
|
||||
->label(function ($row) {
|
||||
$user = Auth::user();
|
||||
$detail = route('projects.issues.show', [$this->projectId, $row->id]);
|
||||
$edit = route('projects.issues.edit', [$this->projectId, $row->id]);
|
||||
->label(function ($row) {
|
||||
$user = Auth::user();
|
||||
$detail = route('projects.issues.show', [$this->projectId, $row->id]);
|
||||
$edit = route('projects.issues.edit', [$this->projectId, $row->id]);
|
||||
|
||||
$html = '<div class="flex items-center justify-end gap-1 flex-wrap">';
|
||||
$html .= '<a href="'.$detail.'" wire:navigate class="btn btn-xs btn-ghost" title="Abrir detalle">
|
||||
$html = '<div class="flex items-center justify-end gap-1 flex-wrap">';
|
||||
$html .= '<a href="'.$detail.'" wire:navigate class="btn btn-xs btn-ghost" title="Abrir detalle">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>
|
||||
</a>';
|
||||
|
||||
if ($user->can('edit issues')) {
|
||||
$html .= '<a href="'.$edit.'" wire:navigate class="btn btn-xs btn-ghost" title="Editar">
|
||||
if ($user->can('edit issues')) {
|
||||
$html .= '<a href="'.$edit.'" wire:navigate class="btn btn-xs btn-ghost" title="Editar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||
</a>';
|
||||
if (in_array($row->status, ['open', 'in_review'])) {
|
||||
$html .= '<button wire:click="resolve('.$row->id.')" class="btn btn-xs btn-success" title="Marcar como resuelto">
|
||||
if (in_array($row->status, ['open', 'in_review'])) {
|
||||
$html .= '<button wire:click="resolve('.$row->id.')" class="btn btn-xs btn-success" title="Marcar como resuelto">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
|
||||
</button>';
|
||||
}
|
||||
if ($row->status !== 'closed') {
|
||||
$html .= '<button wire:click="close('.$row->id.')" class="btn btn-xs btn-neutral" title="Cerrar">
|
||||
}
|
||||
if ($row->status !== 'closed') {
|
||||
$html .= '<button wire:click="close('.$row->id.')" class="btn btn-xs btn-neutral" title="Cerrar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</button>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($user->can('delete issues')) {
|
||||
$html .= '<button wire:click="deleteIssue('.$row->id.')" wire:confirm="¿Eliminar esta incidencia? Esta acción no se puede deshacer."
|
||||
if ($user->can('delete issues')) {
|
||||
$html .= '<button wire:click="deleteIssue('.$row->id.')" wire:confirm="¿Eliminar esta incidencia? Esta acción no se puede deshacer."
|
||||
class="btn btn-xs btn-error btn-outline" title="Eliminar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
||||
</button>';
|
||||
}
|
||||
}
|
||||
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
})
|
||||
->html(),
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
})
|
||||
->html(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -174,26 +185,26 @@ class IssueTable extends DataTableComponent
|
||||
return [
|
||||
SelectFilter::make('Estado', 'status')
|
||||
->options([
|
||||
'' => 'Estado: todos',
|
||||
'open' => 'Abierto',
|
||||
'' => 'Estado: todos',
|
||||
'open' => 'Abierto',
|
||||
'in_review' => 'En revisión',
|
||||
'resolved' => 'Resuelto',
|
||||
'closed' => 'Cerrado',
|
||||
'resolved' => 'Resuelto',
|
||||
'closed' => 'Cerrado',
|
||||
])
|
||||
->filter(fn (Builder $query, string $value) => $query->where('issues.status', $value)),
|
||||
|
||||
SelectFilter::make('Prioridad', 'priority')
|
||||
->options([
|
||||
'' => 'Prioridad: todas',
|
||||
'' => 'Prioridad: todas',
|
||||
'critical' => 'Crítica',
|
||||
'high' => 'Alta',
|
||||
'medium' => 'Media',
|
||||
'low' => 'Baja',
|
||||
'high' => 'Alta',
|
||||
'medium' => 'Media',
|
||||
'low' => 'Baja',
|
||||
])
|
||||
->filter(fn (Builder $query, string $value) => $query->where('issues.priority', $value)),
|
||||
|
||||
SelectFilter::make('Tipo', 'type')
|
||||
->options(['' => 'Tipo: todos'] + \App\Models\Issue::typeLabels())
|
||||
->options(['' => 'Tipo: todos'] + Issue::typeLabels())
|
||||
->filter(fn (Builder $query, string $value) => $query->where('issues.type', $value)),
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user