diff --git a/database/migrations/2020_10_27_102330_create_tasks_table.php b/database/migrations/2020_10_27_102330_create_tasks_table.php index 075a2e8..c759f3a 100644 --- a/database/migrations/2020_10_27_102330_create_tasks_table.php +++ b/database/migrations/2020_10_27_102330_create_tasks_table.php @@ -16,9 +16,11 @@ public function up() $table->tinyInteger('flag')->nullable()->index(); - $table->boolean('completed')->index(); - $table->dateTime('reminder')->nullable(); + $table->tinyInteger('status'); + $table->dateTime('from')->nullable(); + $table->dateTime('to')->nullable(); + $table->boolean('muted')->default(0); $table->unsignedInteger('allocated_to')->nullable()->index(); $table->foreign('allocated_to')->references('id')->on('users'); diff --git a/database/migrations/2020_10_27_102337_create_structure_for_tasks.php b/database/migrations/2020_10_27_102337_create_structure_for_tasks.php index 9c90464..70f4b9d 100644 --- a/database/migrations/2020_10_27_102337_create_structure_for_tasks.php +++ b/database/migrations/2020_10_27_102337_create_structure_for_tasks.php @@ -16,6 +16,8 @@ ['name' => 'tasks.exportExcel', 'description' => 'Export excel for tasks', 'is_default' => false], ['name' => 'tasks.count', 'description' => 'Get number of pending tasks', 'is_default' => false], ['name' => 'tasks.users', 'description' => 'Get user options for task allocation', 'is_default' => false], + ['name' => 'tasks.show', 'description' => 'Display task information', 'is_default' => true], + ['name' => 'tasks.options', 'description' => 'Get tasks options for select', 'is_default' => false], ]; protected array $menu = [ diff --git a/database/migrations/2022_06_29_133122_create_task_checklist_items_table.php b/database/migrations/2022_06_29_133122_create_task_checklist_items_table.php new file mode 100644 index 0000000..cb8cd59 --- /dev/null +++ b/database/migrations/2022_06_29_133122_create_task_checklist_items_table.php @@ -0,0 +1,28 @@ +id(); + + $table->unsignedBigInteger('task_id'); + $table->foreign('task_id')->references('id')->on('tasks'); + + $table->string('name'); + $table->unsignedInteger('order_index')->nullable(); + $table->boolean('is_completed'); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('task_checklist_items'); + } +}; diff --git a/database/migrations/2022_06_29_133125_create_structure_for_checklist_items.php b/database/migrations/2022_06_29_133125_create_structure_for_checklist_items.php new file mode 100644 index 0000000..a70f0f4 --- /dev/null +++ b/database/migrations/2022_06_29_133125_create_structure_for_checklist_items.php @@ -0,0 +1,20 @@ + 'tasks.checklistItems.index', 'description' => 'Show index for task checklist items', 'is_default' => false], + + ['name' => 'tasks.checklistItems.store', 'description' => 'Store a new task checklist item', 'is_default' => false], + ['name' => 'tasks.checklistItems.update', 'description' => 'Update task checklist item', 'is_default' => false], + ['name' => 'tasks.checklistItems.destroy', 'description' => 'Delete task checklist item', 'is_default' => false], + + ['name' => 'tasks.checklistItems.options', 'description' => 'Get task checklist item options for select', 'is_default' => false], + ]; + + protected array $menu = []; + + protected ?string $parentMenu = null; +} diff --git a/database/migrations/2022_07_19_134101_create_task_comments_table.php b/database/migrations/2022_07_19_134101_create_task_comments_table.php new file mode 100644 index 0000000..021cb09 --- /dev/null +++ b/database/migrations/2022_07_19_134101_create_task_comments_table.php @@ -0,0 +1,31 @@ +id(); + $table->unsignedBigInteger('task_id'); + $table->foreign('task_id')->references('id')->on('tasks'); + $table->text('body'); + + $table->unsignedInteger('created_by')->nullable()->index(); + $table->foreign('created_by')->references('id')->on('users'); + + $table->unsignedInteger('updated_by')->nullable()->index(); + $table->foreign('updated_by')->references('id')->on('users'); + + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('task_comments'); + } +}; diff --git a/database/migrations/2022_07_19_141237_create_structure_for_task_comments.php b/database/migrations/2022_07_19_141237_create_structure_for_task_comments.php new file mode 100644 index 0000000..b76603d --- /dev/null +++ b/database/migrations/2022_07_19_141237_create_structure_for_task_comments.php @@ -0,0 +1,13 @@ + 'tasks.comments.index', 'description' => 'List task comments for commentable', 'is_default' => true], + ['name' => 'tasks.comments.store', 'description' => 'Create task comment', 'is_default' => true], + ['name' => 'tasks.comments.update', 'description' => 'Update task edited comment', 'is_default' => true], + ['name' => 'tasks.comments.destroy', 'description' => 'Delete task comment', 'is_default' => true], + ]; +}; diff --git a/routes/api.php b/routes/api.php index 3c6f7c2..db39f26 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,36 +1,12 @@ prefix('api/tasks') ->as('tasks.') ->group(function () { - Route::get('create', Create::class)->name('create'); - Route::post('', Store::class)->name('store'); - Route::get('{task}/edit', Edit::class)->name('edit'); - - Route::patch('{task}', Update::class)->name('update'); - - Route::delete('{task}', Destroy::class)->name('destroy'); - - Route::get('initTable', InitTable::class)->name('initTable'); - Route::get('tableData', TableData::class)->name('tableData'); - Route::get('exportExcel', ExportExcel::class)->name('exportExcel'); - - Route::get('count', Count::class)->name('count'); - Route::get('', Index::class)->name('index'); - - Route::get('users', Users::class)->name('users'); + require __DIR__.'/app/comments.php'; + require __DIR__.'/app/tasks.php'; + require __DIR__.'/app/checklistItems.php'; }); diff --git a/routes/app/checklistItems.php b/routes/app/checklistItems.php new file mode 100644 index 0000000..0c2bf47 --- /dev/null +++ b/routes/app/checklistItems.php @@ -0,0 +1,20 @@ +as('checklistItems.') + ->group(function () { + Route::get('create', Create::class)->name('create'); + Route::post('', Store::class)->name('store'); + Route::get('{checklistItem}/edit', Edit::class)->name('edit'); + Route::patch('{checklistItem}', Update::class)->name('update'); + Route::delete('{checklistItem}', Destroy::class)->name('destroy'); + Route::get('options', Options::class)->name('options'); + }); diff --git a/routes/app/comments.php b/routes/app/comments.php new file mode 100644 index 0000000..cc20b1e --- /dev/null +++ b/routes/app/comments.php @@ -0,0 +1,16 @@ +as('comments.') + ->group(function () { + Route::get('', Index::class)->name('index'); + Route::post('', Store::class)->name('store'); + Route::patch('{comment}', Update::class)->name('update'); + Route::delete('{comment}', Destroy::class)->name('destroy'); + }); diff --git a/routes/app/tasks.php b/routes/app/tasks.php new file mode 100644 index 0000000..4bc84ff --- /dev/null +++ b/routes/app/tasks.php @@ -0,0 +1,36 @@ +name('create'); +Route::post('', Store::class)->name('store'); +Route::get('{task}/edit', Edit::class)->name('edit'); + +Route::patch('{task}', Update::class)->name('update'); + +Route::delete('{task}', Destroy::class)->name('destroy'); + +Route::get('initTable', InitTable::class)->name('initTable'); +Route::get('tableData', TableData::class)->name('tableData'); +Route::get('exportExcel', ExportExcel::class)->name('exportExcel'); + +Route::get('count', Count::class)->name('count'); +Route::get('', Index::class)->name('index'); + +Route::get('users', Users::class)->name('users'); + +Route::get('options', Options::class)->name('options'); +Route::get('{task}', Show::class)->name('show'); diff --git a/src/AppServiceProvider.php b/src/AppServiceProvider.php index 5372ad4..76ab546 100644 --- a/src/AppServiceProvider.php +++ b/src/AppServiceProvider.php @@ -7,7 +7,9 @@ use LaravelEnso\DynamicMethods\Services\Methods; use LaravelEnso\Tasks\Commands\SendTaskReminders; use LaravelEnso\Tasks\DynamicRelations\Tasks; +use LaravelEnso\Tasks\Models\ChecklistItem; use LaravelEnso\Tasks\Models\Task as Model; +use LaravelEnso\Tasks\Observers\ChecklistItem as ChecklistItemObserver; use LaravelEnso\Tasks\Observers\Task as Observer; use LaravelEnso\Users\Models\User; @@ -20,6 +22,7 @@ public function boot() ->command() ->relations() ->observers(); + ChecklistItem::observe(ChecklistItemObserver::class); } private function load(): self diff --git a/src/AuthServiceProvider.php b/src/AuthServiceProvider.php index ec6d91f..86cf086 100644 --- a/src/AuthServiceProvider.php +++ b/src/AuthServiceProvider.php @@ -3,13 +3,16 @@ namespace LaravelEnso\Tasks; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; +use LaravelEnso\Tasks\Models\Comment; use LaravelEnso\Tasks\Models\Task; +use LaravelEnso\Tasks\Policies\Comment as CommentPolicy; use LaravelEnso\Tasks\Policies\Task as Policy; class AuthServiceProvider extends ServiceProvider { protected $policies = [ - Task::class => Policy::class, + Task::class => Policy::class, + Comment::class => CommentPolicy::class, ]; public function boot() diff --git a/src/EnumServiceProvider.php b/src/EnumServiceProvider.php index b644633..aa7498f 100644 --- a/src/EnumServiceProvider.php +++ b/src/EnumServiceProvider.php @@ -4,10 +4,12 @@ use LaravelEnso\Enums\EnumServiceProvider as ServiceProvider; use LaravelEnso\Tasks\Enums\Flags; +use LaravelEnso\Tasks\Enums\Statuses; class EnumServiceProvider extends ServiceProvider { public $register = [ - 'flags' => Flags::class, + 'flags' => Flags::class, + 'statuses' => Statuses::class, ]; } diff --git a/src/Enums/Statuses.php b/src/Enums/Statuses.php new file mode 100644 index 0000000..7d847fa --- /dev/null +++ b/src/Enums/Statuses.php @@ -0,0 +1,21 @@ + 'New', + self::InProgress => 'In Progress', + self::Finished => 'Finished', + ]; + } +} diff --git a/src/Forms/Builders/Task.php b/src/Forms/Builders/Task.php index 3fed158..61f27e7 100644 --- a/src/Forms/Builders/Task.php +++ b/src/Forms/Builders/Task.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Auth; use LaravelEnso\Forms\Services\Form; +use LaravelEnso\Tasks\Enums\Statuses; use LaravelEnso\Tasks\Models\Task as Model; class Task @@ -21,8 +22,9 @@ public function __construct() public function create() { - return $this->form->hide('completed') + return $this->form->hide('status') ->value('allocated_to', Auth::id()) + ->value('status', Statuses::New) ->create(); } diff --git a/src/Forms/Templates/task.json b/src/Forms/Templates/task.json index f0f91a4..5045209 100644 --- a/src/Forms/Templates/task.json +++ b/src/Forms/Templates/task.json @@ -26,7 +26,7 @@ ] }, { - "columns": 3, + "columns": 2, "fields": [ { "label": "Allocated To", @@ -50,12 +50,39 @@ } }, { - "label": "Completed", - "name": "completed", - "value": false, + "label": "Status", + "name": "status", + "value": null, "meta": { - "type": "input", - "content": "checkbox" + "type": "select", + "options": "LaravelEnso\\Tasks\\Enums\\Statuses" + } + } + ] + }, + { + "columns": 2, + "fields": [ + { + "label": "From", + "name": "from", + "value": "", + "meta": { + "type": "datepicker", + "altFormat": "m-d-Y H:i", + "format": "Y-m-d H:i:s", + "time": true + } + }, + { + "label": "To", + "name": "to", + "value": "", + "meta": { + "type": "datepicker", + "altFormat": "m-d-Y H:i", + "format": "Y-m-d H:i:s", + "time": true } } ] @@ -74,4 +101,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/Http/Controllers/ChecklistItems/Create.php b/src/Http/Controllers/ChecklistItems/Create.php new file mode 100644 index 0000000..493eaf3 --- /dev/null +++ b/src/Http/Controllers/ChecklistItems/Create.php @@ -0,0 +1,14 @@ + $form->create()]; + } +} diff --git a/src/Http/Controllers/ChecklistItems/Destroy.php b/src/Http/Controllers/ChecklistItems/Destroy.php new file mode 100644 index 0000000..6110106 --- /dev/null +++ b/src/Http/Controllers/ChecklistItems/Destroy.php @@ -0,0 +1,20 @@ +delete(); + + return [ + 'message' => __('The checklist item was successfully deleted'), + 'redirect' => 'checklists.index', + ]; + } +} diff --git a/src/Http/Controllers/ChecklistItems/Edit.php b/src/Http/Controllers/ChecklistItems/Edit.php new file mode 100644 index 0000000..7225f20 --- /dev/null +++ b/src/Http/Controllers/ChecklistItems/Edit.php @@ -0,0 +1,15 @@ + $form->edit($checklistItem)]; + } +} diff --git a/src/Http/Controllers/ChecklistItems/Options.php b/src/Http/Controllers/ChecklistItems/Options.php new file mode 100644 index 0000000..fa25605 --- /dev/null +++ b/src/Http/Controllers/ChecklistItems/Options.php @@ -0,0 +1,14 @@ +fill($request->validated())->save(); + + return [ + 'message' => __('The checklist item was successfully created'), + 'redirect' => 'tasks.checklistItems.edit', + 'param' => ['checklist' => $checklistItem->id], + ]; + } +} diff --git a/src/Http/Controllers/ChecklistItems/Update.php b/src/Http/Controllers/ChecklistItems/Update.php new file mode 100644 index 0000000..232a3a6 --- /dev/null +++ b/src/Http/Controllers/ChecklistItems/Update.php @@ -0,0 +1,18 @@ +update($request->validated()); + + return ['message' => __('The checklist item was successfully updated')]; + } +} diff --git a/src/Http/Controllers/Comments/Destroy.php b/src/Http/Controllers/Comments/Destroy.php new file mode 100644 index 0000000..a0852fe --- /dev/null +++ b/src/Http/Controllers/Comments/Destroy.php @@ -0,0 +1,21 @@ +authorize('destroy', $comment); + + $comment->delete(); + + return ['count' => $comment->count()]; + } +} diff --git a/src/Http/Controllers/Comments/Index.php b/src/Http/Controllers/Comments/Index.php new file mode 100644 index 0000000..65b03a4 --- /dev/null +++ b/src/Http/Controllers/Comments/Index.php @@ -0,0 +1,23 @@ +task_id)->latest() + ->with('createdBy.person', 'createdBy.avatar', 'updatedBy') + ->get(); + + return Resource::collection($comments)->additional([ + 'humanReadableDates' => Config::get('enso.comments.humanReadableDates'), + ]); + } +} diff --git a/src/Http/Controllers/Comments/Store.php b/src/Http/Controllers/Comments/Store.php new file mode 100644 index 0000000..1d9abb1 --- /dev/null +++ b/src/Http/Controllers/Comments/Store.php @@ -0,0 +1,22 @@ +fill($request->validatedExcept('path')); + + tap($comment)->save(); + + return new Resource($comment->load([ + 'createdBy.person', 'createdBy.avatar', + ])); + } +} diff --git a/src/Http/Controllers/Comments/Update.php b/src/Http/Controllers/Comments/Update.php new file mode 100644 index 0000000..bd78d7d --- /dev/null +++ b/src/Http/Controllers/Comments/Update.php @@ -0,0 +1,25 @@ +authorize('update', $comment); + + tap($comment)->update($request->only('body')); + + return new Resource($comment->load([ + 'createdBy.person', 'createdBy.avatar', 'updatedBy', + ])); + } +} diff --git a/src/Http/Controllers/Tasks/Options.php b/src/Http/Controllers/Tasks/Options.php new file mode 100644 index 0000000..3f8c6f4 --- /dev/null +++ b/src/Http/Controllers/Tasks/Options.php @@ -0,0 +1,14 @@ +authorize('handle', $task); + + return ['task' => new Resource($task->load(['checklistItems', 'allocatedTo', + 'allocatedTo.avatar', 'allocatedTo.person', 'createdBy', 'createdBy.person', ]))]; + } +} diff --git a/src/Http/Requests/ValidateChecklistItem.php b/src/Http/Requests/ValidateChecklistItem.php new file mode 100644 index 0000000..6fc600e --- /dev/null +++ b/src/Http/Requests/ValidateChecklistItem.php @@ -0,0 +1,24 @@ + ['required', 'string', $this->unique('name'), 'max:255'], + 'task_id' => ['required', 'exists:tasks,id'], + 'is_completed' => 'required|boolean', + ]; + } + + protected function unique(string $attribute) + { + return Rule::unique('task_checklist_items', $attribute) + ->ignore($this->route('checklistItem')?->id); + } +} diff --git a/src/Http/Requests/ValidateComment.php b/src/Http/Requests/ValidateComment.php new file mode 100644 index 0000000..a4edc0d --- /dev/null +++ b/src/Http/Requests/ValidateComment.php @@ -0,0 +1,27 @@ + "{$this->requiredOrFilled()}|exists:tasks,id", + 'body' => "{$this->requiredOrFilled()}|required", + 'path' => "{$this->requiredOrFilled()}", + ]; + } + + private function requiredOrFilled() + { + return $this->method() === 'POST' + ? 'required' + : 'filled'; + } +} diff --git a/src/Http/Requests/ValidateCommentFetch.php b/src/Http/Requests/ValidateCommentFetch.php new file mode 100644 index 0000000..9c06f0a --- /dev/null +++ b/src/Http/Requests/ValidateCommentFetch.php @@ -0,0 +1,15 @@ + 'required', + ]; + } +} diff --git a/src/Http/Requests/ValidateTask.php b/src/Http/Requests/ValidateTask.php index bd9054c..3d0cff4 100644 --- a/src/Http/Requests/ValidateTask.php +++ b/src/Http/Requests/ValidateTask.php @@ -21,8 +21,11 @@ public function rules() 'description' => 'filled', 'flag' => 'nullable|in:'.Flags::keys()->implode(','), 'reminder' => 'nullable|date', + 'from' => 'nullable|date', + 'to' => 'nullable|date', + 'muted' => 'nullable|boolean', 'allocated_to' => "{$this->requiredOrFilled()}|exists:users,id", - 'completed' => "{$this->requiredOrFilled()}|boolean", + 'status' => "{$this->requiredOrFilled()}", ]; } diff --git a/src/Http/Resources/ChecklistItems.php b/src/Http/Resources/ChecklistItems.php new file mode 100644 index 0000000..0d44e3c --- /dev/null +++ b/src/Http/Resources/ChecklistItems.php @@ -0,0 +1,22 @@ + $this->id, + 'name' => $this->name, + 'description' => $this->description, + 'taskId' => $this->task_id, + 'isCompleted' => $this->is_completed, + 'orderIndex' => $this->order_index, + 'createdAt' => Carbon::parse($this->created_at)->format('d-m-Y H:i'), + ]; + } +} diff --git a/src/Http/Resources/Comment.php b/src/Http/Resources/Comment.php new file mode 100644 index 0000000..9b3fdae --- /dev/null +++ b/src/Http/Resources/Comment.php @@ -0,0 +1,32 @@ + $this->id, + 'body' => $this->body, + 'owner' => new User($this->whenLoaded('createdBy')), + 'isEditable' => $this->isEditable($request), + 'isDeletable' => $this->isDeletable($request), + 'createdAt' => $this->created_at->toDatetimeString(), + 'updatedAt' => $this->updated_at->toDatetimeString(), + ]; + } + + public function isEditable($request) + { + return $request->user()->can('update', $this->resource); + } + + public function isDeletable($request) + { + return $request->user()->can('destroy', $this->resource); + } +} diff --git a/src/Http/Resources/Task.php b/src/Http/Resources/Task.php index 36e556a..96dbea8 100644 --- a/src/Http/Resources/Task.php +++ b/src/Http/Resources/Task.php @@ -2,19 +2,37 @@ namespace LaravelEnso\Tasks\Http\Resources; +use Carbon\Carbon; use Illuminate\Http\Resources\Json\JsonResource; +use LaravelEnso\Users\Http\Resources\User; class Task extends JsonResource { public function toArray($request) { return [ - 'id' => $this->id, - 'name' => $this->name, - 'description' => $this->description, - 'flag' => $this->flag, - 'overdue' => $this->overdue(), - 'reminder' => $this->reminder, + 'id' => $this->id, + 'name' => $this->name, + 'description' => $this->description, + 'reminder' => Carbon::parse($this->reminder)->format('d-m-Y H:i'), + 'from' => $this->from ? Carbon::parse($this->from)->format('d M Y') : null, + 'to' => Carbon::parse($this->to)->format('d M Y'), + 'muted' => $this->muted, + 'status' => $this->status, + 'flag' => $this->flag, + 'overdue' => $this->overdue(), + 'remindedAt' => $this->reminded_at, + 'allocatedTo' => new User($this->whenLoaded('allocatedTo')), + 'createdBy' => new User($this->whenLoaded('createdBy')), + 'taskChecklistItems' => ChecklistItems::collection($this->whenLoaded('checklistItems')), + 'completedTaskChecklistItems' => $this->completedTaskChecklistItems(), + 'createdAt' => Carbon::parse($this->created_at)->format('d-m-Y H:i'), + 'updatedAt' => Carbon::parse($this->updated_at)->diffForHumans(), ]; } + + public function completedTaskChecklistItems() + { + return "{$this->checklistItems()->completed()->count()}/{$this->checklistItems->count()}"; + } } diff --git a/src/Models/ChecklistItem.php b/src/Models/ChecklistItem.php new file mode 100644 index 0000000..62a02f9 --- /dev/null +++ b/src/Models/ChecklistItem.php @@ -0,0 +1,32 @@ + 'boolean']; + + public function task() + { + return $this->belongsTo(Task::class); + } + + public function scopeCompleted($query) + { + return $query->whereIsCompleted(true); + } + + public function scopePending($query) + { + return $query->whereIsCompleted(false); + } +} diff --git a/src/Models/Comment.php b/src/Models/Comment.php new file mode 100644 index 0000000..dddb4b3 --- /dev/null +++ b/src/Models/Comment.php @@ -0,0 +1,21 @@ + 'boolean']; + protected $casts = []; public function allocatedTo(): Relation { @@ -54,12 +55,12 @@ public function scopeVisible($query) public function scopePending($query) { - return $query->whereCompleted(false); + return $query->whereStatus(Statuses::InProgress); } public function scopeCompleted($query) { - return $query->whereCompleted(true); + return $query->whereStatus(Statuses::Finished); } public function setReminderAttribute($dateTime) @@ -81,7 +82,32 @@ public function remind() public function overdue(): bool { - return !$this->completed + return $this->status != Statuses::Finished && $this->reminder?->lessThan(Carbon::now()); } + + public function checklistItems() + { + return $this->hasMany(ChecklistItem::class); + } + + public function comments() + { + return $this->hasMany(Comment::class); + } + + public function updateStatus() + { + $completedItems = $this->checklistItems()->completed()->count(); + + $totalItems = $this->checklistItems()->count(); + + $status = match ($completedItems) { + $totalItems => Statuses::Finished, + 0 => Statuses::New, + default => Statuses::InProgress, + }; + + $this->update(['status' => $status]); + } } diff --git a/src/Observers/ChecklistItem.php b/src/Observers/ChecklistItem.php new file mode 100644 index 0000000..96a7d91 --- /dev/null +++ b/src/Observers/ChecklistItem.php @@ -0,0 +1,25 @@ +task->updateStatus(); + } + + public function updated(Model $checklist) + { + if ($checklist->isDirty('is_completed')) { + $checklist->task->updateStatus(); + } + } + + public function deleted(Model $checklist) + { + $checklist->task->updateStatus(); + } +} diff --git a/src/Policies/Comment.php b/src/Policies/Comment.php new file mode 100644 index 0000000..9d956d0 --- /dev/null +++ b/src/Policies/Comment.php @@ -0,0 +1,44 @@ +isAdmin() || $user->isSupervisor()) { + return true; + } + } + + public function update(User $user, Model $comment) + { + return $this->ownsComment($user, $comment) + && $this->isRecent($comment); + } + + public function destroy(User $user, Model $comment) + { + return $this->ownsComment($user, $comment) + && $this->isRecent($comment); + } + + private function ownsComment(User $user, Model $comment) + { + return $user->id === (int) $comment->created_by; + } + + private function isRecent(Model $comment) + { + return $comment->created_at->diffInSeconds(Carbon::now()) + < Config::get('enso.comments.editableTimeLimit'); + } +} diff --git a/src/Tables/Builders/Task.php b/src/Tables/Builders/Task.php index 4c61c90..b193dac 100644 --- a/src/Tables/Builders/Task.php +++ b/src/Tables/Builders/Task.php @@ -10,6 +10,7 @@ use LaravelEnso\Tables\Contracts\ConditionalActions; use LaravelEnso\Tables\Contracts\CustomFilter; use LaravelEnso\Tables\Contracts\Table; +use LaravelEnso\Tasks\Enums\Statuses; use LaravelEnso\Tasks\Models\Task as Model; class Task implements Table, AuthenticatesOnExport, CustomFilter, ConditionalActions @@ -19,13 +20,14 @@ class Task implements Table, AuthenticatesOnExport, CustomFilter, ConditionalAct public function query(): Builder { $now = Carbon::now(); - $overdue = "completed = true and reminder >= '{$now}'"; + $finished = Statuses::Finished; + $overdue = "status = '$finished' and reminder >= '{$now}'"; return Model::visible() ->with('createdBy.avatar', 'createdBy.person') ->with('allocatedTo.avatar', 'allocatedTo.person') ->selectRaw(" - tasks.id, tasks.name, tasks.description, tasks.flag, tasks.completed, + tasks.id, tasks.name, tasks.description, tasks.flag, tasks.status, tasks.allocated_to, tasks.reminder, tasks.reminder as rawReminder, created_by, created_at, {$overdue} as overdue "); diff --git a/src/Tables/Templates/tasks.json b/src/Tables/Templates/tasks.json index a0ee75f..a8398b6 100644 --- a/src/Tables/Templates/tasks.json +++ b/src/Tables/Templates/tasks.json @@ -6,6 +6,7 @@ "excel", "create", "edit", + "show", "destroy" ], "strip": ["allocated_to", "created_by"], @@ -46,13 +47,14 @@ "notExportable" ] }, { - "label": "Completed", - "name": "completed", - "data": "tasks.completed", + "label": "Status", + "name": "status", + "data": "tasks.status", "meta": [ "sortable", "slot" - ] + ], + "enum": "LaravelEnso\\Tasks\\Enums\\Statuses" }, { "label": "Created At", "name": "created_at", diff --git a/src/Upgrades/From.php b/src/Upgrades/From.php new file mode 100644 index 0000000..0a0db39 --- /dev/null +++ b/src/Upgrades/From.php @@ -0,0 +1,22 @@ +dateTime('from')->nullable()->after('reminder'); + }); + } +} diff --git a/src/Upgrades/Muted.php b/src/Upgrades/Muted.php new file mode 100644 index 0000000..cf9325a --- /dev/null +++ b/src/Upgrades/Muted.php @@ -0,0 +1,22 @@ +dateTime('muted')->nullable()->after('reminder'); + }); + } +} diff --git a/src/Upgrades/Permissions.php b/src/Upgrades/Permissions.php new file mode 100644 index 0000000..2bea999 --- /dev/null +++ b/src/Upgrades/Permissions.php @@ -0,0 +1,16 @@ + 'tasks.show', 'description' => 'Display task information', 'is_default' => true], + ['name' => 'tasks.options', 'description' => 'Get tasks options for select', 'is_default' => false], + ]; +} diff --git a/src/Upgrades/Statuses.php b/src/Upgrades/Statuses.php new file mode 100644 index 0000000..00dcca5 --- /dev/null +++ b/src/Upgrades/Statuses.php @@ -0,0 +1,46 @@ +tinyInteger('status')->nullable()->after('reminder'); + }); + } + + public function migrateData(): void + { + Task::whereCompleted(false) + ->update(['status' => StatusesEnum::New]); + Task::whereCompleted(true) + ->update(['status' => StatusesEnum::Finished]); + } + + public function migratePostDataMigration(): void + { + Schema::table('tasks', function ($table) { + $table->dropColumn('completed'); + }); + + if (DB::getDriverName() === 'mysql') { + DB::statement('ALTER TABLE `tasks` CHANGE status status TINYINT(3) NOT NULL'); + } + } +} diff --git a/src/Upgrades/To.php b/src/Upgrades/To.php new file mode 100644 index 0000000..08a228b --- /dev/null +++ b/src/Upgrades/To.php @@ -0,0 +1,22 @@ +dateTime('to')->nullable()->after('reminder'); + }); + } +}