diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b6333b8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,53 @@ +# dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# testing +coverage + +# next.js +.next/ +out/ +build +dist + +# misc +.DS_Store +*.pem + +# debug +*.log + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo + +# git +.git +.gitignore + +# editor +.vscode +.idea + +# docker +Dockerfile +.dockerignore +docker-compose.yml + +# SQL scripts +*.sql + +# other +README.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9eb8cfb --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +# Docker Compose Environment Variables +# Kopieren Sie diese Datei nach .env und passen Sie die Werte an + +# MySQL Datenbankzugangsdaten +DB_USER=root +DB_PASS=IhrMySQLPasswort +DB_NAME=RXF + +# Build-Datum (wird automatisch beim Build gesetzt, Format: DD.MM.YYYY) +BUILD_DATE=$(date +%d.%m.%Y) diff --git a/.gitignore b/.gitignore index 5ef6a52..7b8da95 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +!.env.example # vercel .vercel diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..dc0b3a4 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +audit-level=high diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..791ae69 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,317 @@ +# Deployment Anleitung - Werte Next.js App + +## Voraussetzungen + +Auf dem Server benötigt: +- Docker (Version 20.10+) +- Docker Compose (Version 2.0+) +- MySQL Server (bereits installiert und laufend) +- RXF Datenbank mit Tabelle Werte_BZG muss bereits existieren + +## Deployment Schritte + +### 1. Projekt auf den Server übertragen + +```bash +# Auf dem Server ein Verzeichnis erstellen +mkdir -p /opt/werte-next +cd /opt/werte-next + +# Projekt per Git oder rsync übertragen +# Option A: Git +git clone . + +# Option B: rsync (von lokalem Rechner aus) +rsync -av --exclude 'node_modules' --exclude '.next' \ + /Users/rxf/REXFUE_APPS/werte_next/ user@server:/opt/werte-next/ +``` + +### 2. Umgebungsvariablen konfigurieren + +```bash +# .env-Datei aus Beispiel erstellen +cp .env.example .env + +# .env-Datei mit Ihren Zugangsdaten bearbeiten +nano .env +``` + +Passen Sie die Werte in der `.env`-Datei an: +```bash +DB_USER=root +DB_PASS=IhrMySQLPasswort +DB_NAME=RXF +``` + +**Wichtig**: Die `.env`-Datei enthält sensible Daten und wird NICHT ins Git-Repository committed! + +### 3. Docker Images bauen und Container starten + +```bash +cd /opt/werte-next + +# Images bauen und Container im Hintergrund starten +docker-compose up -d --build +``` + +### 4. Container-Status prüfen + +```bash +# Container-Status anzeigen +docker-compose ps + +# Logs anschauen +docker-compose logs -f werte-app +``` + +### 5. Datenbank prüfen + +```bash +# Direkt auf Host-MySQL verbinden +mysql -uroot -p RXF + +# Tabelle prüfen +SHOW TABLES; +DESCRIBE Werte_BZG; +SELECT COUNT(*) FROM Werte_BZG; + +# Oder von außen (falls MySQL externen Zugriff erlaubt) +mysql -h -uroot -p RXF +``` + +### 6. Anwendung im Browser öffnen + +``` +http://:3000 +``` + +## Konfiguration anpassen + +### Port ändern + +In `docker-compose.yml` den Port für die App ändern: +```yaml +ports: + - "8080:3000" # Zugriff über Port 8080 +``` + +### Datenbankverbindung anpassen + +In `docker-compose.yml` die Umgebungsvariablen ändern: +```yaml +environment: + - DB_HOST=host.docker.internal # Für Host-MySQL + - DB_USER=root # MySQL-Benutzer + - DB_PASS=IhrPasswort # MySQL-Passwort + - DB_NAME=RXF # Datenbankname +``` + +**Hinweis**: `host.docker.internal` zeigt auf den Docker-Host. Dies wird durch die `extra_hosts` Konfiguration ermöglicht. + +## Verwaltung + +### Container stoppen + +```bash +docker-compose stop +``` + +### Container starten + +```bash +docker-compose start +``` + +### Container neustarten + +```bash +docker-compose restart +``` + +### Container entfernen (Daten bleiben erhalten) + +```bash +docker-compose down +``` + +### Alles löschen inkl. Daten + +```bash +docker-compose down -v +``` + +### Updates einspielen + +```bash +# Neue Version holen +git pull # oder rsync + +# Neu bauen und starten +docker-compose up -d --build +``` + +### Logs anschauen + +```bash +# App-Logs +docker-compose logs -f werte-app + +# Letzte 100 Zeilen +docker-compose logs --tail=100 werte-app +``` + +## Backup + +### Datenbank-Backup erstellen + +```bash +# Direkt auf dem Host (empfohlen) +# Passwort wird interaktiv abgefragt +mysqldump -uroot -p RXF > backup_$(date +%Y%m%d).sql + +# Oder mit gzip komprimieren +mysqldump -uroot -p RXF | gzip > backup_$(date +%Y%m%d).sql.gz + +# Nur die Werte_BZG Tabelle +mysqldump -uroot -p RXF Werte_BZG > werte_backup_$(date +%Y%m%d).sql +``` + +### Datenbank wiederherstellen + +```bash +# Aus Backup wiederherstellen +mysql -uroot -p RXF < backup_20260222.sql + +# Aus komprimiertem Backup +gunzip < backup_20260222.sql.gz | mysql -uroot -p RXF +``` + +## Reverse Proxy (Optional) + +Für Produktionsumgebungen empfiehlt sich ein Reverse Proxy wie Nginx: + +### Nginx-Konfiguration Beispiel + +```nginx +server { + listen 80; + server_name werte.example.com; + + location / { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + +### Mit SSL (Let's Encrypt) + +```bash +# Certbot installieren +apt install certbot python3-certbot-nginx + +# Zertifikat erstellen +certbot --nginx -d werte.example.com +``` + +## Troubleshooting + +### Container startet nicht + +```bash +# Detaillierte Logs anschauen +docker-compose logs werte-app + +# Container neu bauen +docker-compose build --no-cache werte-app +docker-compose up -d +``` + +### Datenbankverbindung schlägt fehl + +```bash +# Prüfen ob MySQL auf dem Host läuft +sudo systemctl status mysql +# oder +sudo service mysql status + +# MySQL Error Log prüfen +sudo tail -f /var/log/mysql/error.log + +# Verbindung vom Docker-Container testen +docker exec -it werte-next-app sh +# Im Container: +ping host.docker.internal +``` + +**Hinweis**: Wenn `host.docker.internal` nicht funktioniert, können Sie stattdessen die Server-IP verwenden: +```yaml +environment: + - DB_HOST=192.168.1.100 # Ersetzen mit tatsächlicher Server-IP +``` + +### App ist langsam + +```bash +# Ressourcen-Nutzung prüfen +docker stats + +# Container neu starten +docker-compose restart werte-app +``` + +## Systemd Service (Optional) + +Für automatischen Start beim Server-Reboot: + +```bash +# Service-Datei erstellen +sudo nano /etc/systemd/system/werte-next.service +``` + +Inhalt: +```ini +[Unit] +Description=Werte Next.js Application +After=docker.service +Requires=docker.service + +[Service] +Type=oneshot +RemainAfterExit=yes +WorkingDirectory=/opt/werte-next +ExecStart=/usr/bin/docker-compose up -d +ExecStop=/usr/bin/docker-compose down +TimeoutStartSec=0 + +[Install] +WantedBy=multi-user.target +``` + +Service aktivieren: +```bash +sudo systemctl enable werte-next +sudo systemctl start werte-next +sudo systemctl status werte-next +``` + +## Monitoring + +CPU und Memory-Nutzung überwachen: +```bash +docker stats werte-next-app +``` + +## Support + +Bei Problemen: +- Logs prüfen: `docker-compose logs -f` +- Container neu starten: `docker-compose restart` +- Issues auf GitHub melden diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e6ab089 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +# Multi-stage build for Next.js application +FROM node:22-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Copy package files +COPY package.json package-lock.json* ./ +RUN npm ci + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Set build date as build argument +ARG BUILD_DATE +ENV NEXT_PUBLIC_BUILD_DATE=${BUILD_DATE} + +# Disable telemetry during build +ENV NEXT_TELEMETRY_DISABLED=1 + +# Build the application +RUN npm run build + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy necessary files +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/README.md b/README.md index e215bc4..624c6d5 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,185 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Werte Next.js - Gesundheitsdaten Tracking -## Getting Started +Eine moderne Next.js-Anwendung zur Erfassung und Verwaltung von Gesundheitsdaten, basierend auf dem ursprünglichen Werte-Projekt. -First, run the development server: +## Features + +- ✅ **Eingabeformular** für Gesundheitsdaten: + - Datum und Zeit + - Blutzucker (mg/dl) + - Mahlzeiten-Informationen + - Gewicht (kg) + - Blutdruck (systolisch/diastolisch in mmHg) + - Puls + +- ✅ **Anzeige der letzten 10 Einträge** mit Löschfunktion +- ✅ **Responsive Design** mit Tailwind CSS +- ✅ **TypeScript** für Type-Safety +- ✅ **MySQL-Datenbankanbindung** (Docker-Container) + +## Technologie-Stack + +- **Framework**: Next.js 16.x (App Router) +- **Sprache**: TypeScript +- **Styling**: Tailwind CSS +- **Datenbank**: MySQL 5.6 (Docker) +- **ORM**: mysql2 + +## Voraussetzungen + +- Node.js 18.x oder höher +- Docker (für MySQL-Container) +- Die MySQL-Datenbank muss bereits laufen (siehe docker-compose.yml im übergeordneten Verzeichnis) + +## Installation + +1. **Dependencies installieren**: + ```bash + npm install + ``` + +2. **Umgebungsvariablen konfigurieren**: + Die Datei `.env.local` ist bereits mit den korrekten Datenbankzugangsdaten angelegt: + ``` + DB_HOST=mydbase_mysql + DB_USER=root + DB_PASS=SFluorit + DB_NAME=RXF + ``` + +3. **Datenbank prüfen**: + Stelle sicher, dass die MySQL-Datenbank läuft und die Tabelle `RXF.Werte_BZG` existiert. + +## Entwicklung + +Development-Server starten: ```bash npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +Die Anwendung ist dann unter [http://localhost:3000](http://localhost:3000) erreichbar. -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +## Produktion -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +### Lokale Produktion -## Learn More +Build erstellen: -To learn more about Next.js, take a look at the following resources: +```bash +npm run build +``` -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +Produktions-Server starten: -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +```bash +npm start +``` -## Deploy on Vercel +### Docker Deployment -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +Für das Deployment auf einem externen Server ist die Anwendung vollständig containerisiert. Alle Details zur Installation und Verwaltung finden Sie in der **[DEPLOYMENT.md](DEPLOYMENT.md)**. -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +Kurzanleitung: + +```bash +# Docker-Container bauen und starten +docker-compose up -d --build + +# Status prüfen +docker-compose ps + +# Logs anschauen +docker-compose logs -f +``` + +Die Anwendung ist dann unter `http://:3000` erreichbar. + +## Projekt-Struktur + +``` +werte_next/ +├── app/ +│ ├── api/ +│ │ └── werte/ +│ │ ├── route.ts # GET/POST API für Einträge +│ │ └── [id]/ +│ │ └── route.ts # DELETE API für einzelne Einträge +│ ├── layout.tsx # Root Layout +│ ├── page.tsx # Hauptseite +│ └── globals.css # Globale Styles +├── components/ +│ ├── WerteForm.tsx # Eingabeformular +│ └── WerteList.tsx # Liste der letzten Einträge +├── lib/ +│ └── db.ts # Datenbankverbindung +├── types/ +│ └── werte.ts # TypeScript-Typen +├── .env.local # Umgebungsvariablen (lokal, gitignored) +└── package.json +``` + +## API-Endpunkte + +### GET /api/werte +Holt die letzten N Einträge aus der Datenbank. + +**Query Parameters**: +- `limit` (optional): Anzahl der Einträge (Standard: 10) + +**Response**: +```json +{ + "success": true, + "data": [...] +} +``` + +### POST /api/werte +Erstellt einen neuen Eintrag. + +**Request Body**: +```json +{ + "Datum": "2026-02-21", + "Zeit": "14:30", + "Zucker": 120, + "Essen": "nüchtern", + "Gewicht": 75.5, + "DruckS": 130, + "DruckD": 85, + "Puls": 72 +} +``` + +### DELETE /api/werte/[id] +Löscht einen Eintrag anhand der ID. + +## Design + +Das Design orientiert sich am ursprünglichen Werte-Projekt: +- **Hintergrundfarbe**: #FFFFDD (hellgelb) +- **Eingabebereich**: #CCCCFF (hellblau) +- **Buttons**: #85B7D7 (blau) +- **Schriftarten**: Arial, Verdana, Helvetica + +## Unterschiede zum Original + +- **Modernes Framework**: Next.js statt PHP +- **TypeScript**: Type-Safety für bessere Wartbarkeit +- **React**: Komponentenbasierte Architektur +- **API Routes**: RESTful API statt direkter PHP-Skripte +- **Vereinfachte Features**: Fokus auf Eingabe und letzte 10 Einträge (Listen- und Statistik-Tabs wurden weggelassen wie gewünscht) + +## Bekannte Einschränkungen + +- Die Tabs "Liste" und "Statistik" aus dem Original wurden bewusst nicht implementiert +- Die jqGrid-Funktionalität wurde durch eine einfache Tabelle ersetzt + +## Support + +Bei Fragen oder Problemen: [rxf@gmx.de](mailto:rxf@gmx.de) + +## Lizenz + +Private Nutzung diff --git a/app/api/werte/[id]/route.ts b/app/api/werte/[id]/route.ts new file mode 100644 index 0000000..d83d673 --- /dev/null +++ b/app/api/werte/[id]/route.ts @@ -0,0 +1,56 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { query } from '@/lib/db'; +import { CreateWerteEntry } from '@/types/werte'; + +const TABLE = 'RXF.Werte_BZG'; + +export async function PUT( + request: NextRequest, + context: { params: Promise<{ id: string }> } +) { + try { + const { id } = await context.params; + const body: CreateWerteEntry = await request.json(); + + const sql = `UPDATE ${TABLE} SET + Datum = '${body.Datum}', + Zeit = '${body.Zeit}', + Zucker = ${body.Zucker || 'NULL'}, + Essen = ${body.Essen ? `'${body.Essen.replace(/'/g, "''")}'` : 'NULL'}, + Gewicht = ${body.Gewicht || 'NULL'}, + DruckS = ${body.DruckS || 'NULL'}, + DruckD = ${body.DruckD || 'NULL'}, + Puls = ${body.Puls || 'NULL'} + WHERE ID = ${parseInt(id, 10)}`; + + const result = await query(sql); + + return NextResponse.json({ success: true, data: result }); + } catch (error) { + console.error('Database error:', error); + return NextResponse.json( + { success: false, error: 'Failed to update entry' }, + { status: 500 } + ); + } +} + +export async function DELETE( + request: NextRequest, + context: { params: Promise<{ id: string }> } +) { + try { + const { id } = await context.params; + + const sql = `DELETE FROM ${TABLE} WHERE ID = ${parseInt(id, 10)}`; + const result = await query(sql); + + return NextResponse.json({ success: true, data: result }); + } catch (error) { + console.error('Database error:', error); + return NextResponse.json( + { success: false, error: 'Failed to delete entry' }, + { status: 500 } + ); + } +} diff --git a/app/api/werte/route.ts b/app/api/werte/route.ts new file mode 100644 index 0000000..40c03b9 --- /dev/null +++ b/app/api/werte/route.ts @@ -0,0 +1,63 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { query } from '@/lib/db'; +import { CreateWerteEntry } from '@/types/werte'; + +const TABLE = 'RXF.Werte_BZG'; + +// GET - Fetch entries +export async function GET(request: NextRequest) { + try { + const searchParams = request.nextUrl.searchParams; + const limit = parseInt(searchParams.get('limit') || '10', 10); + + const sql = `SELECT ID, DATE_FORMAT(Datum, '%Y-%m-%d') as Datum, Zeit, Zucker, Essen, Gewicht, DruckD, DruckS, Puls FROM ${TABLE} ORDER BY Datum DESC, Zeit DESC LIMIT ${limit}`; + const rows = await query(sql); + + return NextResponse.json( + { success: true, data: rows }, + { + headers: { + 'Cache-Control': 'no-store, no-cache, must-revalidate', + 'Pragma': 'no-cache', + 'Expires': '0', + }, + } + ); + } catch (error) { + console.error('Database error:', error); + return NextResponse.json( + { success: false, error: 'Failed to fetch entries' }, + { status: 500 } + ); + } +} + +// POST - Create new entry +export async function POST(request: NextRequest) { + try { + const body: CreateWerteEntry = await request.json(); + + const sql = `INSERT INTO ${TABLE} (Datum, Zeit, Zucker, Essen, Gewicht, DruckS, DruckD, Puls) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`; + + const params = [ + body.Datum, + body.Zeit, + body.Zucker || null, + body.Essen || null, + body.Gewicht || null, + body.DruckS || null, + body.DruckD || null, + body.Puls || null, + ]; + + const result = await query(sql, params); + + return NextResponse.json({ success: true, data: result }); + } catch (error) { + console.error('Database error:', error); + return NextResponse.json( + { success: false, error: 'Failed to create entry' }, + { status: 500 } + ); + } +} diff --git a/app/globals.css b/app/globals.css index a2dc41e..c78599b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,3 +1,4 @@ +/* stylelint-disable at-rule-no-unknown */ @import "tailwindcss"; :root { diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..0bae61f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -13,8 +13,8 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Werte - Log", + description: "Gesundheitsdaten Tracking Anwendung", }; export default function RootLayout({ @@ -23,7 +23,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + diff --git a/app/page.tsx b/app/page.tsx index 295f8fd..7875f76 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,64 +1,110 @@ -import Image from "next/image"; +'use client'; + +import { useState, useEffect } from 'react'; +import WerteForm from '@/components/WerteForm'; +import WerteList from '@/components/WerteList'; +import { WerteEntry } from '@/types/werte'; +import packageJson from '@/package.json'; export default function Home() { + const [entries, setEntries] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [selectedEntry, setSelectedEntry] = useState(null); + + const version = packageJson.version; + const buildDate = process.env.NEXT_PUBLIC_BUILD_DATE || new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }); + + useEffect(() => { + let isMounted = true; + + // Fetch initial data + (async () => { + try { + const response = await fetch('/api/werte?limit=14', { + cache: 'no-store', + headers: { + 'Cache-Control': 'no-cache', + }, + }); + const data = await response.json(); + if (data.success && isMounted) { + setEntries(data.data); + } + } catch (error) { + console.error('Error fetching entries:', error); + } + if (isMounted) { + setIsLoading(false); + } + })(); + + return () => { + isMounted = false; + }; + }, []); + + const refreshEntries = async () => { + try { + const response = await fetch('/api/werte?limit=14', { + cache: 'no-store', + headers: { + 'Cache-Control': 'no-cache', + }, + }); + const data = await response.json(); + if (data.success) { + setEntries(data.data); + } + } catch (error) { + console.error('Error fetching entries:', error); + } + }; + + const handleSuccess = () => { + setSelectedEntry(null); + // Small delay to ensure database commit is complete + setTimeout(() => { + refreshEntries(); + }, 100); + }; + + const handleDelete = (id: number) => { + setEntries(entries.filter(entry => entry.ID !== id)); + }; + + const handleEdit = (entry: WerteEntry) => { + setSelectedEntry(entry); + // Scroll to top + window.scrollTo({ top: 0, behavior: 'smooth' }); + }; + return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

