diff --git a/app/Http/Resources/TaskResource.php b/app/Http/Resources/TaskResource.php index 296cc84..39bf716 100644 --- a/app/Http/Resources/TaskResource.php +++ b/app/Http/Resources/TaskResource.php @@ -14,8 +14,13 @@ class TaskResource extends JsonResource */ public function toArray(Request $request): array { - // Calculate days cleanly or fallback to 0 if null - $daysInColumn = $this->column_updated_at ? (int) $this->column_updated_at->diffInDays(now()) : 0; + // Calculate historical + active seconds for current column properly + $currentSeconds = $this->column_updated_at ? (int) abs($this->column_updated_at->diffInSeconds(now())) : 0; + $history = $this->time_spent_in_columns ?? []; + $historicalSeconds = $history[$this->column_id] ?? 0; + + $totalSeconds = $historicalSeconds + $currentSeconds; + $daysInColumn = (int) floor($totalSeconds / 86400); return [ 'id' => $this->id, diff --git a/app/Models/Task.php b/app/Models/Task.php index 78e0839..164df55 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -19,6 +19,7 @@ class Task extends Model 'team_id', 'column_id', 'column_updated_at', + 'time_spent_in_columns', 'order', 'title', 'description', @@ -36,6 +37,7 @@ protected function casts(): array return [ 'column_updated_at' => 'datetime', 'due_date' => 'datetime', + 'time_spent_in_columns' => 'array', ]; } diff --git a/app/Services/TaskService.php b/app/Services/TaskService.php index 2ea08c1..9a8d547 100644 --- a/app/Services/TaskService.php +++ b/app/Services/TaskService.php @@ -79,8 +79,13 @@ public function updateSequence(Task $task, int $newColumnId, int $newOrder): voi 'order' => $newOrder, ]; - // If it moved to a new column, reset its timer + // If it moved to a new column, reset its timer and accumulate the history if ($isDifferentColumn) { + $timeInOldColumn = $task->column_updated_at ? (int) abs($task->column_updated_at->diffInSeconds(now())) : 0; + $history = $task->time_spent_in_columns ?? []; + $history[$oldColumnId] = ($history[$oldColumnId] ?? 0) + $timeInOldColumn; + + $updateData['time_spent_in_columns'] = $history; $updateData['column_updated_at'] = now(); } diff --git a/database/migrations/2026_03_25_105633_add_time_spent_in_columns_to_tasks_table.php b/database/migrations/2026_03_25_105633_add_time_spent_in_columns_to_tasks_table.php new file mode 100644 index 0000000..78a4416 --- /dev/null +++ b/database/migrations/2026_03_25_105633_add_time_spent_in_columns_to_tasks_table.php @@ -0,0 +1,28 @@ +json('time_spent_in_columns')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('tasks', function (Blueprint $table) { + $table->dropColumn('time_spent_in_columns'); + }); + } +}; diff --git a/tests/Feature/TaskSequenceTest.php b/tests/Feature/TaskSequenceTest.php new file mode 100644 index 0000000..63631a0 --- /dev/null +++ b/tests/Feature/TaskSequenceTest.php @@ -0,0 +1,78 @@ +team = Team::factory()->create(); + $this->user = User::factory()->create(['team_id' => $this->team->id]); + $this->columnA = Column::create(['team_id' => $this->team->id, 'name' => 'Col A', 'order' => 1]); + $this->columnB = Column::create(['team_id' => $this->team->id, 'name' => 'Col B', 'order' => 2]); +}); + +describe('sequence updates', function () { + test('moving a task to a different column records time spent', function () { + // Create task exactly now + $task = Task::factory()->create([ + 'team_id' => $this->team->id, + 'column_id' => $this->columnA->id, + 'column_updated_at' => now(), + 'order' => 0, + ]); + + // Fast forward 1 day (86400 seconds) + $this->travel(1)->days(); + + // Move task to column B + $this->actingAs($this->user) + ->put(route('tasks.sequence.update', $task), [ + 'column_id' => $this->columnB->id, + 'order' => 0, + ]) + ->assertRedirect(); + + $task->refresh(); + + // Verify task updated correctly + $this->assertEquals($this->columnB->id, $task->column_id); + $this->assertEquals(0, $task->order); + + // Verify time tracking + $this->assertIsArray($task->time_spent_in_columns); + $this->assertTrue(isset($task->time_spent_in_columns[$this->columnA->id])); + $this->assertEquals(86400, $task->time_spent_in_columns[$this->columnA->id]); + + // Fast forward 2 days (172800 seconds) + $this->travel(2)->days(); + + // Move task back to column A + $this->actingAs($this->user) + ->put(route('tasks.sequence.update', $task), [ + 'column_id' => $this->columnA->id, + 'order' => 0, + ]) + ->assertRedirect(); + + $task->refresh(); + + // Verify time tracking for Column B + $this->assertEquals(172800, $task->time_spent_in_columns[$this->columnB->id]); + + // Move task AGAIN (after 10 seconds) to ensure time accumulates rather than overwrites + $this->travel(10)->seconds(); + + $this->actingAs($this->user) + ->put(route('tasks.sequence.update', $task), [ + 'column_id' => $this->columnB->id, + 'order' => 0, + ]) + ->assertRedirect(); + + $task->refresh(); + + // Column A should now have 86400 + 10 = 86410 seconds + $this->assertEquals(86410, $task->time_spent_in_columns[$this->columnA->id]); + }); +});