소스 검색

feat(kanban): add order field and reorder endpoint for drag-and-drop

- Migration: add order column to kanbans
- KanbanService: sort by order; add reorder() bulk update
- KanbanRequest: accept order field
- KanbanReorderRequest: validate items[].{id,phase,order}
- KanbanResource: expose order field
- KanbanController: add reorder action
- Routes: POST /kanban/reorder
ebagabee 2 주 전
부모
커밋
81cb2e7585

+ 7 - 0
app/Http/Controllers/KanbanController.php

@@ -5,6 +5,7 @@
 use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\KanbanService;
 use App\Http\Requests\KanbanRequest;
+use App\Http\Requests\KanbanReorderRequest;
 use App\Http\Resources\KanbanResource;
 use App\Models\Unit;
 use Illuminate\Http\JsonResponse;
@@ -94,6 +95,12 @@ public function destroy(int $id): JsonResponse
         return $this->successResponse(message: __('messages.deleted'), code: 204);
     }
 
+    public function reorder(KanbanReorderRequest $request): JsonResponse
+    {
+        $this->service->reorder($request->validated()['items']);
+        return $this->successResponse(message: __('messages.updated'));
+    }
+
     private function isMatriz(\App\Models\User $user): bool
     {
         return $user->user_type === 'ADMIN';

+ 18 - 0
app/Http/Requests/KanbanReorderRequest.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class KanbanReorderRequest extends FormRequest
+{
+    public function rules(): array
+    {
+        return [
+            'items'         => 'required|array|min:1',
+            'items.*.id'    => 'required|integer|exists:kanbans,id',
+            'items.*.phase' => 'required|string|in:a_fazer,em_progresso,em_revisao,concluido,demandas_especiais',
+            'items.*.order' => 'required|integer|min:0',
+        ];
+    }
+}

+ 1 - 0
app/Http/Requests/KanbanRequest.php

@@ -15,6 +15,7 @@ public function rules(): array
             'title'               => "{$sometimes}|string|max:255",
             'priority'            => "{$sometimes}|string|in:alta,normal,baixa",
             'phase'               => "{$sometimes}|string|in:a_fazer,em_progresso,em_revisao,concluido,demandas_especiais",
+            'order'               => 'sometimes|integer|min:0',
             'scope'               => "{$sometimes}|string|in:internal,all,specific",
             'responsible_user_id' => 'nullable|integer|exists:users,id',
             'target_unit_id'      => 'nullable|integer|exists:units,id',

+ 1 - 0
app/Http/Resources/KanbanResource.php

@@ -16,6 +16,7 @@ public function toArray(Request $request): array
             'title'                => $this->title,
             'description'          => $this->description,
             'phase'                => $this->phase,
+            'order'                => $this->order,
             'priority'             => $this->priority,
             'origin'               => $this->origin,
             'scope'                => $this->scope,

+ 16 - 0
app/Services/KanbanService.php

@@ -16,6 +16,7 @@ private function baseQuery()
     public function getAll(): Collection
     {
         return $this->baseQuery()
+            ->orderBy('order', 'asc')
             ->orderBy('created_at', 'desc')
             ->get();
     }
@@ -24,10 +25,25 @@ public function getAllForUnit(int $unitId): Collection
     {
         return $this->baseQuery()
             ->visibleToUnit($unitId)
+            ->orderBy('order', 'asc')
             ->orderBy('created_at', 'desc')
             ->get();
     }
 
+    /**
+     * Bulk-update phase + order for a set of cards (drag-and-drop persistence).
+     * Expects: [['id' => int, 'phase' => string, 'order' => int], ...]
+     */
+    public function reorder(array $items): void
+    {
+        foreach ($items as $item) {
+            Kanban::where('id', $item['id'])->update([
+                'phase' => $item['phase'],
+                'order' => $item['order'],
+            ]);
+        }
+    }
+
     public function findById(int $id): ?Kanban
     {
         return $this->baseQuery()->find($id);

+ 22 - 0
database/migrations/2026_05_27_120000_add_order_to_kanbans_table.php

@@ -0,0 +1,22 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::table('kanbans', function (Blueprint $table) {
+            $table->unsignedInteger('order')->default(0)->after('phase');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::table('kanbans', function (Blueprint $table) {
+            $table->dropColumn('order');
+        });
+    }
+};

+ 1 - 0
routes/authRoutes/kanban.php

@@ -7,6 +7,7 @@
 Route::controller(KanbanController::class)->prefix('kanban')->group(function () {
     Route::get('/', 'index')->middleware('permission:kanban,view');
     Route::post('/', 'store')->middleware('permission:kanban,add');
+    Route::post('/reorder', 'reorder')->middleware('permission:kanban,edit');
     Route::get('/{id}', 'show')->middleware('permission:kanban,view');
     Route::put('/{id}', 'update')->middleware('permission:kanban,edit');
     Route::delete('/{id}', 'destroy')->middleware('permission:kanban,delete');