Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.14">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.14" />
|
||||||
|
<PackageReference Include="NSwag.AspNetCore" Version="14.1.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Api.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260506065055_InitialCreate")]
|
||||||
|
partial class InitialCreate
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.14");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Workout", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("DurationMinutes")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Workouts");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Api.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class InitialCreate : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Workouts",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Date = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
DurationMinutes = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Notes = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Workouts", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Workouts");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Api.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
partial class AppDbContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.14");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Workout", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("DurationMinutes")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Workouts");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddDbContext<AppDbContext>(options =>
|
||||||
|
options.UseSqlite("Data Source=fitness.db"));
|
||||||
|
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddOpenApiDocument();
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseOpenApi();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// CRUD ENDPUNKTE (minimal, ohne Auth)
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
// CREATE
|
||||||
|
app.MapPost("/api/workouts", async (Workout workout, AppDbContext db) =>
|
||||||
|
{
|
||||||
|
workout.Id = Guid.NewGuid();
|
||||||
|
db.Workouts.Add(workout);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return Results.Created($"/api/workouts/{workout.Id}", workout);
|
||||||
|
});
|
||||||
|
|
||||||
|
// READ ALL
|
||||||
|
app.MapGet("/api/workouts", async (AppDbContext db) =>
|
||||||
|
await db.Workouts.OrderByDescending(w => w.Date).ToListAsync());
|
||||||
|
|
||||||
|
// READ BY ID
|
||||||
|
app.MapGet("/api/workouts/{id:guid}", async (Guid id, AppDbContext db) =>
|
||||||
|
await db.Workouts.FindAsync(id) is Workout w ? Results.Ok(w) : Results.NotFound());
|
||||||
|
|
||||||
|
// UPDATE
|
||||||
|
app.MapPut("/api/workouts/{id:guid}", async (Guid id, Workout input, AppDbContext db) =>
|
||||||
|
{
|
||||||
|
var workout = await db.Workouts.FindAsync(id);
|
||||||
|
if (workout is null) return Results.NotFound();
|
||||||
|
|
||||||
|
workout.Name = input.Name;
|
||||||
|
workout.Date = input.Date;
|
||||||
|
workout.DurationMinutes = input.DurationMinutes;
|
||||||
|
workout.Notes = input.Notes;
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return Results.Ok(workout);
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
app.MapDelete("/api/workouts/{id:guid}", async (Guid id, AppDbContext db) =>
|
||||||
|
{
|
||||||
|
var workout = await db.Workouts.FindAsync(id);
|
||||||
|
if (workout is null) return Results.NotFound();
|
||||||
|
|
||||||
|
db.Workouts.Remove(workout);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return Results.NoContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.MapGet("/", () => "🏋️ Fitness API läuft!");
|
||||||
|
app.Run();
|
||||||
|
|
||||||
|
public class AppDbContext : DbContext
|
||||||
|
{
|
||||||
|
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
|
||||||
|
public DbSet<Workout> Workouts => Set<Workout>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:16915",
|
||||||
|
"sslPort": 44327
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "http://localhost:5107",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "https://localhost:7079;http://localhost:5107",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
public class Workout
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public int DurationMinutes { get; set; }
|
||||||
|
public string? Notes { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
+4
-2
@@ -1,10 +1,12 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>web</title>
|
<meta name="theme-color" content="#030712" />
|
||||||
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
|
<title>Fitness Tracker</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"runtime": "Net80",
|
||||||
|
"documentGenerator": {
|
||||||
|
"fromDocument": {
|
||||||
|
"url": "http://localhost:5107/swagger/v1/swagger.json",
|
||||||
|
"output": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"codeGenerators": {
|
||||||
|
"openApiToTypeScriptClient": {
|
||||||
|
"className": "FitnessClient",
|
||||||
|
"typeScriptVersion": 5.0,
|
||||||
|
"template": "Fetch",
|
||||||
|
"promiseType": "Promise",
|
||||||
|
"httpClass": "HttpClient",
|
||||||
|
"dateTimeType": "Date",
|
||||||
|
"nullValue": "Undefined",
|
||||||
|
"generateClientClasses": true,
|
||||||
|
"generateClientInterfaces": false,
|
||||||
|
"exportTypes": true,
|
||||||
|
"generateDtoTypes": true,
|
||||||
|
"operationGenerationMode": "MultipleClientsFromOperationId",
|
||||||
|
"markOptionalProperties": true,
|
||||||
|
"typeStyle": "Interface",
|
||||||
|
"enumStyle": "Enum",
|
||||||
|
"useAbortSignal": false,
|
||||||
|
"output": "src/api/client.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,8 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"generate-api": "dotnet nswag run nswag.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.2.4",
|
"@tailwindcss/vite": "^4.2.4",
|
||||||
@@ -21,12 +22,14 @@
|
|||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
|
"@vitejs/plugin-react-swc": "^4.3.0",
|
||||||
"eslint": "^10.2.1",
|
"eslint": "^10.2.1",
|
||||||
"eslint-plugin-react-hooks": "^7.1.1",
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"globals": "^17.5.0",
|
"globals": "^17.5.0",
|
||||||
"typescript": "~6.0.2",
|
"typescript": "~6.0.2",
|
||||||
"typescript-eslint": "^8.58.2",
|
"typescript-eslint": "^8.58.2",
|
||||||
"vite": "^8.0.10"
|
"vite": "^8.0.10",
|
||||||
|
"vite-plugin-pwa": "^1.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+96
-108
@@ -1,122 +1,110 @@
|
|||||||
import { useState } from 'react'
|
import { useEffect, useState } from "react";
|
||||||
import reactLogo from './assets/react.svg'
|
import { fitnessApi, type Workout } from "./api/client";
|
||||||
import viteLogo from './assets/vite.svg'
|
|
||||||
import heroImg from './assets/hero.png'
|
|
||||||
import './App.css'
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [count, setCount] = useState(0)
|
const [workouts, setWorkouts] = useState<Workout[]>([]);
|
||||||
|
const [form, setForm] = useState<Workout>({
|
||||||
|
name: "",
|
||||||
|
date: new Date().toISOString().slice(0, 10),
|
||||||
|
durationMinutes: 30,
|
||||||
|
notes: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Workouts laden
|
||||||
|
const loadWorkouts = async () => {
|
||||||
|
const data = await fitnessApi.getWorkouts();
|
||||||
|
setWorkouts(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadWorkouts();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Formular absenden
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await fitnessApi.createWorkout({
|
||||||
|
...form,
|
||||||
|
date: new Date(form.date).toISOString(),
|
||||||
|
});
|
||||||
|
setForm({ name: "", date: new Date().toISOString().slice(0, 10), durationMinutes: 30, notes: "" });
|
||||||
|
loadWorkouts();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Workout löschen
|
||||||
|
const handleDelete = async (id: string) => {
|
||||||
|
await fitnessApi.deleteWorkout(id);
|
||||||
|
loadWorkouts();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="min-h-screen bg-gray-950 text-white p-4 max-w-md mx-auto">
|
||||||
<section id="center">
|
<h1 className="text-2xl font-bold mb-4">🏋️ Fitness Tracker</h1>
|
||||||
<div className="hero">
|
|
||||||
<img src={heroImg} className="base" width="170" height="179" alt="" />
|
{/* FORMULAR */}
|
||||||
<img src={reactLogo} className="framework" alt="React logo" />
|
<form onSubmit={handleSubmit} className="bg-gray-900 p-4 rounded-lg mb-6 space-y-3">
|
||||||
<img src={viteLogo} className="vite" alt="Vite logo" />
|
<input
|
||||||
</div>
|
type="text"
|
||||||
|
placeholder="Workout Name"
|
||||||
|
value={form.name}
|
||||||
|
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||||
|
className="w-full p-2 rounded bg-gray-800 border border-gray-700"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={form.date}
|
||||||
|
onChange={(e) => setForm({ ...form, date: e.target.value })}
|
||||||
|
className="w-full p-2 rounded bg-gray-800 border border-gray-700"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
placeholder="Dauer (Minuten)"
|
||||||
|
value={form.durationMinutes}
|
||||||
|
onChange={(e) => setForm({ ...form, durationMinutes: +e.target.value })}
|
||||||
|
className="w-full p-2 rounded bg-gray-800 border border-gray-700"
|
||||||
|
min="1"
|
||||||
|
/>
|
||||||
|
<textarea
|
||||||
|
placeholder="Notizen (optional)"
|
||||||
|
value={form.notes}
|
||||||
|
onChange={(e) => setForm({ ...form, notes: e.target.value })}
|
||||||
|
className="w-full p-2 rounded bg-gray-800 border border-gray-700"
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-green-600 hover:bg-green-500 p-2 rounded font-bold transition"
|
||||||
|
>
|
||||||
|
💪 Workout speichern
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* LISTE */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
{workouts.length === 0 && (
|
||||||
|
<p className="text-gray-500 text-center">Noch keine Workouts. Leg los! 🏃</p>
|
||||||
|
)}
|
||||||
|
{workouts.map((w) => (
|
||||||
|
<div key={w.id} className="bg-gray-900 p-4 rounded-lg flex justify-between items-start">
|
||||||
<div>
|
<div>
|
||||||
<h1>Get started</h1>
|
<h3 className="font-bold">{w.name}</h3>
|
||||||
<p>
|
<p className="text-sm text-gray-400">
|
||||||
Edit <code>src/App.tsx</code> and save to test <code>HMR</code>
|
{new Date(w.date).toLocaleDateString("de-DE")} • {w.durationMinutes} Min
|
||||||
</p>
|
</p>
|
||||||
|
{w.notes && <p className="text-sm text-gray-500 mt-1">{w.notes}</p>}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
onClick={() => w.id && handleDelete(w.id)}
|
||||||
className="counter"
|
className="text-red-500 hover:text-red-400 text-sm ml-2 shrink-0"
|
||||||
onClick={() => setCount((count) => count + 1)}
|
|
||||||
>
|
>
|
||||||
Count is {count}
|
✕
|
||||||
</button>
|
</button>
|
||||||
</section>
|
|
||||||
|
|
||||||
<div className="ticks"></div>
|
|
||||||
|
|
||||||
<section id="next-steps">
|
|
||||||
<div id="docs">
|
|
||||||
<svg className="icon" role="presentation" aria-hidden="true">
|
|
||||||
<use href="/icons.svg#documentation-icon"></use>
|
|
||||||
</svg>
|
|
||||||
<h2>Documentation</h2>
|
|
||||||
<p>Your questions, answered</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://vite.dev/" target="_blank">
|
|
||||||
<img className="logo" src={viteLogo} alt="" />
|
|
||||||
Explore Vite
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://react.dev/" target="_blank">
|
|
||||||
<img className="button-icon" src={reactLogo} alt="" />
|
|
||||||
Learn more
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="social">
|
))}
|
||||||
<svg className="icon" role="presentation" aria-hidden="true">
|
|
||||||
<use href="/icons.svg#social-icon"></use>
|
|
||||||
</svg>
|
|
||||||
<h2>Connect with us</h2>
|
|
||||||
<p>Join the Vite community</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/vitejs/vite" target="_blank">
|
|
||||||
<svg
|
|
||||||
className="button-icon"
|
|
||||||
role="presentation"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use href="/icons.svg#github-icon"></use>
|
|
||||||
</svg>
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://chat.vite.dev/" target="_blank">
|
|
||||||
<svg
|
|
||||||
className="button-icon"
|
|
||||||
role="presentation"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use href="/icons.svg#discord-icon"></use>
|
|
||||||
</svg>
|
|
||||||
Discord
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://x.com/vite_js" target="_blank">
|
|
||||||
<svg
|
|
||||||
className="button-icon"
|
|
||||||
role="presentation"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use href="/icons.svg#x-icon"></use>
|
|
||||||
</svg>
|
|
||||||
X.com
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://bsky.app/profile/vite.dev" target="_blank">
|
|
||||||
<svg
|
|
||||||
className="button-icon"
|
|
||||||
role="presentation"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use href="/icons.svg#bluesky-icon"></use>
|
|
||||||
</svg>
|
|
||||||
Bluesky
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
);
|
||||||
<div className="ticks"></div>
|
|
||||||
<section id="spacer"></section>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App
|
export default App;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// apps/web/src/api/client.ts
|
||||||
|
|
||||||
|
// const API_BASE = "http://localhost:5107";
|
||||||
|
const API_BASE = "http://192.168.178.189:5107";
|
||||||
|
|
||||||
|
export interface Workout {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
date: string;
|
||||||
|
durationMinutes: number;
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fitnessApi = {
|
||||||
|
// Alle Workouts abrufen
|
||||||
|
async getWorkouts(): Promise<Workout[]> {
|
||||||
|
const res = await fetch(`${API_BASE}/api/workouts`);
|
||||||
|
return res.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Einzelnes Workout abrufen
|
||||||
|
async getWorkout(id: string): Promise<Workout> {
|
||||||
|
const res = await fetch(`${API_BASE}/api/workouts/${id}`);
|
||||||
|
if (!res.ok) throw new Error("Not found");
|
||||||
|
return res.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Neues Workout erstellen
|
||||||
|
async createWorkout(workout: Workout): Promise<Workout> {
|
||||||
|
const res = await fetch(`${API_BASE}/api/workouts`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(workout),
|
||||||
|
});
|
||||||
|
return res.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Workout aktualisieren
|
||||||
|
async updateWorkout(id: string, workout: Workout): Promise<Workout> {
|
||||||
|
const res = await fetch(`${API_BASE}/api/workouts/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(workout),
|
||||||
|
});
|
||||||
|
return res.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Workout löschen
|
||||||
|
async deleteWorkout(id: string): Promise<void> {
|
||||||
|
await fetch(`${API_BASE}/api/workouts/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
+31
-4
@@ -1,10 +1,37 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import react from '@vitejs/plugin-react-swc'
|
import react from "@vitejs/plugin-react-swc";
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
base: "/app/",
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
|
VitePWA({
|
||||||
|
registerType: "autoUpdate",
|
||||||
|
manifest: {
|
||||||
|
name: "Fitness Tracker",
|
||||||
|
short_name: "Fitness",
|
||||||
|
description: "Minimaler Workout-Tracker",
|
||||||
|
theme_color: "#030712",
|
||||||
|
background_color: "#030712",
|
||||||
|
display: "standalone",
|
||||||
|
start_url: "/app/",
|
||||||
|
scope: "/app/",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/icon-192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/icon-512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.2.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "apps", "apps", "{1787FE1D-075E-9E68-7218-25F1BD1BBEAB}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "apps\api\Api.csproj", "{3FC7EC71-31D1-712E-1320-384AD08193E8}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{3FC7EC71-31D1-712E-1320-384AD08193E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3FC7EC71-31D1-712E-1320-384AD08193E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3FC7EC71-31D1-712E-1320-384AD08193E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3FC7EC71-31D1-712E-1320-384AD08193E8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{3FC7EC71-31D1-712E-1320-384AD08193E8} = {1787FE1D-075E-9E68-7218-25F1BD1BBEAB}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {2C003E0A-78A4-4BC7-9682-A39A864747B6}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
Generated
+3215
-8
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user