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.
+5
-3
@@ -1,13 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<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>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -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",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"generate-api": "dotnet nswag run nswag.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.2.4",
|
||||
@@ -21,12 +22,14 @@
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"@vitejs/plugin-react-swc": "^4.3.0",
|
||||
"eslint": "^10.2.1",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.5.0",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.58.2",
|
||||
"vite": "^8.0.10"
|
||||
"vite": "^8.0.10",
|
||||
"vite-plugin-pwa": "^1.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
+100
-112
@@ -1,122 +1,110 @@
|
||||
import { useState } from 'react'
|
||||
import reactLogo from './assets/react.svg'
|
||||
import viteLogo from './assets/vite.svg'
|
||||
import heroImg from './assets/hero.png'
|
||||
import './App.css'
|
||||
import { useEffect, useState } from "react";
|
||||
import { fitnessApi, type Workout } from "./api/client";
|
||||
|
||||
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 (
|
||||
<>
|
||||
<section id="center">
|
||||
<div className="hero">
|
||||
<img src={heroImg} className="base" width="170" height="179" alt="" />
|
||||
<img src={reactLogo} className="framework" alt="React logo" />
|
||||
<img src={viteLogo} className="vite" alt="Vite logo" />
|
||||
</div>
|
||||
<div>
|
||||
<h1>Get started</h1>
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to test <code>HMR</code>
|
||||
</p>
|
||||
</div>
|
||||
<div className="min-h-screen bg-gray-950 text-white p-4 max-w-md mx-auto">
|
||||
<h1 className="text-2xl font-bold mb-4">🏋️ Fitness Tracker</h1>
|
||||
|
||||
{/* FORMULAR */}
|
||||
<form onSubmit={handleSubmit} className="bg-gray-900 p-4 rounded-lg mb-6 space-y-3">
|
||||
<input
|
||||
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="button"
|
||||
className="counter"
|
||||
onClick={() => setCount((count) => count + 1)}
|
||||
type="submit"
|
||||
className="w-full bg-green-600 hover:bg-green-500 p-2 rounded font-bold transition"
|
||||
>
|
||||
Count is {count}
|
||||
💪 Workout speichern
|
||||
</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<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 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>
|
||||
</section>
|
||||
|
||||
<div className="ticks"></div>
|
||||
<section id="spacer"></section>
|
||||
</>
|
||||
)
|
||||
{/* 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>
|
||||
<h3 className="font-bold">{w.name}</h3>
|
||||
<p className="text-sm text-gray-400">
|
||||
{new Date(w.date).toLocaleDateString("de-DE")} • {w.durationMinutes} Min
|
||||
</p>
|
||||
{w.notes && <p className="text-sm text-gray-500 mt-1">{w.notes}</p>}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => w.id && handleDelete(w.id)}
|
||||
className="text-red-500 hover:text-red-400 text-sm ml-2 shrink-0"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 react from '@vitejs/plugin-react-swc'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
|
||||
export default defineConfig({
|
||||
base: "/app/",
|
||||
plugins: [
|
||||
react(),
|
||||
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