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:
@@ -1,12 +1,33 @@
|
||||
<div class="flex items-center gap-1"
|
||||
x-on:locale-changed.window="window.location.reload()">
|
||||
@foreach(['es' => ['flag' => 'es.svg', 'label' => 'ES'], 'en' => ['flag' => 'gb.svg', 'label' => 'EN']] as $code => $lang)
|
||||
<button wire:click="switchLanguage('{{ $code }}')"
|
||||
class="btn btn-xs gap-1.5 {{ $currentLocale === $code ? 'btn-primary' : 'btn-ghost' }}"
|
||||
title="{{ __('Language') }}: {{ __($code === 'en' ? 'English' : 'Spanish') }}">
|
||||
<img src="{{ asset('images/flags/' . $lang['flag']) }}"
|
||||
class="w-4 h-auto rounded-sm border border-base-300" alt="{{ $lang['label'] }}">
|
||||
{{ $lang['label'] }}
|
||||
<x-dropdown align="right" width="48">
|
||||
<x-slot name="trigger">
|
||||
<button class="btn btn-ghost btn-sm gap-2" type="button">
|
||||
<img src="{{ asset('images/flags/' . ($languages[$currentLocale]['flag'] ?? 'gb.svg')) }}"
|
||||
class="w-5 h-4 rounded-sm border border-base-300"
|
||||
alt="{{ $languages[$currentLocale]['name'] ?? 'English' }}">
|
||||
<span class="hidden sm:inline">{{ $languages[$currentLocale]['name'] ?? 'English' }}</span>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="content">
|
||||
<div class="py-1">
|
||||
@foreach($languages as $code => $lang)
|
||||
<button wire:click="switchLanguage('{{ $code }}')"
|
||||
class="flex items-center gap-2 w-full px-3 py-2 text-sm hover:bg-base-200 rounded-md {{ $currentLocale === $code ? 'bg-primary/10 text-primary font-medium' : 'text-base-content' }}"
|
||||
type="button">
|
||||
<img src="{{ asset('images/flags/' . $lang['flag']) }}"
|
||||
class="w-5 h-4 rounded-sm border border-base-300"
|
||||
alt="{{ $lang['name'] }}">
|
||||
<span>{{ $lang['name'] }}</span>
|
||||
@if($currentLocale === $code)
|
||||
<svg class="ml-auto w-4 h-4 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
@endif
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-slot>
|
||||
</x-dropdown>
|
||||
@@ -18,11 +18,11 @@
|
||||
<div tabindex="0" class="dropdown-content z-[50] menu p-0 shadow-lg bg-base-100 rounded-box w-80 mt-1 border border-base-200">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between px-4 py-3 border-b border-base-200">
|
||||
<span class="font-semibold text-base-content">Notificaciones</span>
|
||||
<span class="font-semibold text-base-content">{{ __('Notifications') }}</span>
|
||||
@if($unreadCount > 0)
|
||||
<button wire:click="markAllAsRead"
|
||||
class="text-xs text-primary hover:underline focus:outline-none">
|
||||
Marcar todas
|
||||
{{ __('Mark all as read') }}
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
@@ -48,7 +48,7 @@
|
||||
<!-- Content -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm text-base-content leading-snug">
|
||||
{{ $data['message'] ?? 'Notificación' }}
|
||||
{{ $data['message'] ?? __('Notification') }}
|
||||
</p>
|
||||
<p class="text-xs text-base-content/50 mt-1">
|
||||
{{ $createdAt->diffForHumans() }}
|
||||
@@ -59,7 +59,7 @@
|
||||
@if($isUnread)
|
||||
<button wire:click="markAsRead('{{ $notification['id'] }}')"
|
||||
class="shrink-0 text-base-content/40 hover:text-primary focus:outline-none"
|
||||
title="Marcar como leída">
|
||||
title="{{ __('Mark as read') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
@@ -68,7 +68,7 @@
|
||||
</li>
|
||||
@empty
|
||||
<li class="px-4 py-8 text-center text-sm text-base-content/50">
|
||||
No hay notificaciones
|
||||
{{ __('No notifications') }}
|
||||
</li>
|
||||
@endforelse
|
||||
</ul>
|
||||
@@ -78,7 +78,7 @@
|
||||
<div class="border-t border-base-200 px-4 py-2 text-center">
|
||||
<button wire:click="markAllAsRead"
|
||||
class="btn btn-ghost btn-xs w-full text-primary">
|
||||
Marcar todas como leídas
|
||||
{{ __('Mark all as read') }}
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
Reference in New Issue
Block a user