Files
construprogress/app/Livewire/IssueManager.php
T
javier 3f240e5277 feat(issues): incidencias enriquecidas (tareas/comentarios/fotos/verificación) + tabla Rappasoft + logo
Web:
- IssueTask + IssueComment (modelos, migraciones, soft-deletes, campos de sync).
  Issue gana tasks()/comments() y accessor de % de avance derivado de tareas.
- IssueDetail (página): checklist con asignado/fecha límite/progreso, hilo de
  comentarios con foto por comentario, galería de fotos de la incidencia y flujo
  de verificación open→in_review→resolved/closed (+reabrir) con notas.
- Creación/edición en páginas propias (IssueForm), sin modal; al guardar redirige
  al detalle. Rutas projects.issues.create/edit/show.
- Listado con tabla Rappasoft (IssueTable): filtros por estado/prioridad, búsqueda,
  barra de progreso y acciones por fila gateadas por permisos; IssueManager queda
  como contenedor (cabecera + stats) que embebe la tabla.
- Seguridad: pertenencia al proyecto + permisos por acción (view/create/edit/delete
  issues, upload/delete media) en todos los componentes.

API móvil (offline):
- /sync: issue_task.create/update y issue_comment.create (idempotente, LWW).
- /media: parent_entity issue_task / issue_comment.
- bundle + tombstones incluyen issue_tasks / issue_comments.
- openapi.yaml + MOBILE_SYNC_PROTOCOL.md actualizados.

Tests: MobileApiTest 23 passing (+5); IssuesTablePageTest (3) smoke de la tabla.

Branding: logo RTE International — MAI Group (public/images/logo-rte.png) en login
y navegación; application-logo pasa de SVG por defecto a <img>.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 12:12:39 +02:00

55 lines
1.6 KiB
PHP

<?php
namespace App\Livewire;
use Livewire\Component;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Illuminate\Support\Facades\Auth;
use App\Models\Project;
use App\Models\Issue;
#[Layout('layouts.app')]
class IssueManager extends Component
{
public Project $project;
public function mount(Project $project)
{
$this->project = $project;
abort_unless($this->canAccessProject() && Auth::user()->can('view issues'), 403);
}
/** The current user must be a project member (or super-admin) to touch issues. */
private function canAccessProject(): bool
{
$user = Auth::user();
return $user->can('manage all')
|| $this->project->users()->where('user_id', $user->id)->exists();
}
/** Re-render the stats bar after the embedded table changes an issue. */
#[On('issuesChanged')]
public function refreshStats(): void
{
// No state to mutate — the listener simply triggers a re-render so the
// stat counters recompute from the database in render().
}
public function render()
{
$counts = Issue::where('project_id', $this->project->id)
->selectRaw('status, count(*) as c')
->groupBy('status')
->pluck('c', 'status');
return view('livewire.issues.issue-manager', [
'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(),
]);
}
}