-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from Intrepiware/add-workout-history
Add workout history
- Loading branch information
Showing
14 changed files
with
271 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { useEffect, useState } from "react"; | ||
import { WorkoutListItem, getUserWorkouts } from "../apis/workoutListItem"; | ||
|
||
interface QueryCriteria { | ||
take: number; | ||
skip: number; | ||
favorites: boolean; | ||
} | ||
|
||
function WorkoutsIndex() { | ||
const [query, setQuery] = useState<QueryCriteria>({ | ||
skip: 0, | ||
take: 26, | ||
favorites: false, | ||
}); | ||
const [workouts, setWorkouts] = useState<WorkoutListItem[]>([]); | ||
|
||
useEffect(() => { | ||
getUserWorkouts(query.skip, query.take, query.favorites).then( | ||
(data: WorkoutListItem[]) => setWorkouts(data) | ||
); | ||
}, [query]); | ||
|
||
const setFavorite = (id: string) => { | ||
if (!!id) { | ||
fetch(`/Workouts/Favorite/${id}`, { | ||
method: "POST", | ||
credentials: "include", | ||
}) | ||
.then((res) => res.json()) | ||
.then((json) => { | ||
const workoutsClone = [...workouts]; | ||
const index = workoutsClone.findIndex((x) => x.publicId == id); | ||
workoutsClone[index].isFavorite = json.isFavorite; | ||
setWorkouts(workoutsClone); | ||
}); | ||
} | ||
}; | ||
|
||
const formatDate = (dateString: string) => { | ||
const date = new Date(`${dateString}Z`); | ||
const hoursSince = | ||
(new Date().getTime() - date.getTime()) / (1000 * 60 * 60); | ||
return hoursSince >= 24 | ||
? date.toLocaleDateString() | ||
: date.toLocaleTimeString(); | ||
}; | ||
|
||
return ( | ||
<section className="section"> | ||
<div className="container is-max-desktop"> | ||
<div className="buttons has-addons"> | ||
<button | ||
className={`button ${query.favorites ? "is-info" : ""}`} | ||
onClick={() => setQuery((x) => ({ ...x, favorites: true }))} | ||
> | ||
Favorites | ||
</button> | ||
<button | ||
className={`button ${query.favorites ? "" : "is-info"}`} | ||
onClick={() => setQuery((x) => ({ ...x, favorites: false }))} | ||
> | ||
All | ||
</button> | ||
</div> | ||
<div className="table-container"> | ||
<table className="table is-fullwidth"> | ||
<thead> | ||
<tr> | ||
<th>Name</th> | ||
<th>Created</th> | ||
<th className="has-text-centered">Favorite</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{workouts && | ||
workouts.slice(0, 25).map((x) => ( | ||
<tr key={x.id}> | ||
<td> | ||
<a href={`${document.location.origin}?id=${x.publicId}`}> | ||
{x.name} | ||
</a> | ||
</td> | ||
<td>{formatDate(x.createDate)}</td> | ||
<td className="has-text-centered"> | ||
<a | ||
className="button" | ||
onClick={() => setFavorite(x.publicId)} | ||
> | ||
<span className="icon is-small"> | ||
<span | ||
className={`material-symbols-outlined ${ | ||
x.isFavorite ? "filled-star" : "" | ||
}`} | ||
> | ||
star | ||
</span> | ||
</span> | ||
</a> | ||
</td> | ||
</tr> | ||
))} | ||
</tbody> | ||
</table> | ||
</div> | ||
<div className="buttons is-right"> | ||
<button | ||
className="button is-info" | ||
disabled={query.skip == 0} | ||
onClick={() => setQuery((x) => ({ ...x, skip: x.skip - 25 }))} | ||
> | ||
Prev | ||
</button> | ||
<button | ||
className="button is-info" | ||
disabled={workouts.length < 26} | ||
onClick={() => setQuery((x) => ({ ...x, skip: x.skip + 25 }))} | ||
> | ||
Next | ||
</button> | ||
</div> | ||
</div> | ||
</section> | ||
); | ||
} | ||
|
||
export default WorkoutsIndex; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export interface WorkoutListItem { | ||
id: number; | ||
publicId: string; | ||
createDate: string; | ||
isFavorite: boolean; | ||
name: string; | ||
} | ||
|
||
export function getUserWorkouts( | ||
skip: number, | ||
take: number, | ||
favorites: boolean | ||
): Promise<WorkoutListItem[]> { | ||
const takeParam = `?take=${take}`; | ||
const skipParam = skip > 0 ? `&skip=${skip}` : ""; | ||
const favoritesParam = favorites ? "&onlyFavorites=true" : ""; | ||
return fetch(`/Workouts${takeParam}${skipParam}${favoritesParam}`, { | ||
credentials: "include", | ||
headers: { "X-Requested-With": "XMLHttpRequest" }, | ||
}).then((res) => res.json()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import React from "react"; | ||
import ReactDOM from "react-dom/client"; | ||
import WorkoutsIndex from "./Pages/WorkoutsIndex"; | ||
|
||
ReactDOM.createRoot(document.getElementById("root")!).render( | ||
<React.StrictMode> | ||
<WorkoutsIndex /> | ||
</React.StrictMode> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Newtonsoft.Json; | ||
using WorkoutBuilder.Data; | ||
using WorkoutBuilder.Models; | ||
using WorkoutBuilder.Services; | ||
using WorkoutBuilder.Services.ExtensionMethods; | ||
using WorkoutBuilder.Services.Impl; | ||
using WorkoutBuilder.Services.Models; | ||
|
||
namespace WorkoutBuilder.Controllers | ||
{ | ||
[Authorize] | ||
public class WorkoutsController : Controller | ||
{ | ||
public IRepository<Workout> WorkoutRepository { init; protected get; } = null!; | ||
public IUserContext UserContext { init; protected get; } = null!; | ||
public IWorkoutService WorkoutService { protected get; init; } = null!; | ||
public IUrlBuilder UrlBuilder { protected get; init; } = null!; | ||
|
||
public IActionResult Index(int take = 25, int skip = 0, bool onlyFavorites = false) | ||
{ | ||
if(Request.IsAjaxRequest()) | ||
{ | ||
var query = WorkoutRepository.GetAll().Where(x => x.UserId == UserContext.GetUserId().Value); | ||
|
||
if (onlyFavorites) | ||
query = query.Where(x => x.IsFavorite); | ||
|
||
var output = query | ||
.OrderByDescending(x => x.CreateDate) | ||
.Skip(skip).Take(take) | ||
.Select(x => new WorkoutListItemModel | ||
{ | ||
CreateDate = x.CreateDate, | ||
Id = x.Id, | ||
IsFavorite = x.IsFavorite, | ||
Name = JsonConvert.DeserializeObject<WorkoutGenerationResponseModel>(x.Body).Name, | ||
PublicId = x.PublicId | ||
}) | ||
.ToList(); | ||
|
||
return Json(output); | ||
} | ||
return View(); | ||
} | ||
|
||
[HttpPost] | ||
public async Task<IActionResult> Favorite(string id) | ||
{ | ||
var workout = await WorkoutService.ToggleFavorite(id); | ||
if (workout != null && workout.PublicId != id) | ||
Response.Headers.Add("Location", UrlBuilder.Action("Index", "Home", new { id = workout.PublicId })); | ||
return Json(new { success = true, workout.IsFavorite }); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
namespace WorkoutBuilder.Models | ||
{ | ||
public class WorkoutListItemModel | ||
{ | ||
public long Id { get; set; } | ||
public string PublicId { get; set; } | ||
public DateTime CreateDate { get; set; } | ||
public bool IsFavorite { get; set; } | ||
public string Name { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
WorkoutBuilder/Services/ExtensionMethods/HttpRequestExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
namespace WorkoutBuilder.Services.ExtensionMethods | ||
{ | ||
public static class HttpRequestExtensions | ||
{ | ||
/// <summary> | ||
/// Determines whether the specified HTTP request is an AJAX request. | ||
/// </summary> | ||
/// | ||
/// <returns> | ||
/// true if the specified HTTP request is an AJAX request; otherwise, false. | ||
/// </returns> | ||
/// <param name="request">The HTTP request.</param><exception cref="T:System.ArgumentNullException">The <paramref name="request"/> parameter is null (Nothing in Visual Basic).</exception> | ||
public static bool IsAjaxRequest(this HttpRequest request) | ||
{ | ||
if (request == null) | ||
throw new ArgumentNullException(nameof(request)); | ||
|
||
if (request.Headers != null) | ||
return request.Headers["X-Requested-With"] == "XMLHttpRequest"; | ||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
@{ | ||
ViewData["Title"] = "Workout Builder"; | ||
} | ||
|
||
<div id="root"></div> | ||
|
||
@section Scripts | ||
{ | ||
<script src="~/js/pages/workouts-index.js"></script> | ||
} |