diff --git a/TaskTracker.Api/Controllers/TasksController.cs b/TaskTracker.Api/Controllers/TasksController.cs index f68ff9e..17a776c 100644 --- a/TaskTracker.Api/Controllers/TasksController.cs +++ b/TaskTracker.Api/Controllers/TasksController.cs @@ -51,6 +51,7 @@ public class TasksController(ITaskRepository taskRepo, ILogger Description = request.Description, Category = request.Category, ParentTaskId = request.ParentTaskId, + EstimatedMinutes = request.EstimatedMinutes, Status = WorkTaskStatus.Pending }; @@ -161,6 +162,22 @@ public class TasksController(ITaskRepository taskRepo, ILogger return Ok(ApiResponse.Ok(task)); } + [HttpPut("{id:int}")] + public async Task Update(int id, [FromBody] UpdateTaskRequest request) + { + var task = await taskRepo.GetByIdAsync(id); + if (task is null) + return NotFound(ApiResponse.Fail("Task not found")); + + if (request.Title is not null) task.Title = request.Title; + if (request.Description is not null) task.Description = request.Description; + if (request.Category is not null) task.Category = request.Category; + if (request.EstimatedMinutes.HasValue) task.EstimatedMinutes = request.EstimatedMinutes; + + await taskRepo.UpdateAsync(task); + return Ok(ApiResponse.Ok(task)); + } + [HttpDelete("{id:int}")] public async Task Delete(int id) { diff --git a/TaskTracker.Core/DTOs/CreateTaskRequest.cs b/TaskTracker.Core/DTOs/CreateTaskRequest.cs index aaf6cb8..2e4fb0c 100644 --- a/TaskTracker.Core/DTOs/CreateTaskRequest.cs +++ b/TaskTracker.Core/DTOs/CreateTaskRequest.cs @@ -6,4 +6,5 @@ public class CreateTaskRequest public string? Description { get; set; } public string? Category { get; set; } public int? ParentTaskId { get; set; } + public int? EstimatedMinutes { get; set; } } diff --git a/TaskTracker.Core/DTOs/UpdateTaskRequest.cs b/TaskTracker.Core/DTOs/UpdateTaskRequest.cs new file mode 100644 index 0000000..24fd07e --- /dev/null +++ b/TaskTracker.Core/DTOs/UpdateTaskRequest.cs @@ -0,0 +1,9 @@ +namespace TaskTracker.Core.DTOs; + +public class UpdateTaskRequest +{ + public string? Title { get; set; } + public string? Description { get; set; } + public string? Category { get; set; } + public int? EstimatedMinutes { get; set; } +} diff --git a/TaskTracker.Core/Entities/WorkTask.cs b/TaskTracker.Core/Entities/WorkTask.cs index 9038c5c..c4c47c4 100644 --- a/TaskTracker.Core/Entities/WorkTask.cs +++ b/TaskTracker.Core/Entities/WorkTask.cs @@ -12,6 +12,7 @@ public class WorkTask public DateTime CreatedAt { get; set; } public DateTime? StartedAt { get; set; } public DateTime? CompletedAt { get; set; } + public int? EstimatedMinutes { get; set; } public int? ParentTaskId { get; set; } public WorkTask? ParentTask { get; set; } diff --git a/TaskTracker.Infrastructure/Migrations/20260227031015_AddEstimatedMinutes.Designer.cs b/TaskTracker.Infrastructure/Migrations/20260227031015_AddEstimatedMinutes.Designer.cs new file mode 100644 index 0000000..d078478 --- /dev/null +++ b/TaskTracker.Infrastructure/Migrations/20260227031015_AddEstimatedMinutes.Designer.cs @@ -0,0 +1,262 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TaskTracker.Infrastructure.Data; + +#nullable disable + +namespace TaskTracker.Infrastructure.Migrations +{ + [DbContext(typeof(TaskTrackerDbContext))] + [Migration("20260227031015_AddEstimatedMinutes")] + partial class AddEstimatedMinutes + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("TaskTracker.Core.Entities.AppMapping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("FriendlyName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("MatchType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Pattern") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("AppMappings"); + + b.HasData( + new + { + Id = 1, + Category = "Engineering", + FriendlyName = "SolidWorks", + MatchType = "ProcessName", + Pattern = "SLDWORKS" + }, + new + { + Id = 2, + Category = "Email", + FriendlyName = "Outlook", + MatchType = "ProcessName", + Pattern = "OUTLOOK" + }, + new + { + Id = 3, + Category = "General", + FriendlyName = "Notepad", + MatchType = "ProcessName", + Pattern = "notepad" + }, + new + { + Id = 4, + Category = "LaserCutting", + FriendlyName = "PEP System", + MatchType = "UrlContains", + Pattern = "pep" + }, + new + { + Id = 5, + Category = "Engineering", + FriendlyName = "SolidWorks", + MatchType = "TitleContains", + Pattern = "solidworks" + }); + }); + + modelBuilder.Entity("TaskTracker.Core.Entities.ContextEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AppName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Timestamp") + .HasColumnType("datetime2"); + + b.Property("Url") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("WindowTitle") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("WorkTaskId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Timestamp"); + + b.HasIndex("WorkTaskId"); + + b.ToTable("ContextEvents"); + }); + + modelBuilder.Entity("TaskTracker.Core.Entities.TaskNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("WorkTaskId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("WorkTaskId"); + + b.ToTable("Notes"); + }); + + modelBuilder.Entity("TaskTracker.Core.Entities.WorkTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Category") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("ParentTaskId") + .HasColumnType("int"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("ParentTaskId"); + + b.ToTable("Tasks"); + }); + + modelBuilder.Entity("TaskTracker.Core.Entities.ContextEvent", b => + { + b.HasOne("TaskTracker.Core.Entities.WorkTask", "WorkTask") + .WithMany("ContextEvents") + .HasForeignKey("WorkTaskId"); + + b.Navigation("WorkTask"); + }); + + modelBuilder.Entity("TaskTracker.Core.Entities.TaskNote", b => + { + b.HasOne("TaskTracker.Core.Entities.WorkTask", "WorkTask") + .WithMany("Notes") + .HasForeignKey("WorkTaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WorkTask"); + }); + + modelBuilder.Entity("TaskTracker.Core.Entities.WorkTask", b => + { + b.HasOne("TaskTracker.Core.Entities.WorkTask", "ParentTask") + .WithMany("SubTasks") + .HasForeignKey("ParentTaskId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentTask"); + }); + + modelBuilder.Entity("TaskTracker.Core.Entities.WorkTask", b => + { + b.Navigation("ContextEvents"); + + b.Navigation("Notes"); + + b.Navigation("SubTasks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TaskTracker.Infrastructure/Migrations/20260227031015_AddEstimatedMinutes.cs b/TaskTracker.Infrastructure/Migrations/20260227031015_AddEstimatedMinutes.cs new file mode 100644 index 0000000..be05235 --- /dev/null +++ b/TaskTracker.Infrastructure/Migrations/20260227031015_AddEstimatedMinutes.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TaskTracker.Infrastructure.Migrations +{ + /// + public partial class AddEstimatedMinutes : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EstimatedMinutes", + table: "Tasks", + type: "int", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "EstimatedMinutes", + table: "Tasks"); + } + } +} diff --git a/TaskTracker.Infrastructure/Migrations/TaskTrackerDbContextModelSnapshot.cs b/TaskTracker.Infrastructure/Migrations/TaskTrackerDbContextModelSnapshot.cs index 63f16fc..82d6797 100644 --- a/TaskTracker.Infrastructure/Migrations/TaskTrackerDbContextModelSnapshot.cs +++ b/TaskTracker.Infrastructure/Migrations/TaskTrackerDbContextModelSnapshot.cs @@ -189,6 +189,9 @@ namespace TaskTracker.Infrastructure.Migrations b.Property("Description") .HasColumnType("nvarchar(max)"); + b.Property("EstimatedMinutes") + .HasColumnType("int"); + b.Property("ParentTaskId") .HasColumnType("int");