diff --git a/WorkManagementTool/Components/ProjectTask/ProjectTaskCreation.razor b/WorkManagementTool/Components/ProjectTask/ProjectTaskCreation.razor new file mode 100644 index 0000000..a8344d0 --- /dev/null +++ b/WorkManagementTool/Components/ProjectTask/ProjectTaskCreation.razor @@ -0,0 +1,186 @@ +@page "/projects/tasks/creation/{ProjectId:int}" + +@using Microsoft.AspNetCore.Authorization +@using Services; +@using Data.Entities; +@using Microsoft.AspNetCore.Components.Forms + +@attribute [Authorize] + +@rendermode InteractiveServer + +@inject ProjectService ProjectService +@inject ProjectTaskService ProjectTaskService +@inject NavigationManager NavigationManager +@inject ILogger Logger + +

Project Task Creation

+ +@if (Project is null) +{ +

Project not found

+} +else +{ +
@Project.Name task creation
+ + + + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ + @if (Project.Tasks != null && Project.Tasks.Any()) + { +
+ +
+ @foreach (var t in Project.Tasks) + { +
+ + +
+ } +
+ @if (previousTasksError is not null) + { +
@previousTasksError
+ } +
+ } + + +
+} + +@code { + [Parameter] + public int ProjectId { get; set; } + + public Project? Project { get; set; } + public ProjectTask? Model { get; set; } + + private EditContext? editContext; + private ValidationMessageStore? messageStore; + + // contient les ids sélectionnés via les checkboxes + private HashSet selectedPreviousTaskIds = new(); + private string? previousTasksError; + + protected async override Task OnInitializedAsync() + { + Project = await ProjectService.GetProjectByIdAsync(ProjectId); + Model = new() + { + DueDate = DateTime.Now.AddDays(1) + }; + + editContext = new EditContext(Model!); + messageStore = new ValidationMessageStore(editContext); + + editContext.OnFieldChanged += (_, args) => + { + if (args.FieldIdentifier.FieldName == nameof(ProjectTask.DueDate)) + { + ValidatePreviousDates(); + } + }; + } + + private void TogglePreviousTask(ChangeEventArgs e, int id) + { + var isChecked = e?.Value is bool b && b; + if (!isChecked && e?.Value is string s) + isChecked = s == "true" || s == "on" || s == "checked"; + + if (isChecked) + selectedPreviousTaskIds.Add(id); + else + selectedPreviousTaskIds.Remove(id); + + ValidatePreviousDates(); + } + + private void ValidatePreviousDates() + { + if (messageStore is null || editContext is null || Model is null) + return; + + messageStore.Clear(); + + if (selectedPreviousTaskIds.Count > 0) + { + var invalids = new List(); + foreach (var id in selectedPreviousTaskIds) + { + var prev = Project?.Tasks?.FirstOrDefault(t => t.Id == id); + if (prev != null) + { + if (prev.DueDate >= Model.DueDate) + { + invalids.Add($"« {prev.Title} » due {prev.DueDate.ToShortDateString()} must be before the new task due date."); + } + } + } + + if (invalids.Any()) + { + messageStore.Add(new FieldIdentifier(Model, nameof(Model.DueDate)), invalids); + previousTasksError = "Certaines tâches précédentes ont une date d'échéance non cohérente."; + } + else + { + previousTasksError = null; + } + } + else + { + previousTasksError = null; + } + + editContext.NotifyValidationStateChanged(); + } + + private async Task Submit() + { + if (Model is null || Project is null || editContext is null) + return; + + ValidatePreviousDates(); + + var valid = editContext.Validate(); + if (!valid) + { + Logger.LogWarning("Project task form invalid."); + return; + } + + // On passe uniquement les ids au service (solution simple et sans conflit de tracking) + Model.Project = Project; + await ProjectTaskService.AddTaskAsync(Model, selectedPreviousTaskIds); + + NavigationManager.NavigateTo($"/projects/tasks/{ProjectId}"); + } +} diff --git a/WorkManagementTool/Components/ProjectTask/ProjectTaskListing.razor b/WorkManagementTool/Components/ProjectTask/ProjectTaskListing.razor index b60078d..eb58abb 100644 --- a/WorkManagementTool/Components/ProjectTask/ProjectTaskListing.razor +++ b/WorkManagementTool/Components/ProjectTask/ProjectTaskListing.razor @@ -8,14 +8,33 @@ @rendermode InteractiveServer @inject ProjectService ProjectService ++ + @if (Model is null) {

Project not found

} else { -

@ProjectId

@Model.Name

+ + @if (Model.Tasks is null || Model.Tasks.Count == 0) + { +

No task found in the project

+ } + else + { +
    + @foreach (ProjectTask task in Model.Tasks) + { +
  • +
    @task.Title
    +

    Created at: @task.CreatedAt, Due date : @task.DueDate

    + +
  • + } +
+ } } @code { diff --git a/WorkManagementTool/Data/Entities/ProjectTask.cs b/WorkManagementTool/Data/Entities/ProjectTask.cs index 7914eec..a4011bf 100644 --- a/WorkManagementTool/Data/Entities/ProjectTask.cs +++ b/WorkManagementTool/Data/Entities/ProjectTask.cs @@ -7,7 +7,7 @@ public string? Description { get; set; } public DateTime DueDate { get; set; } public bool IsCompleted { get; set; } - public List? NextTasks { get; set; } + public List? PreviousTasks { get; set; } public Project Project { get; set; } = null!; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; } diff --git a/WorkManagementTool/Services/ProjectTaskService.cs b/WorkManagementTool/Services/ProjectTaskService.cs index aa25233..12680b7 100644 --- a/WorkManagementTool/Services/ProjectTaskService.cs +++ b/WorkManagementTool/Services/ProjectTaskService.cs @@ -16,7 +16,7 @@ namespace WorkManagementTool.Services public async Task> GetAllTasksAsync(int projectId) { return await _context.ProjectTasks - .Include(t => t.NextTasks) + .Include(t => t.PreviousTasks) .Include(t => t.Project) .Where(w => w.Project.Id == projectId) .ToListAsync(); @@ -25,20 +25,82 @@ namespace WorkManagementTool.Services public async Task GetTaskByIdAsync(int taskId) { return await _context.ProjectTasks - .Include(t => t.NextTasks) + .Include(t => t.PreviousTasks) .Include(t => t.Project) .FirstOrDefaultAsync(t => t.Id == taskId); } - public async Task AddTaskAsync(ProjectTask task) + // Ajout : accepter une liste d'ids pour PreviousTasks afin d'éviter les conflits de tracking + public async Task AddTaskAsync(ProjectTask task, IEnumerable? previousIds = null) { + if (previousIds != null && previousIds.Any()) + { + var trackedPrev = await _context.ProjectTasks + .Where(t => previousIds.Contains(t.Id)) + .ToListAsync(); + + task.PreviousTasks = trackedPrev; + } + else + { + task.PreviousTasks = new List(); + } + _context.ProjectTasks.Add(task); await _context.SaveChangesAsync(); } - public async Task UpdateTaskAsync(ProjectTask task) + // Mise à jour : charger l'entité existante et appliquer les modifications + dépendances + public async Task UpdateTaskAsync(ProjectTask task, IEnumerable? previousIds = null) { - _context.ProjectTasks.Update(task); + // Récupérer l'entité suivie par le contexte + var existing = await _context.ProjectTasks + .Include(t => t.PreviousTasks) + .Include(t => t.Project) + .FirstOrDefaultAsync(t => t.Id == task.Id); + + if (existing is null) + { + // si l'entité n'existe pas encore dans la BDD, fallback : attacher et mettre à jour + if (previousIds != null && previousIds.Any()) + { + var trackedPrev = await _context.ProjectTasks + .Where(t => previousIds.Contains(t.Id)) + .ToListAsync(); + task.PreviousTasks = trackedPrev; + } + + _context.ProjectTasks.Update(task); + await _context.SaveChangesAsync(); + return; + } + + // Mettre à jour les propriétés scalaires + existing.Title = task.Title; + existing.Description = task.Description; + existing.DueDate = task.DueDate; + existing.IsCompleted = task.IsCompleted; + existing.CreatedAt = task.CreatedAt; + + // Mettre à jour la collection PreviousTasks selon les ids fournis + if (previousIds != null) + { + if (previousIds.Any()) + { + var trackedPrev = await _context.ProjectTasks + .Where(t => previousIds.Contains(t.Id)) + .ToListAsync(); + + // Remplacer la collection existante par les entités tracées + existing.PreviousTasks = trackedPrev; + } + else + { + existing.PreviousTasks = new List(); + } + } + + _context.ProjectTasks.Update(existing); await _context.SaveChangesAsync(); }