From ba614bddbc5f1e1efc341274f93029cfe77d5cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Bra=C3=B1a?= Date: Mon, 3 Aug 2026 13:44:10 +0200 Subject: [PATCH] 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 --- .claude/settings.local.json | 112 +++++ TASK_FEATURE_PLAN.md | 346 ++++++++++++++ .../Controllers/OfflineSyncController.php | 26 +- app/Http/Middleware/SetLocale.php | 9 +- app/Jobs/NotifyOverdueTasks.php | 35 ++ app/Livewire/Common/LanguageSwitcher.php | 19 +- app/Livewire/Projects/InspectionTable.php | 35 +- app/Livewire/Projects/ProjectMap.php | 62 +++ app/Livewire/Tasks/TaskCalendar.php | 167 +++++++ app/Livewire/Tasks/TaskDetail.php | 177 +++++++ app/Livewire/Tasks/TaskForm.php | 206 +++++++++ app/Livewire/Tasks/TaskKanban.php | 194 ++++++++ app/Livewire/Tasks/TaskManager.php | 186 ++++++++ app/Livewire/Users/UserForm.php | 2 + app/Models/Comment.php | 101 ++++ app/Models/Inspection.php | 8 + app/Models/Phase.php | 61 ++- app/Models/Project.php | 16 +- app/Models/Task.php | 326 +++++++++++++ app/Models/User.php | 27 +- .../InspectionDeletedNotification.php | 32 ++ .../InspectionUpdatedNotification.php | 33 ++ .../TaskAssignedNotification.php | 30 ++ app/Notifications/TaskCommentNotification.php | 31 ++ app/Notifications/TaskOverdueNotification.php | 30 ++ .../TaskStatusChangedNotification.php | 45 ++ app/Policies/TaskPolicy.php | 122 +++++ app/Providers/AppServiceProvider.php | 7 +- .../2026_07_31_114835_create_tasks_table.php | 51 +++ ...026_07_31_114906_create_comments_table.php | 33 ++ .../seeders/RolesAndPermissionsSeeder.php | 19 +- lang/en.json | 33 +- lang/es.json | 33 +- lang/fr.json | 430 ++++++++++++++++++ lang/fr/auth.php | 20 + lang/fr/pagination.php | 19 + lang/fr/passwords.php | 22 + lang/fr/validation.php | 194 ++++++++ lang/ru.json | 430 ++++++++++++++++++ lang/ru/auth.php | 20 + lang/ru/pagination.php | 19 + lang/ru/passwords.php | 22 + lang/ru/validation.php | 194 ++++++++ public/images/flags/fr.svg | 5 + public/images/flags/ru.svg | 5 + resources/views/client/dashboard.blade.php | 60 +-- resources/views/dashboard.blade.php | 50 +- .../common/language-switcher.blade.php | 43 +- .../common/notification-bell.blade.php | 12 +- .../livewire/companies/company-form.blade.php | 83 ++-- .../livewire/companies/company-view.blade.php | 324 ++++++------- .../livewire/layout/navigation.blade.php | 8 + .../project-map-inspections-tab.blade.php | 10 + .../livewire/projects/project-map.blade.php | 10 + .../livewire/tasks/task-calendar.blade.php | 229 ++++++++++ .../livewire/tasks/task-detail.blade.php | 319 +++++++++++++ .../views/livewire/tasks/task-form.blade.php | 177 +++++++ .../livewire/tasks/task-kanban.blade.php | 243 ++++++++++ .../livewire/tasks/task-manager.blade.php | 258 +++++++++++ routes/console.php | 4 + routes/web.php | 153 ++++--- tests/Feature/InspectionFormTest.php | 283 +++++++++++- 62 files changed, 5878 insertions(+), 382 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 TASK_FEATURE_PLAN.md create mode 100644 app/Jobs/NotifyOverdueTasks.php create mode 100644 app/Livewire/Tasks/TaskCalendar.php create mode 100644 app/Livewire/Tasks/TaskDetail.php create mode 100644 app/Livewire/Tasks/TaskForm.php create mode 100644 app/Livewire/Tasks/TaskKanban.php create mode 100644 app/Livewire/Tasks/TaskManager.php create mode 100644 app/Models/Comment.php create mode 100644 app/Models/Task.php create mode 100644 app/Notifications/InspectionDeletedNotification.php create mode 100644 app/Notifications/InspectionUpdatedNotification.php create mode 100644 app/Notifications/TaskAssignedNotification.php create mode 100644 app/Notifications/TaskCommentNotification.php create mode 100644 app/Notifications/TaskOverdueNotification.php create mode 100644 app/Notifications/TaskStatusChangedNotification.php create mode 100644 app/Policies/TaskPolicy.php create mode 100644 database/migrations/2026_07_31_114835_create_tasks_table.php create mode 100644 database/migrations/2026_07_31_114906_create_comments_table.php create mode 100644 lang/fr.json create mode 100644 lang/fr/auth.php create mode 100644 lang/fr/pagination.php create mode 100644 lang/fr/passwords.php create mode 100644 lang/fr/validation.php create mode 100644 lang/ru.json create mode 100644 lang/ru/auth.php create mode 100644 lang/ru/pagination.php create mode 100644 lang/ru/passwords.php create mode 100644 lang/ru/validation.php create mode 100644 public/images/flags/fr.svg create mode 100644 public/images/flags/ru.svg create mode 100644 resources/views/livewire/tasks/task-calendar.blade.php create mode 100644 resources/views/livewire/tasks/task-detail.blade.php create mode 100644 resources/views/livewire/tasks/task-form.blade.php create mode 100644 resources/views/livewire/tasks/task-kanban.blade.php create mode 100644 resources/views/livewire/tasks/task-manager.blade.php diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..1c9b4c7 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,112 @@ +{ + "permissions": { + "allow": [ + "Bash(grep -n 'protected $fillable\\\\|protected $guarded' 'C:\\\\xampp\\\\htdocs\\\\construprogress\\\\app\\\\Models\\\\Inspection.php')", + "Bash(findstr /E \"\\\\.php$\")", + "Bash(Get-ChildItem \"C:\\\\xampp\\\\htdocs\\\\construprogress\\\\app\\\\Models\" -Filter \"*.php\")", + "Bash(Select-Object -ExpandProperty Name)", + "mcp__visualize__read_me", + "mcp__visualize__show_widget", + "Bash(php -l C:\\\\xampp\\\\htdocs\\\\construprogress\\\\app\\\\Livewire\\\\ProjectMap.php)", + "Bash(php -l C:\\\\xampp\\\\htdocs\\\\construprogress\\\\app\\\\Livewire\\\\LayerManager.php)", + "Bash(php -l C:\\\\xampp\\\\htdocs\\\\construprogress\\\\app\\\\Models\\\\Project.php)", + "Bash(cd /d C:\\\\xampp\\\\htdocs\\\\construprogress)", + "Bash(php -l app/Livewire/ProjectMap.php)", + "Bash(php -l app/Livewire/LayerManager.php)", + "Bash(php -l app/Models/Project.php)", + "PowerShell(Start-Process \"C:\\\\xampp\\\\xampp-control.exe\" -WindowStyle Hidden)", + "PowerShell(& \"C:\\\\xampp\\\\mysql\\\\bin\\\\mysqladmin.exe\" -u root ping 2>&1)", + "Bash(php artisan *)", + "Bash(php -l app/Models/Issue.php)", + "Bash(php -l app/Livewire/IssueManager.php)", + "Bash(php -l app/Notifications/InspectionCompletedNotification.php)", + "Bash(php -l app/Notifications/IssueReportedNotification.php)", + "Bash(php -l app/Notifications/FeatureCompletedNotification.php)", + "Bash(php -l app/Livewire/NotificationBell.php)", + "Bash(php -l app/Traits/LogsActivity.php)", + "Bash(php -l database/migrations/2026_06_16_100001_create_notifications_table.php)", + "Bash(php -l database/migrations/2026_06_16_100002_create_activity_logs_table.php)", + "Bash(php -l app/Livewire/PhaseGantt.php)", + "Bash(php -l app/Http/Controllers/ProjectReportController.php)", + "Bash(php -l routes/web.php)", + "PowerShell(Set-Location C:\\\\xampp\\\\htdocs\\\\construprogress; php artisan route:list --name=projects 2>&1 | Where-Object { $_ -notmatch \"Warning|openssl\" })", + "Bash(mkdir -p \"C:/Users/JavierBraña/.claude/projects/C--xampp-htdocs-construprogress/memory\")", + "Bash(grep -v \"//\\\\| - +

- Órdenes de Cambio Pendientes + {{ __('Pending Change Orders') }}

- +

- Orden de cambio #124 + {{ __('Change order #:id', ['id' => '124']) }}

- Solicitud de ampliación de zona de almacenamiento + {{ __('Storage area expansion request') }}

- +

- Orden de cambio #125 + {{ __('Change order #:id', ['id' => '125']) }}

- Cambio de material en acabados interiores + {{ __('Interior finish material change') }}

diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 62f4ac3..2e1961c 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -10,8 +10,8 @@ {{-- Saludo --}}
-

Hola, {{ $user?->first_name ?? auth()->user()->name }}

-

Un resumen rápido de lo tuyo.

+

{{ __('Hello, :name', ['name' => $user?->first_name ?? auth()->user()->name]) }}

+

{{ __('A quick summary of your items.') }}

@@ -21,10 +21,10 @@

- Mis proyectos + {{ __('My Projects') }} {{ $projectsCount }}

- Ver todos + {{ __('View all') }}
@forelse($projects as $project) @php $pct = round($project->phases->avg('progress_percent') ?? 0); @endphp @@ -37,7 +37,7 @@ {{ $pct }}% @empty -

No tienes proyectos asignados.

+

{{ __('You have no assigned projects.') }}

@endforelse
@@ -46,7 +46,7 @@

- Mis tareas + {{ __('My Tasks') }} {{ $myTasksCount }}

@forelse($myTasks as $task) @@ -55,9 +55,7 @@ class="flex items-center gap-2 py-2 border-b border-base-200 last:border-0 hover:bg-base-200/50 rounded px-1">
{{ $task->title }}
-
- {{ $task->issue?->project?->name ?? '—' }} -
+
{{ $task->issue?->project?->name ?? '—' }}
@if($task->due_date) @@ -66,7 +64,25 @@ @endif @empty -

No tienes tareas pendientes. 🎉

+ @if($myTasksFromTasks->isNotEmpty()) + @foreach($myTasksFromTasks as $task) + +
+
{{ $task->title }}
+
{{ $task->project?->name ?? '—' }}
+
+ @if($task->due_date) + + {{ $task->due_date->format('d/m/Y') }} + + @endif +
+ @endforeach + @else +

{{ __('You have no pending tasks. 🎉') }}

+ @endif @endforelse
@@ -75,12 +91,12 @@

- Incidencias asignadas + {{ __('Assigned Issues') }} {{ $myIssuesCount }}

@forelse($myIssues as $issue) @php - $sLabel = ['open'=>'Abierto','in_review'=>'En revisión'][$issue->status] ?? $issue->status; + $sLabel = ['open'=>__('Open'),'in_review'=>__('In Review')][$issue->status] ?? $issue->status; @endphp @@ -92,7 +108,7 @@ {{ $sLabel }} @empty -

No tienes incidencias asignadas.

+

{{ __('You have no assigned issues.') }}

@endforelse
@@ -101,7 +117,7 @@

- Notificaciones + {{ __('Notifications') }} @if($unreadCount){{ $unreadCount }}@endif

@forelse($notifications as $note) @@ -115,12 +131,12 @@ class="flex items-start gap-2 py-2 border-b border-base-200 last:border-0 hover:bg-base-200/50 rounded px-1 {{ is_null($note->read_at) ? 'font-medium' : '' }}">
-
{{ $d['message'] ?? 'Notificación' }}
+
{{ $d['message'] ?? __('Notification') }}
{{ $note->created_at->diffForHumans() }}
@empty -

Sin notificaciones.

+

{{ __('No notifications.') }}

@endforelse
@@ -128,4 +144,4 @@
- + \ No newline at end of file diff --git a/resources/views/livewire/common/language-switcher.blade.php b/resources/views/livewire/common/language-switcher.blade.php index 225b6fc..f00f506 100644 --- a/resources/views/livewire/common/language-switcher.blade.php +++ b/resources/views/livewire/common/language-switcher.blade.php @@ -1,12 +1,33 @@ -
- @foreach(['es' => ['flag' => 'es.svg', 'label' => 'ES'], 'en' => ['flag' => 'gb.svg', 'label' => 'EN']] as $code => $lang) - - @endforeach -
\ No newline at end of file + + + +
+ @foreach($languages as $code => $lang) + + @endforeach +
+
+ \ No newline at end of file diff --git a/resources/views/livewire/common/notification-bell.blade.php b/resources/views/livewire/common/notification-bell.blade.php index b9ca28f..efffeb4 100644 --- a/resources/views/livewire/common/notification-bell.blade.php +++ b/resources/views/livewire/common/notification-bell.blade.php @@ -18,11 +18,11 @@ @@ -37,71 +37,74 @@ {{-- ── Sección: Identificación ──────────────────────────── --}}

- Identificación + {{ __('Identification') }}

+ placeholder="{{ __('E.g.: Acme Construct, S.L.') }}" /> @error('name')

{{ $message }}

@enderror
- + placeholder="{{ __('E.g.: Acme Construct') }}" />
+ placeholder="{{ __('E.g.: B12345678') }}" /> @error('tax_id')

{{ $message }}

@enderror
+ @error('type')

{{ $message }}

@enderror
- + + + + + @error('status')

{{ $message }}

@enderror
@@ -111,24 +114,24 @@ {{-- ── Sección: Contacto ─────────────────────────────────── --}}

- Contacto + {{ __('Contact') }}

+ class="textarea textarea-bordered w-full" + placeholder="{{ __('Street, number, city, ZIP, country') }}">