+
+
+

Werte - Log

+ +
+

Eingabe

+
-
- - Vercel logomark - Deploy Now - - - Documentation - + +
+

Letzte 14 Einträge

+ {isLoading ? ( +
Lade Daten...
+ ) : ( + + )}
+ +
); diff --git a/components/WerteForm.tsx b/components/WerteForm.tsx new file mode 100644 index 0000000..6a9b130 --- /dev/null +++ b/components/WerteForm.tsx @@ -0,0 +1,274 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { CreateWerteEntry, WerteEntry } from '@/types/werte'; + +interface WerteFormProps { + onSuccess: () => void; + selectedEntry?: WerteEntry | null; +} + +export default function WerteForm({ onSuccess, selectedEntry }: WerteFormProps) { + const [formData, setFormData] = useState({ + Datum: '', + Zeit: '', + Zucker: '', + Essen: 'nüchtern', + Gewicht: '', + DruckS: '', + DruckD: '', + Puls: '', + }); + + const [isSubmitting, setIsSubmitting] = useState(false); + const [weekday, setWeekday] = useState(''); + const [editId, setEditId] = useState(null); + + useEffect(() => { + if (selectedEntry) { + // Load selected entry for editing + const dateStr = selectedEntry.Datum.toString().split('T')[0]; + const timeStr = selectedEntry.Zeit.toString().substring(0, 5); + + setFormData({ + Datum: dateStr, + Zeit: timeStr, + Zucker: selectedEntry.Zucker || '', + Essen: selectedEntry.Essen || '', + Gewicht: selectedEntry.Gewicht || '', + DruckS: selectedEntry.DruckS || '', + DruckD: selectedEntry.DruckD || '', + Puls: selectedEntry.Puls || '', + }); + + setEditId(selectedEntry.ID || null); + + // Parse date to avoid timezone issues + const [year, month, day] = dateStr.split('T')[0].split('-'); + const date = new Date(Number(year), Number(month) - 1, Number(day)); + updateWeekday(date); + } else { + // Initialize with current date and time for new entry + const now = new Date(); + const dateStr = now.toISOString().split('T')[0]; + const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; + + setFormData(prev => ({ + ...prev, + Datum: dateStr, + Zeit: timeStr, + })); + + setEditId(null); + updateWeekday(now); + } + }, [selectedEntry]); + + const updateWeekday = (date: Date) => { + const weekdays = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']; + setWeekday(weekdays[date.getDay()]); + }; + + const handleDateChange = (dateStr: string) => { + setFormData(prev => ({ ...prev, Datum: dateStr })); + // Parse date to avoid timezone issues + const [year, month, day] = dateStr.split('-'); + const date = new Date(Number(year), Number(month) - 1, Number(day)); + updateWeekday(date); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsSubmitting(true); + + try { + const url = editId ? `/api/werte/${editId}` : '/api/werte'; + const method = editId ? 'PUT' : 'POST'; + + const response = await fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + if (response.ok) { + // Reset form but keep date and time + const now = new Date(); + const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; + + setFormData(prev => ({ + ...prev, + Zeit: timeStr, + Zucker: '', + Essen: 'nüchtern', + Gewicht: '', + DruckS: '', + DruckD: '', + Puls: '', + })); + + setEditId(null); + onSuccess(); + } else { + alert('Fehler beim Speichern!'); + } + } catch (error) { + console.error('Error:', error); + alert('Fehler beim Speichern!'); + } finally { + setIsSubmitting(false); + } + }; + + const handleReset = () => { + const now = new Date(); + const dateStr = now.toISOString().split('T')[0]; + const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; + + setFormData({ + Datum: dateStr, + Zeit: timeStr, + Zucker: '', + Essen: 'nüchtern', + Gewicht: '', + DruckS: '', + DruckD: '', + Puls: '', + }); + + setEditId(null); + updateWeekday(now); + }; + + return ( +
+ {editId && ( +
+ ℹ️ Bearbeitungsmodus: Sie bearbeiten einen bestehenden Eintrag. Klicken Sie auf "Aktualisieren", um die Änderungen zu speichern, oder "Abbrechen", um zur Neuerfassung zurückzukehren. +
+ )} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DatumZeitZuckerEssenGewichtDruck sysDruck diaPuls
{weekday}mg/dlkgmmHgmmHgbpm
+ handleDateChange(e.target.value)} + required + /> + + setFormData(prev => ({ ...prev, Zeit: e.target.value }))} + required + /> + + setFormData(prev => ({ ...prev, Zucker: e.target.value }))} + maxLength={4} + /> + +