feat(tasks): complete task management system with Kanban, Calendar, notifications
- Add Task model with subtasks, priorities, status transitions, dates, hours - Add Comment polymorphic model for tasks/issues/projects - Livewire components: TaskManager (list+filters), TaskForm (modal), TaskDetail, TaskKanban (drag&drop), TaskCalendar (FullCalendar) - TaskPolicy with permissions (view/create/edit/delete/assign/manage all) - Notifications: assigned, status change, overdue, comment added - Daily overdue notification job scheduled - Dashboard widget unifies IssueTask + Task - i18n: en/es/fr/ru (393 keys each) - Routes, navigation, offline sync support - 101 tests passing, Pint compliant on new files
This commit is contained in:
+101
-52
@@ -1,14 +1,43 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Livewire\Reports\ReportsDashboard;
|
||||
use App\Http\Controllers\ProjectController;
|
||||
use App\Http\Controllers\OfflineSyncController;
|
||||
use App\Livewire\Projects\ProjectMap;
|
||||
use App\Livewire\Projects\ProjectList;
|
||||
use App\Livewire\Phases\PhaseProgress;
|
||||
use App\Livewire\Phases\PhaseGantt;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\ProjectController;
|
||||
use App\Http\Controllers\ProjectReportController;
|
||||
use App\Http\Controllers\Reports\ExportController;
|
||||
use App\Livewire\Admin\RoleForm;
|
||||
use App\Livewire\Admin\RolePermissionManager;
|
||||
use App\Livewire\Admin\RoleView;
|
||||
use App\Livewire\Companies\CompanyForm;
|
||||
use App\Livewire\Companies\CompanyManagement;
|
||||
use App\Livewire\Companies\CompanyView;
|
||||
use App\Livewire\Inspections\GlobalTemplateManager;
|
||||
use App\Livewire\Issues\IssueChecklistManager;
|
||||
use App\Livewire\Issues\IssueDetail;
|
||||
use App\Livewire\Issues\IssueForm;
|
||||
use App\Livewire\Issues\IssueManager;
|
||||
use App\Livewire\Layers\LayerManager;
|
||||
use App\Livewire\Phases\PhaseGantt;
|
||||
use App\Livewire\Phases\PhaseProgress;
|
||||
use App\Livewire\Projects\FeatureManager;
|
||||
use App\Livewire\Projects\FeatureTypeManager;
|
||||
use App\Livewire\Projects\ProjectDashboard;
|
||||
use App\Livewire\Projects\ProjectForm;
|
||||
use App\Livewire\Projects\ProjectList;
|
||||
use App\Livewire\Projects\ProjectTemplatesPicker;
|
||||
use App\Livewire\Reports\ReportsDashboard;
|
||||
use App\Livewire\Tasks\TaskCalendar;
|
||||
use App\Livewire\Tasks\TaskDetail;
|
||||
use App\Livewire\Tasks\TaskForm;
|
||||
use App\Livewire\Tasks\TaskKanban;
|
||||
use App\Livewire\Tasks\TaskManager;
|
||||
use App\Livewire\Users\UserForm;
|
||||
use App\Livewire\Users\UserView;
|
||||
use App\Models\Issue;
|
||||
use App\Models\IssueTask;
|
||||
use App\Models\Project;
|
||||
use App\Models\Task;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
@@ -27,84 +56,100 @@ Route::middleware(['auth'])->group(function () {
|
||||
|
||||
// Home: vista ligera (proyectos, tareas e incidencias del usuario + notificaciones)
|
||||
Route::get('/dashboard', function () {
|
||||
$user = \Illuminate\Support\Facades\Auth::user();
|
||||
$user = Auth::user();
|
||||
|
||||
$projects = \App\Models\Project::accessibleBy($user)
|
||||
$projects = Project::accessibleBy($user)
|
||||
->with(['phases' => fn ($q) => $q->select('id', 'project_id', 'progress_percent')])
|
||||
->orderBy('name')->take(8)->get();
|
||||
$projectsCount = \App\Models\Project::accessibleBy($user)->count();
|
||||
$projectsCount = Project::accessibleBy($user)->count();
|
||||
|
||||
$myTasks = \App\Models\IssueTask::where('assigned_to', $user->id)
|
||||
$myTasks = IssueTask::where('assigned_to', $user->id)
|
||||
->where('is_done', false)
|
||||
->with('issue.project')
|
||||
->orderByRaw('due_date IS NULL, due_date ASC')
|
||||
->take(8)->get();
|
||||
$myTasksCount = \App\Models\IssueTask::where('assigned_to', $user->id)->where('is_done', false)->count();
|
||||
|
||||
$myIssues = \App\Models\Issue::where('assigned_to', $user->id)
|
||||
$myTasksFromTasks = Task::where('assigned_to', $user->id)
|
||||
->where('status', '!=', 'completed')
|
||||
->with('project')
|
||||
->orderByRaw('due_date IS NULL, due_date ASC')
|
||||
->take(8)->get();
|
||||
|
||||
$myTasksCount = IssueTask::where('assigned_to', $user->id)->where('is_done', false)->count()
|
||||
+ Task::where('assigned_to', $user->id)->where('status', '!=', 'completed')->count();
|
||||
|
||||
$myIssues = Issue::where('assigned_to', $user->id)
|
||||
->whereIn('status', ['open', 'in_review'])
|
||||
->with('project')
|
||||
->latest()->take(6)->get();
|
||||
$myIssuesCount = \App\Models\Issue::where('assigned_to', $user->id)->whereIn('status', ['open', 'in_review'])->count();
|
||||
$myIssuesCount = Issue::where('assigned_to', $user->id)->whereIn('status', ['open', 'in_review'])->count();
|
||||
|
||||
$notifications = $user->notifications()->latest()->take(6)->get();
|
||||
$unreadCount = $user->unreadNotifications()->count();
|
||||
$unreadCount = $user->unreadNotifications()->count();
|
||||
|
||||
return view('dashboard', compact(
|
||||
'user', 'projects', 'projectsCount', 'myTasks', 'myTasksCount',
|
||||
'user', 'projects', 'projectsCount', 'myTasks', 'myTasksFromTasks', 'myTasksCount',
|
||||
'myIssues', 'myIssuesCount', 'notifications', 'unreadCount'
|
||||
));
|
||||
})->name('dashboard');
|
||||
|
||||
Route::get('/reports/dashboard', ReportsDashboard::class)->name('reports.dashboard');
|
||||
|
||||
Route::get('/reports/dashboard', ReportsDashboard::class)->name('reports.dashboard');
|
||||
Route::prefix('reports')->name('reports.')->group(function () {
|
||||
Route::get('export/projects', [App\Http\Controllers\Reports\ExportController::class, 'exportProjects'])->name('export.projects');
|
||||
Route::get('export/phases', [App\Http\Controllers\Reports\ExportController::class, 'exportPhases'])->name('export.phases');
|
||||
Route::get('export/inspections', [App\Http\Controllers\Reports\ExportController::class, 'exportInspections'])->name('export.inspections');
|
||||
Route::get('export/projects', [ExportController::class, 'exportProjects'])->name('export.projects');
|
||||
Route::get('export/phases', [ExportController::class, 'exportPhases'])->name('export.phases');
|
||||
Route::get('export/inspections', [ExportController::class, 'exportInspections'])->name('export.inspections');
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Gestión de proyectos
|
||||
// ------------------------------------------------------------
|
||||
// Create/Edit handled by unified Livewire component
|
||||
Route::get('/projects/create', \App\Livewire\Projects\ProjectForm::class)->name('projects.create');
|
||||
Route::get('/projects/{project}/edit', \App\Livewire\Projects\ProjectForm::class)->name('projects.edit');
|
||||
Route::get('/projects/create', ProjectForm::class)->name('projects.create');
|
||||
Route::get('/projects/{project}/edit', ProjectForm::class)->name('projects.edit');
|
||||
Route::resource('projects', ProjectController::class)->except(['create', 'edit']);
|
||||
// Ruta personalizada para ver el mapa de un proyecto específico
|
||||
Route::get('/projects/{project}/map', [ProjectController::class, 'map'])->name('projects.map');
|
||||
// Ruta para que el componente Livewire muestre/gestione el progreso de una fase
|
||||
Route::get('/phases/{phase}/progress', PhaseProgress::class)->name('phases.progress');
|
||||
Route::get('/projects-list', ProjectList::class)->name('projects.list');
|
||||
|
||||
|
||||
// Ruta para templates
|
||||
// Plantillas globales (catálogo único)
|
||||
Route::get('/inspection-templates', \App\Livewire\Inspections\GlobalTemplateManager::class)
|
||||
Route::get('/inspection-templates', GlobalTemplateManager::class)
|
||||
->middleware('can:manage templates')->name('inspection-templates');
|
||||
|
||||
// Plantillas asignadas a un proyecto (picker sobre el catálogo global)
|
||||
Route::get('/projects/{project}/templates', \App\Livewire\Projects\ProjectTemplatesPicker::class)
|
||||
Route::get('/projects/{project}/templates', ProjectTemplatesPicker::class)
|
||||
->middleware('can:edit projects')->name('projects.templates');
|
||||
|
||||
|
||||
// Rutas para el LayerManager:
|
||||
Route::get('/projects/{project}/phases/{phase}/layers/manage', \App\Livewire\Layers\LayerManager::class)->name('layers.manage');
|
||||
Route::get('/projects/{project}/phases/{phase}/layers/manage', LayerManager::class)->name('layers.manage');
|
||||
|
||||
// Elementos del proyecto y catálogo de tipos de elemento
|
||||
Route::get('/projects/{project}/features', \App\Livewire\Projects\FeatureManager::class)->middleware('can:edit layers')->name('projects.features');
|
||||
Route::get('/feature-types', \App\Livewire\Projects\FeatureTypeManager::class)->middleware('can:edit layers')->name('feature-types');
|
||||
Route::get('/projects/{project}/features', FeatureManager::class)->middleware('can:edit layers')->name('projects.features');
|
||||
Route::get('/feature-types', FeatureTypeManager::class)->middleware('can:edit layers')->name('feature-types');
|
||||
|
||||
// Cronograma Gantt y reporte del proyecto
|
||||
Route::get('/projects/{project}/gantt', PhaseGantt::class)->name('projects.gantt');
|
||||
Route::get('/projects/{project}/report', [ProjectReportController::class, 'show'])->name('projects.report');
|
||||
|
||||
// Issues del proyecto
|
||||
Route::get('/projects/{project}/issues', \App\Livewire\Issues\IssueManager::class)->name('projects.issues');
|
||||
Route::get('/projects/{project}/issues/create', \App\Livewire\Issues\IssueForm::class)->name('projects.issues.create');
|
||||
Route::get('/projects/{project}/issues/checklists', \App\Livewire\Issues\IssueChecklistManager::class)->name('projects.issues.checklists');
|
||||
Route::get('/projects/{project}/issues/{issue}', \App\Livewire\Issues\IssueDetail::class)->name('projects.issues.show');
|
||||
Route::get('/projects/{project}/issues/{issue}/edit', \App\Livewire\Issues\IssueForm::class)->name('projects.issues.edit');
|
||||
Route::get('/projects/{project}/issues', IssueManager::class)->name('projects.issues');
|
||||
Route::get('/projects/{project}/issues/create', IssueForm::class)->name('projects.issues.create');
|
||||
Route::get('/projects/{project}/issues/checklists', IssueChecklistManager::class)->name('projects.issues.checklists');
|
||||
Route::get('/projects/{project}/issues/{issue}', IssueDetail::class)->name('projects.issues.show');
|
||||
Route::get('/projects/{project}/issues/{issue}/edit', IssueForm::class)->name('projects.issues.edit');
|
||||
|
||||
// Tasks del proyecto
|
||||
Route::get('/projects/{project}/tasks', TaskManager::class)->name('projects.tasks');
|
||||
Route::get('/projects/{project}/tasks/create', TaskForm::class)->name('projects.tasks.create');
|
||||
Route::get('/projects/{project}/tasks/{task}', TaskDetail::class)->name('projects.tasks.show');
|
||||
Route::get('/projects/{project}/tasks/{task}/edit', TaskForm::class)->name('projects.tasks.edit');
|
||||
Route::get('/projects/{project}/tasks/kanban', TaskKanban::class)->name('projects.tasks.kanban');
|
||||
Route::get('/projects/{project}/tasks/calendar', TaskCalendar::class)->name('projects.tasks.calendar');
|
||||
|
||||
// Dashboard por proyecto
|
||||
Route::get('/projects/{project}/dashboard', \App\Livewire\Projects\ProjectDashboard::class)->name('projects.dashboard');
|
||||
Route::get('/projects/{project}/dashboard', ProjectDashboard::class)->name('projects.dashboard');
|
||||
|
||||
// Cliente: portal cliente
|
||||
Route::middleware(['auth', 'role:client'])->prefix('client')->name('client.')->group(function () {
|
||||
@@ -115,25 +160,29 @@ Route::get('/reports/dashboard', ReportsDashboard::class)->name('reports.dashboa
|
||||
|
||||
// Admin: gestión de usuarios y roles (cada ruta protegida por su permiso)
|
||||
Route::prefix('admin')->name('admin.')->group(function () {
|
||||
Route::get('/users', function () { return view('admin.users'); })->middleware('can:view users')->name('users');
|
||||
Route::get('/users/create', \App\Livewire\Users\UserForm::class)->middleware('can:create users')->name('users.create');
|
||||
Route::get('/users/{user}', \App\Livewire\Users\UserView::class)->middleware('can:view users')->name('users.show');
|
||||
Route::get('/users/{user}/edit', \App\Livewire\Users\UserForm::class)->middleware('can:edit users')->name('users.edit');
|
||||
Route::get('/roles', function () { return view('admin.roles'); })->middleware('can:manage roles')->name('roles');
|
||||
Route::get('/roles/create', \App\Livewire\Admin\RoleForm::class)->middleware('can:manage roles')->name('roles.create');
|
||||
Route::get('/roles/{role}/edit', \App\Livewire\Admin\RoleForm::class)->middleware('can:manage roles')->name('roles.edit');
|
||||
Route::get('/roles/{role}', \App\Livewire\Admin\RoleView::class)->middleware('can:manage roles')->name('roles.show');
|
||||
Route::get('/permissions', \App\Livewire\Admin\RolePermissionManager::class)->middleware('can:manage roles')->name('permissions');
|
||||
Route::get('/users', function () {
|
||||
return view('admin.users');
|
||||
})->middleware('can:view users')->name('users');
|
||||
Route::get('/users/create', UserForm::class)->middleware('can:create users')->name('users.create');
|
||||
Route::get('/users/{user}', UserView::class)->middleware('can:view users')->name('users.show');
|
||||
Route::get('/users/{user}/edit', UserForm::class)->middleware('can:edit users')->name('users.edit');
|
||||
Route::get('/roles', function () {
|
||||
return view('admin.roles');
|
||||
})->middleware('can:manage roles')->name('roles');
|
||||
Route::get('/roles/create', RoleForm::class)->middleware('can:manage roles')->name('roles.create');
|
||||
Route::get('/roles/{role}/edit', RoleForm::class)->middleware('can:manage roles')->name('roles.edit');
|
||||
Route::get('/roles/{role}', RoleView::class)->middleware('can:manage roles')->name('roles.show');
|
||||
Route::get('/permissions', RolePermissionManager::class)->middleware('can:manage roles')->name('permissions');
|
||||
});
|
||||
|
||||
// Gestor de medios
|
||||
Route::get('/projects/{project}/media', function (\App\Models\Project $project) {
|
||||
Route::get('/projects/{project}/media', function (Project $project) {
|
||||
return view('projects.media', compact('project'));
|
||||
})->name('projects.media');
|
||||
Route::get('/companies', \App\Livewire\Companies\CompanyManagement::class)->name('companies.manage');
|
||||
Route::get('/companies/create', \App\Livewire\Companies\CompanyForm::class)->name('companies.create');
|
||||
Route::get('/companies/{company}', \App\Livewire\Companies\CompanyView::class)->name('companies.show');
|
||||
Route::get('/companies/{company}/edit', \App\Livewire\Companies\CompanyForm::class)->name('companies.edit');
|
||||
Route::get('/companies', CompanyManagement::class)->name('companies.manage');
|
||||
Route::get('/companies/create', CompanyForm::class)->name('companies.create');
|
||||
Route::get('/companies/{company}', CompanyView::class)->name('companies.show');
|
||||
Route::get('/companies/{company}/edit', CompanyForm::class)->name('companies.edit');
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Sincronización offline (para trabajadores en campo)
|
||||
@@ -145,7 +194,7 @@ Route::get('/reports/dashboard', ReportsDashboard::class)->name('reports.dashboa
|
||||
// Perfil de usuario (proporcionado por Laravel Breeze)
|
||||
// ------------------------------------------------------------
|
||||
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile');
|
||||
//Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||
// Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
||||
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
|
||||
// Logout se gestiona vía la acción Volt en el componente de navegación
|
||||
@@ -153,4 +202,4 @@ Route::get('/reports/dashboard', ReportsDashboard::class)->name('reports.dashboa
|
||||
});
|
||||
|
||||
// Incluir rutas de autenticación (login, registro, recuperación de contraseña, logout)
|
||||
require __DIR__ . '/auth.php';
|
||||
require __DIR__.'/auth.php';
|
||||
|
||||
Reference in New Issue
Block a user