feat: add delete functionality for exercises and routines
This commit is contained in:
@@ -79,6 +79,23 @@ internal static class ExerciseEndpoints
|
||||
.WithSummary("Update exercise")
|
||||
.WithDescription("Updates the name of an exercise for the specified user.");
|
||||
|
||||
group.MapDelete("/users/{userId}/exercises/{exerciseId:int}", async (string userId, int exerciseId, AppDbContext db) =>
|
||||
{
|
||||
var user = await UserProvisioning.EnsureUserAsync(db, userId);
|
||||
var exercise = await db.Exercises.FirstOrDefaultAsync(e => e.Id == exerciseId && e.UserId == user.Id);
|
||||
if (exercise is null)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
db.Exercises.Remove(exercise);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return Results.NoContent();
|
||||
})
|
||||
.WithSummary("Delete exercise")
|
||||
.WithDescription("Deletes an exercise for the specified user.");
|
||||
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +160,23 @@ internal static class RoutineEndpoints
|
||||
.WithSummary("Update routine")
|
||||
.WithDescription("Updates routine metadata and exercise ordering.");
|
||||
|
||||
group.MapDelete("/users/{userId}/routines/{routineId:int}", async (string userId, int routineId, AppDbContext db) =>
|
||||
{
|
||||
var user = await UserProvisioning.EnsureUserAsync(db, userId);
|
||||
var routine = await db.Routines.FirstOrDefaultAsync(r => r.Id == routineId && r.UserId == user.Id);
|
||||
if (routine is null)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
db.Routines.Remove(routine);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return Results.NoContent();
|
||||
})
|
||||
.WithSummary("Delete routine")
|
||||
.WithDescription("Deletes a routine and its associated data for the specified user.");
|
||||
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,10 @@
|
||||
else
|
||||
{
|
||||
<div class="item-title">@exercise.Name</div>
|
||||
<button class="ghost" @onclick="() => StartEdit(exercise)">Edit</button>
|
||||
<div class="actions">
|
||||
<button class="ghost" @onclick="() => StartEdit(exercise)" aria-label="Edit exercise">✏️</button>
|
||||
<button class="ghost" @onclick="() => DeleteExerciseAsync(exercise.Id)" aria-label="Delete exercise">🗑️</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -148,4 +151,10 @@
|
||||
|
||||
CancelEdit();
|
||||
}
|
||||
|
||||
private async Task DeleteExerciseAsync(int exerciseId)
|
||||
{
|
||||
await Api.DeleteExerciseAsync(UserContext.UserId, exerciseId);
|
||||
ExerciseList.RemoveAll(e => e.Id == exerciseId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,53 +25,60 @@
|
||||
</section>
|
||||
}
|
||||
|
||||
@if (ShowCreateRoutine)
|
||||
@if (EditingRoutine is null)
|
||||
{
|
||||
<section class="card">
|
||||
<h2>Create Routine</h2>
|
||||
<input class="input" placeholder="Routine name" @bind="NewRoutineName" @bind:event="oninput" />
|
||||
<div class="list">
|
||||
@foreach (var exercise in ExerciseList)
|
||||
{
|
||||
<label class="checkbox-row">
|
||||
<input type="checkbox" checked="@SelectedExerciseIds.Contains(exercise.Id)" @onchange="() => ToggleExercise(exercise.Id)" />
|
||||
<span>@exercise.Name</span>
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
<button class="primary" @onclick="CreateRoutineAsync" disabled="@string.IsNullOrWhiteSpace(NewRoutineName)">Save Routine</button>
|
||||
</section>
|
||||
}
|
||||
@if (ShowCreateRoutine)
|
||||
{
|
||||
<section class="card">
|
||||
<h2>Create Routine</h2>
|
||||
<input class="input" placeholder="Routine name" @bind="NewRoutineName" @bind:event="oninput" />
|
||||
<div class="list">
|
||||
@foreach (var exercise in ExerciseList)
|
||||
{
|
||||
<label class="checkbox-row">
|
||||
<input type="checkbox" checked="@SelectedExerciseIds.Contains(exercise.Id)" @onchange="() => ToggleExercise(exercise.Id)" />
|
||||
<span>@exercise.Name</span>
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
<button class="primary" @onclick="CreateRoutineAsync" disabled="@string.IsNullOrWhiteSpace(NewRoutineName)">Save Routine</button>
|
||||
</section>
|
||||
}
|
||||
|
||||
<section class="card">
|
||||
<h2>Your Routines</h2>
|
||||
@if (IsLoading)
|
||||
@if (ExerciseList.Count > 0 || RoutineList.Count > 0)
|
||||
{
|
||||
<p>Loading...</p>
|
||||
}
|
||||
else if (RoutineList.Count == 0)
|
||||
{
|
||||
<p class="muted">No routines yet. Create one above.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="list">
|
||||
@foreach (var routine in RoutineList)
|
||||
<section class="card">
|
||||
<h2>Your Routines</h2>
|
||||
@if (IsLoading)
|
||||
{
|
||||
<div class="list-item">
|
||||
<div>
|
||||
<div class="item-title">@routine.Name</div>
|
||||
<div class="item-subtitle">@string.Join(" · ", routine.Exercises.Select(e => e.Name))</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="ghost" @onclick="() => StartEdit(routine)">Edit</button>
|
||||
<button class="primary" @onclick="() => StartRun(routine)">Start</button>
|
||||
</div>
|
||||
<p>Loading...</p>
|
||||
}
|
||||
else if (RoutineList.Count == 0)
|
||||
{
|
||||
<p class="muted">No routines yet. Create one above.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="list">
|
||||
@foreach (var routine in RoutineList)
|
||||
{
|
||||
<div class="list-item">
|
||||
<div>
|
||||
<div class="item-title">@routine.Name</div>
|
||||
<div class="item-subtitle">@string.Join(" · ", routine.Exercises.Select(e => e.Name))</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="ghost" @onclick="() => StartEdit(routine)" aria-label="Edit routine">✏️</button>
|
||||
<button class="ghost" @onclick="() => DeleteRoutineAsync(routine.Id)" aria-label="Delete routine">🗑️</button>
|
||||
<button class="primary" @onclick="() => StartRun(routine)">Start</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</section>
|
||||
}
|
||||
|
||||
@if (EditingRoutine is not null)
|
||||
{
|
||||
@@ -246,6 +253,12 @@
|
||||
CancelEdit();
|
||||
}
|
||||
|
||||
private async Task DeleteRoutineAsync(int routineId)
|
||||
{
|
||||
await Api.DeleteRoutineAsync(UserContext.UserId, routineId);
|
||||
RoutineList.RemoveAll(r => r.Id == routineId);
|
||||
}
|
||||
|
||||
private async Task StartRun(RoutineDto routine)
|
||||
{
|
||||
ActiveRun = routine;
|
||||
|
||||
@@ -37,6 +37,11 @@ public class ApiClient
|
||||
return await response.Content.ReadFromJsonAsync<ExerciseDto>();
|
||||
}
|
||||
|
||||
public async Task DeleteExerciseAsync(string userId, int exerciseId)
|
||||
{
|
||||
await _http.DeleteAsync($"/api/users/{userId}/exercises/{exerciseId}");
|
||||
}
|
||||
|
||||
public async Task<List<RoutineDto>> GetRoutinesAsync(string userId)
|
||||
{
|
||||
return await _http.GetFromJsonAsync<List<RoutineDto>>($"/api/users/{userId}/routines") ?? new List<RoutineDto>();
|
||||
@@ -59,6 +64,11 @@ public class ApiClient
|
||||
return await response.Content.ReadFromJsonAsync<RoutineDto>();
|
||||
}
|
||||
|
||||
public async Task DeleteRoutineAsync(string userId, int routineId)
|
||||
{
|
||||
await _http.DeleteAsync($"/api/users/{userId}/routines/{routineId}");
|
||||
}
|
||||
|
||||
public async Task<RoutineRunSummaryDto> GetLastRunAsync(string userId, int routineId)
|
||||
{
|
||||
return await _http.GetFromJsonAsync<RoutineRunSummaryDto>($"/api/users/{userId}/routines/{routineId}/last-run")
|
||||
|
||||
Reference in New Issue
Block a user