From ec6d7e2a80b7850f1447afc35960500b36cd890f Mon Sep 17 00:00:00 2001 From: Alexander Gabriel Date: Thu, 2 Jul 2026 19:34:12 +0000 Subject: [PATCH] reminder for todos --- .../Commands/SendTodoFollowUpReminders.php | 37 ++++++++++++++++++ app/Filament/Actions/SetReminderAction.php | 38 +++++++++++++++++++ .../Resources/Todos/Pages/EditTodo.php | 2 + .../Resources/Todos/Schemas/TodoForm.php | 2 +- .../Resources/Todos/Tables/TodosTable.php | 29 +++++++++++--- app/Models/Todo.php | 31 ++++++++++----- app/Observers/TodoObserver.php | 27 +++++++------ bootstrap/app.php | 4 ++ ...d_follow_up_notified_at_to_todos_table.php | 28 ++++++++++++++ 9 files changed, 171 insertions(+), 27 deletions(-) create mode 100644 app/Console/Commands/SendTodoFollowUpReminders.php create mode 100644 app/Filament/Actions/SetReminderAction.php create mode 100644 database/migrations/2026_07_02_192737_add_follow_up_notified_at_to_todos_table.php diff --git a/app/Console/Commands/SendTodoFollowUpReminders.php b/app/Console/Commands/SendTodoFollowUpReminders.php new file mode 100644 index 0000000..2ab4df7 --- /dev/null +++ b/app/Console/Commands/SendTodoFollowUpReminders.php @@ -0,0 +1,37 @@ +whereNull('done_date') + ->whereNull('follow_up_notified_at') + ->whereNotNull('follow_up') + ->whereDate('follow_up', '<=', today()) + ->get(); + + foreach ($todos as $todo) { + foreach ($todo->getassignetusers() as $user) { + Mail::to($user)->send(new TodoMail($todo, 'Todo Wiedervorlage', 'Die Wiedervorlage für folgende Todo ist erreicht:')); + } + + $todo->follow_up_notified_at = now(); + $todo->saveQuietly(); + } + } +} diff --git a/app/Filament/Actions/SetReminderAction.php b/app/Filament/Actions/SetReminderAction.php new file mode 100644 index 0000000..553dfc6 --- /dev/null +++ b/app/Filament/Actions/SetReminderAction.php @@ -0,0 +1,38 @@ +label('Erinnerung') + ->tooltip('Wiedervorlage setzen') + ->icon(Heroicon::BellSnooze) + ->iconButton() + ->visible(function (Model $record) { + return $record->is_owner_or_assigned(); + }) + ->actions([ + Action::make('reminderIn1Day') + ->label('in 1 Tag') + ->action(fn (Model $record) => $record->update(['follow_up' => now()->addDay()])), + Action::make('reminderIn3Days') + ->label('in 3 Tagen') + ->action(fn (Model $record) => $record->update(['follow_up' => now()->addDays(3)])), + Action::make('reminderIn1Week') + ->label('in 1 Woche') + ->action(fn (Model $record) => $record->update(['follow_up' => now()->addWeek()])), + Action::make('reminderIn1Month') + ->label('in 1 Monat') + ->action(fn (Model $record) => $record->update(['follow_up' => now()->addMonth()])), + ]); + } +} diff --git a/app/Filament/Resources/Todos/Pages/EditTodo.php b/app/Filament/Resources/Todos/Pages/EditTodo.php index a4f82df..54bbc3e 100644 --- a/app/Filament/Resources/Todos/Pages/EditTodo.php +++ b/app/Filament/Resources/Todos/Pages/EditTodo.php @@ -4,6 +4,7 @@ namespace App\Filament\Resources\Todos\Pages; use App\Filament\Actions\DoneTodoAction; use App\Filament\Actions\ReopenTodoAction; +use App\Filament\Actions\SetReminderAction; use App\Filament\Resources\Todos\TodoResource; use Filament\Actions\DeleteAction; use Filament\Resources\Pages\EditRecord; @@ -18,6 +19,7 @@ class EditTodo extends EditRecord return [ DoneTodoAction::make('done'), ReopenTodoAction::make('reopen'), + SetReminderAction::make([]), DeleteAction::make()->iconButton()->icon(Heroicon::Trash), ]; } diff --git a/app/Filament/Resources/Todos/Schemas/TodoForm.php b/app/Filament/Resources/Todos/Schemas/TodoForm.php index cf7606f..0cd9c8c 100644 --- a/app/Filament/Resources/Todos/Schemas/TodoForm.php +++ b/app/Filament/Resources/Todos/Schemas/TodoForm.php @@ -39,7 +39,7 @@ class TodoForm Toggle::make('review')->label('Review')->hint('Todo geht nach Erledigung noch mal zum Ersteller'), ]), - // DatePicker::make('follow_up')->label('Wiedervorlage/Erinnerung'), + DatePicker::make('follow_up')->label('Wiedervorlage/Erinnerung')->hiddenOn(['create']), DatePicker::make('done_date')->label('Erledigt am')->hiddenOn(['create']), ]); } diff --git a/app/Filament/Resources/Todos/Tables/TodosTable.php b/app/Filament/Resources/Todos/Tables/TodosTable.php index 7915aa5..36f6916 100644 --- a/app/Filament/Resources/Todos/Tables/TodosTable.php +++ b/app/Filament/Resources/Todos/Tables/TodosTable.php @@ -4,14 +4,13 @@ namespace App\Filament\Resources\Todos\Tables; use App\Filament\Actions\DoneTodoAction; use App\Filament\Actions\ReopenTodoAction; +use App\Filament\Actions\SetReminderAction; use App\Models\Group; use App\Models\User; -use Filament\Actions\Action; use Filament\Actions\BulkActionGroup; use Filament\Actions\DeleteAction; use Filament\Actions\DeleteBulkAction; use Filament\Actions\EditAction; -use Filament\Support\Icons\Heroicon; use Filament\Tables\Columns\IconColumn; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Filters\Filter; @@ -52,10 +51,10 @@ class TodosTable ->label('Erledigt am') ->date() ->sortable(), - // TextColumn::make('follow_up') - // ->label('Wiedervorlage') - // ->date() - // ->sortable(), + TextColumn::make('follow_up') + ->label('Wiedervorlage') + ->date() + ->sortable(), IconColumn::make('review') ->label('Review') ->boolean(), @@ -66,6 +65,7 @@ class TodosTable ->label('nicht erledigt') ->query(function (Builder $query) { $query->whereNull('done_date'); + return $query; }), Filter::make('mine')->label('von mir oder für mich') @@ -76,8 +76,24 @@ class TodosTable ->orWhere('user_id', filament()->auth()->user()->id) ->orWhere('todoable_type', User::class)->where('todoable_id', filament()->auth()->user()->id) ->orwhere('todoable_type', Group::class)->whereIn('todoable_id', User::find(filament()->auth()->user()->id)->groups()->get()->pluck('id')); + return $query; }); + + return $query; + }), + Filter::make('keineZukuenftigeWiedervorlage') + ->label('ohne zukünftige Wiedervorlage') + ->default() + ->query(function (Builder $query) { + $query->where(function (Builder $query) { + $query + ->whereNull('follow_up') + ->orWhereDate('follow_up', '<=', today()); + + return $query; + }); + return $query; }), @@ -85,6 +101,7 @@ class TodosTable ->recordActions([ DoneTodoAction::make('done'), ReopenTodoAction::make('reopen'), + SetReminderAction::make([]), EditAction::make()->iconButton(), DeleteAction::make()->iconButton() ->visible(function (Model $record) { diff --git a/app/Models/Todo.php b/app/Models/Todo.php index 2895e3d..fbda34c 100644 --- a/app/Models/Todo.php +++ b/app/Models/Todo.php @@ -9,7 +9,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; -#[Fillable('name', 'content', 'user_id', 'todoable_type', 'todoable_id', 'due_date', 'done_date', 'follow_up', 'review')] +#[Fillable('name', 'content', 'user_id', 'todoable_type', 'todoable_id', 'due_date', 'done_date', 'follow_up', 'follow_up_notified_at', 'review')] #[ObservedBy([TodoObserver::class])] class Todo extends Model { @@ -23,15 +23,28 @@ class Todo extends Model return $this->belongsTo(User::class); } - public function is_owner_or_assigned() { - if(filament()->auth()->user()->id == $this->user_id) return true; - if($this->todoable_type == Group::class && in_array(filament()->auth()->user()->id, $this->todoable()->first()->users()->get()->pluck('id')->toArray())) return true; - if($this->todoable_type == User::class && $this->todoable()->first()->id == filament()->auth()->user()->id) return true; - else return false; + public function is_owner_or_assigned() + { + if (filament()->auth()->user()->id == $this->user_id) { + return true; + } + if ($this->todoable_type == Group::class && in_array(filament()->auth()->user()->id, $this->todoable()->first()->users()->get()->pluck('id')->toArray())) { + return true; + } + if ($this->todoable_type == User::class && $this->todoable()->first()->id == filament()->auth()->user()->id) { + return true; + } else { + return false; + } } - public function getassignetusers() { - if($this->todoable_type == Group::class) return $this->todoable()->first()->users()->get(); - if($this->todoable_type == User::class) return [$this->todoable()->first()]; + public function getassignetusers() + { + if ($this->todoable_type == Group::class) { + return $this->todoable()->first()->users()->get(); + } + if ($this->todoable_type == User::class) { + return [$this->todoable()->first()]; + } } } diff --git a/app/Observers/TodoObserver.php b/app/Observers/TodoObserver.php index e0fb949..e0b34b2 100644 --- a/app/Observers/TodoObserver.php +++ b/app/Observers/TodoObserver.php @@ -2,16 +2,10 @@ namespace App\Observers; -use App\Mail\TodoAssigned; -use App\Mail\TodoCreated; use App\Mail\TodoMail; -use App\Mail\TodoNeedsReview; -use App\Mail\TodoReopened; -use App\Models\Group; use App\Models\Todo; use App\Models\User; use Illuminate\Support\Facades\Mail; -use Illuminate\Support\Str; class TodoObserver { @@ -20,12 +14,13 @@ class TodoObserver */ public function created(Todo $todo): void { - if($todo->todoable_type == null) return; - // if($todo->todoable_type == User::class && $todo->user_id == $todo->todoable_id) return; - if($todo->todoable_type == User::class) { - Mail::to(User::find($todo->todoable()->first()))->send(new TodoMail($todo, 'Neue Todo', 'Es gibt eine neue Todo für dich:')); + if ($todo->todoable_type == null) { + return; } - else { + // if($todo->todoable_type == User::class && $todo->user_id == $todo->todoable_id) return; + if ($todo->todoable_type == User::class) { + Mail::to(User::find($todo->todoable()->first()))->send(new TodoMail($todo, 'Neue Todo', 'Es gibt eine neue Todo für dich:')); + } else { $users = $todo->todoable()->first()->users()->get(); foreach ($users as $user) { Mail::to($user)->send(new TodoMail($todo, 'Neue Todo', 'Es gibt eine neue Todo für dich:')); @@ -33,6 +28,16 @@ class TodoObserver } } + /** + * Handle the Todo "updating" event. + */ + public function updating(Todo $todo): void + { + if ($todo->isDirty('follow_up')) { + $todo->follow_up_notified_at = null; + } + } + /** * Handle the Todo "updated" event. */ diff --git a/bootstrap/app.php b/bootstrap/app.php index c183276..e3082a1 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,5 +1,6 @@ withExceptions(function (Exceptions $exceptions): void { // + }) + ->withSchedule(function (Schedule $schedule): void { + $schedule->command('app:send-todo-follow-up-reminders')->dailyAt('07:00'); })->create(); diff --git a/database/migrations/2026_07_02_192737_add_follow_up_notified_at_to_todos_table.php b/database/migrations/2026_07_02_192737_add_follow_up_notified_at_to_todos_table.php new file mode 100644 index 0000000..bc34ed0 --- /dev/null +++ b/database/migrations/2026_07_02_192737_add_follow_up_notified_at_to_todos_table.php @@ -0,0 +1,28 @@ +timestamp('follow_up_notified_at')->nullable()->after('follow_up'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('todos', function (Blueprint $table) { + $table->dropColumn('follow_up_notified_at'); + }); + } +};