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:
Javier Braña
2026-08-03 13:44:10 +02:00
parent a65d4b12f2
commit ba614bddbc
62 changed files with 5878 additions and 382 deletions
@@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->foreignId('project_id')->nullable()->constrained()->cascadeOnDelete();
$table->foreignId('phase_id')->nullable()->constrained()->nullOnDelete();
$table->foreignId('parent_task_id')->nullable()->constrained('tasks')->nullOnDelete();
$table->string('title');
$table->text('description')->nullable();
$table->enum('status', ['pending', 'in_progress', 'completed', 'cancelled'])->default('pending');
$table->enum('priority', ['low', 'medium', 'high', 'critical'])->default('medium');
$table->foreignId('created_by')->constrained('users')->cascadeOnDelete();
$table->foreignId('assigned_to')->nullable()->constrained('users')->nullOnDelete();
$table->date('due_date')->nullable();
$table->date('start_date')->nullable();
$table->datetime('completed_at')->nullable();
$table->foreignId('completed_by')->nullable()->constrained('users')->nullOnDelete();
$table->unsignedInteger('estimated_hours')->nullable();
$table->unsignedInteger('actual_hours')->nullable();
$table->integer('order')->default(0);
$table->uuid('uuid')->unique();
$table->datetime('client_updated_at')->nullable();
$table->softDeletes();
$table->timestamps();
$table->index(['project_id', 'status']);
$table->index(['assigned_to', 'status']);
$table->index(['due_date', 'status']);
$table->index(['parent_task_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tasks');
}
};
@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->morphs('commentable'); // commentable_id, commentable_type
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->text('body');
$table->foreignId('parent_id')->nullable()->constrained('comments')->nullOnDelete(); // para respuestas/hilos
$table->timestamps();
$table->index(['commentable_type', 'commentable_id', 'created_at']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('comments');
}
};
+11 -8
View File
@@ -2,23 +2,25 @@
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use App\Models\User;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
class RolesAndPermissionsSeeder extends Seeder
{
public function run()
{
// Reset cached roles and permissions
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
app()[PermissionRegistrar::class]->forgetCachedPermissions();
// Create permissions
$permissions = [
'view projects', 'create projects', 'edit projects', 'delete projects',
'assign users', 'upload layers', 'update progress', 'view reports', 'manage all',
'view inspections', 'create inspections', 'edit inspections', 'delete inspections', 'manage templates',
'view tasks', 'create tasks', 'edit tasks', 'delete tasks', 'assign tasks', 'manage all tasks',
];
foreach ($permissions as $perm) {
Permission::firstOrCreate(['name' => $perm]);
@@ -31,11 +33,12 @@ class RolesAndPermissionsSeeder extends Seeder
$supervisor = Role::firstOrCreate(['name' => 'Supervisor']);
$supervisor->givePermissionTo([
'view projects', 'upload layers', 'update progress',
'view inspections', 'create inspections', 'edit inspections',
'view inspections', 'create inspections', 'edit inspections', 'delete inspections',
'view tasks', 'create tasks', 'edit tasks', 'assign tasks',
]);
$consultor = Role::firstOrCreate(['name' => 'Consultor']);
$consultor->givePermissionTo(['view projects', 'view reports']);
$consultor->givePermissionTo(['view projects', 'view reports', 'view tasks']);
$cliente = Role::firstOrCreate(['name' => 'Cliente']);
$cliente->givePermissionTo(['view projects']);
@@ -45,10 +48,10 @@ class RolesAndPermissionsSeeder extends Seeder
$user = User::firstOrCreate(
['email' => 'admin@email.com'],
[
'name' => 'Admin User',
'name' => 'Admin User',
'password' => bcrypt('password'),
],
);
$user->assignRole('Admin');
}
}
}