🌟 🎉 Major Update:

This commit is contained in:
2025-06-01 23:41:02 +02:00
parent 62efe03887
commit 4042f07c00
228 changed files with 8598 additions and 1893 deletions

534
frontend/README.md Normal file
View File

@@ -0,0 +1,534 @@
# MYP Frontend-System
## 🌐 Moderne Web-Oberfläche & Analytics-Dashboard
**Entwickler**: Torben Haack
**Fachrichtung**: Fachinformatiker für Daten- und Prozessanalyse
**Zweck**: Moderne React-basierte Benutzeroberfläche für das MYP-Druckerverwaltungssystem
## 🎯 Projektübersicht
Das **Frontend-System** ist eine vollständige **Next.js-Webanwendung**, die als moderne Benutzeroberfläche für Till Tomczaks Backend-APIs dient. Es bietet eine intuitive, responsive Bedienung und erweiterte Analytics-Funktionen für alle Stakeholder des MYP-Systems.
### Kernfunktionen
- **Moderne Web-UI**: React 18 + Next.js 14 für optimale Performance
- **Backend-Integration**: Nahtlose Anbindung an Till Tomczaks REST-APIs
- **Advanced Analytics**: Interaktive Dashboards und Datenvisualisierung
- **Responsive Design**: Optimiert für Desktop, Tablet und Mobile
- **Real-time Updates**: Live-Synchronisation mit Backend-Daten
## 🏗️ Architektur & Integration
### System-Übersicht
```
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Frontend-Server │◄──►│ Backend-Server │◄──►│ Raspberry Pi │
│ (Port 3000) │ │ (Port 5000/443) │ │ (Hardware) │
│ Torben Haack │ │ Till Tomczak │ │ Till Tomczak │
│ │ │ │ │ │
│ • Next.js App │ │ • Flask REST-API │ │ • Smart-Plug Control│
│ • React Components │ │ • SQLite Database │ │ • Kiosk Interface │
│ • Analytics UI │ │ • Authentication │ │ • Offline Operation │
│ • Chart Libraries │ │ • Business Logic │ │ • Touch Interface │
│ • Export Functions │ │ • Hardware APIs │ │ • System Services │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
```
### Frontend-Technologie-Stack
- **Framework**: Next.js 14 mit App Router
- **Language**: TypeScript für Type-Safety
- **UI-Library**: Radix UI + Tailwind CSS
- **State Management**: React Server Components + SWR
- **Charts**: Recharts + Tremor für Datenvisualisierung
- **Database**: Drizzle ORM mit SQLite (für Frontend-spezifische Daten)
- **Authentication**: Backend-Session-Integration
## 🚀 Installation & Setup
### Voraussetzungen
- Node.js 18+
- pnpm (empfohlen) oder npm
- Zugriff auf Till Tomczaks Backend-Server
### 1. Frontend-Installation
```bash
# Repository klonen
git clone <repository-url>
cd Projektarbeit-MYP/frontend
# Abhängigkeiten installieren
pnpm install
# Frontend-Datenbank einrichten (für UI-spezifische Daten)
pnpm db:create-default
pnpm db:generate-sqlite
pnpm db:migrate
# Environment-Konfiguration
cp .env.example .env.local
# Backend-API-URL in .env.local eintragen
```
### 2. Backend-Integration konfigurieren
```env
# .env.local
NEXT_PUBLIC_BACKEND_API_URL=http://backend-server:5000/api
NEXT_PUBLIC_BACKEND_WS_URL=ws://backend-server:5000/ws
# Für lokale Entwicklung
NEXT_PUBLIC_BACKEND_API_URL=http://localhost:5000/api
```
### 3. Entwicklung starten
```bash
# Entwicklungsserver starten
pnpm dev
# Frontend läuft auf http://localhost:3000
# Backend-APIs werden von http://backend-server:5000/api konsumiert
```
### 4. Produktions-Deployment
```bash
# Build für Produktion
pnpm build
# Produktionsserver starten
pnpm start
# Oder mit PM2 für Produktionsumgebung
pm2 start ecosystem.config.js
```
## 📁 Projektstruktur
```
frontend/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── (dashboard)/ # Dashboard-Layout-Gruppe
│ │ │ ├── page.tsx # Haupt-Dashboard
│ │ │ ├── printers/ # Drucker-Management
│ │ │ ├── jobs/ # Job-Verwaltung
│ │ │ └── analytics/ # Analytics-Dashboards
│ │ ├── admin/ # Admin-Bereich
│ │ ├── auth/ # Authentifizierung (Backend-Integration)
│ │ └── api/ # Frontend-API-Routes (Proxy/Cache)
│ ├── components/ # React-Komponenten
│ │ ├── ui/ # Basis-UI-Komponenten (Radix UI)
│ │ ├── charts/ # Chart-Komponenten (Recharts)
│ │ ├── forms/ # Formular-Komponenten
│ │ ├── layout/ # Layout-Komponenten
│ │ └── printer/ # Drucker-spezifische Komponenten
│ ├── lib/ # Utility-Bibliotheken
│ │ ├── api/ # Backend-API-Client
│ │ ├── auth/ # Authentifizierung
│ │ ├── utils/ # Helper-Funktionen
│ │ └── analytics/ # Analytics-Algorithmen
│ ├── hooks/ # Custom React Hooks
│ ├── types/ # TypeScript-Typen
│ └── styles/ # Global Styles
├── public/ # Statische Assets
├── docs/ # Frontend-Dokumentation
├── drizzle/ # Frontend-DB-Migrationen
└── package.json # Dependencies
```
## 🔗 Backend-API-Integration
### API-Client-Implementation
```typescript
// src/lib/api/myp-client.ts
export class MYPApiClient {
private baseURL: string;
constructor() {
this.baseURL = process.env.NEXT_PUBLIC_BACKEND_API_URL || 'http://localhost:5000/api';
}
// Drucker-Management (Till Tomczaks Backend APIs)
async getPrinters(): Promise<Printer[]> {
const response = await fetch(`${this.baseURL}/printers`, {
credentials: 'include', // Session-Cookies übertragen
});
return response.json();
}
async createPrinter(printer: CreatePrinterRequest): Promise<Printer> {
const response = await fetch(`${this.baseURL}/printers`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(printer),
});
return response.json();
}
// Job-Management
async getJobs(): Promise<Job[]> {
const response = await fetch(`${this.baseURL}/jobs`, {
credentials: 'include',
});
return response.json();
}
async createJob(job: CreateJobRequest): Promise<Job> {
const response = await fetch(`${this.baseURL}/jobs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(job),
});
return response.json();
}
// Smart-Plug-Steuerung (über Backend)
async controlPrinter(printerId: string, action: 'on' | 'off'): Promise<void> {
await fetch(`${this.baseURL}/plugs/${printerId}/${action}`, {
method: 'POST',
credentials: 'include',
});
}
// Analytics & Statistiken
async getStats(): Promise<Statistics> {
const response = await fetch(`${this.baseURL}/stats`, {
credentials: 'include',
});
return response.json();
}
}
```
### React Hooks für API-Integration
```typescript
// src/hooks/usePrinters.ts
import useSWR from 'swr';
import { MYPApiClient } from '@/lib/api/myp-client';
const apiClient = new MYPApiClient();
export function usePrinters() {
const { data, error, mutate } = useSWR('/printers', () => apiClient.getPrinters(), {
refreshInterval: 30000, // Alle 30 Sekunden aktualisieren
});
return {
printers: data || [],
isLoading: !error && !data,
isError: error,
refresh: mutate,
};
}
export function useJobs() {
const { data, error, mutate } = useSWR('/jobs', () => apiClient.getJobs(), {
refreshInterval: 10000, // Alle 10 Sekunden aktualisieren
});
return {
jobs: data || [],
isLoading: !error && !data,
isError: error,
refresh: mutate,
};
}
```
## 📊 Analytics & Visualisierung
### Dashboard-Komponenten
```typescript
// src/components/charts/PrinterUsageChart.tsx
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
interface PrinterUsageChartProps {
data: UsageData[];
}
export function PrinterUsageChart({ data }: PrinterUsageChartProps) {
return (
<ResponsiveContainer width="100%" height={400}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="usage" stroke="#8884d8" strokeWidth={2} />
</LineChart>
</ResponsiveContainer>
);
}
```
### Analytics-Algorithmen
```typescript
// src/lib/analytics/calculations.ts
export class Analytics {
static calculateUsageStats(jobs: Job[]): UsageStats {
const totalJobs = jobs.length;
const totalDuration = jobs.reduce((sum, job) => sum + job.duration, 0);
const averageDuration = totalDuration / totalJobs;
return {
totalJobs,
totalDuration,
averageDuration,
peakHours: this.identifyPeakHours(jobs),
efficiency: this.calculateEfficiency(jobs),
};
}
static generateReport(data: any[]): ReportData {
// Report-Generierung für PDF/Excel-Export
return {
summary: this.calculateSummary(data),
charts: this.prepareChartData(data),
tables: this.prepareTableData(data),
};
}
private static identifyPeakHours(jobs: Job[]): number[] {
const hourCounts = new Array(24).fill(0);
jobs.forEach(job => {
const hour = new Date(job.startTime).getHours();
hourCounts[hour]++;
});
return hourCounts;
}
}
```
## 🎨 UI/UX-Design
### Design-System
```typescript
// src/components/ui/button.tsx (Radix UI + Tailwind)
import { cn } from '@/lib/utils';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
size?: 'default' | 'sm' | 'lg' | 'icon';
}
export function Button({ className, variant = 'default', size = 'default', ...props }: ButtonProps) {
return (
<button
className={cn(
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
'bg-primary text-primary-foreground hover:bg-primary/90': variant === 'default',
'bg-destructive text-destructive-foreground hover:bg-destructive/90': variant === 'destructive',
// ... weitere Varianten
},
{
'h-10 px-4 py-2': size === 'default',
'h-9 rounded-md px-3': size === 'sm',
// ... weitere Größen
},
className
)}
{...props}
/>
);
}
```
### Responsive Layout
```typescript
// src/components/layout/DashboardLayout.tsx
export function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-background">
<header className="border-b">
<div className="container mx-auto px-4 py-4">
<nav className="flex items-center justify-between">
<h1 className="text-2xl font-bold">MYP Dashboard</h1>
<UserMenu />
</nav>
</div>
</header>
<div className="container mx-auto px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
<aside className="lg:col-span-1">
<Navigation />
</aside>
<main className="lg:col-span-3">
{children}
</main>
</div>
</div>
</div>
);
}
```
## 📈 Erweiterte Features
### Real-time Updates via WebSocket
```typescript
// src/hooks/useRealTimeUpdates.ts
export function useRealTimeUpdates() {
const [socket, setSocket] = useState<WebSocket | null>(null);
useEffect(() => {
const ws = new WebSocket(process.env.NEXT_PUBLIC_BACKEND_WS_URL!);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
// Updates an SWR-Cache weiterleiten
mutate('/printers', data.printers, false);
mutate('/jobs', data.jobs, false);
};
setSocket(ws);
return () => ws.close();
}, []);
return socket;
}
```
### Export-Funktionen
```typescript
// src/lib/export/exportUtils.ts
export class ExportUtils {
static async generatePDFReport(data: ReportData): Promise<Blob> {
const { jsPDF } = await import('jspdf');
const doc = new jsPDF();
// PDF-Generierung
doc.text('MYP Analytics Report', 20, 20);
// ... Report-Inhalte hinzufügen
return doc.output('blob');
}
static async generateExcelReport(data: ReportData): Promise<Blob> {
const XLSX = await import('xlsx');
const workbook = XLSX.utils.book_new();
// Excel-Sheet erstellen
const worksheet = XLSX.utils.json_to_sheet(data.tables);
XLSX.utils.book_append_sheet(workbook, worksheet, 'Analytics');
return XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
}
}
```
## 🚀 Deployment
### Produktions-Konfiguration
```bash
# .env.production
NEXT_PUBLIC_BACKEND_API_URL=https://backend.myp.mercedes-benz.com/api
NEXT_PUBLIC_BACKEND_WS_URL=wss://backend.myp.mercedes-benz.com/ws
```
### PM2 Ecosystem
```javascript
// ecosystem.config.js
module.exports = {
apps: [{
name: 'myp-frontend',
script: 'npm',
args: 'start',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000,
},
}],
};
```
### Docker-Support
```dockerfile
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# Dependencies
COPY package*.json ./
RUN npm ci --only=production
# App
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
```
## 🔧 Development-Workflow
### Code-Quality
```bash
# Linting & Formatting
pnpm lint # ESLint check
pnpm lint:fix # ESLint fix
pnpm format # Prettier formatting
# Testing
pnpm test # Unit tests
pnpm test:e2e # End-to-end tests
# Build-Checks
pnpm build # Production build
pnpm type-check # TypeScript check
```
### Integration-Testing
```typescript
// tests/integration/api.test.ts
describe('Backend-Integration', () => {
test('should fetch printers from backend', async () => {
const client = new MYPApiClient();
const printers = await client.getPrinters();
expect(printers).toBeInstanceOf(Array);
expect(printers[0]).toHaveProperty('id');
expect(printers[0]).toHaveProperty('name');
});
});
```
## 👥 Entwickler-Information
### Torben Haack - Frontend & Analytics-Spezialist
- **UI/UX-Expertise**: React-Komponenten und responsive Design
- **Integration-Spezialist**: Nahtlose Backend-API-Anbindung
- **Analytics-Entwicklung**: Datenvisualisierung und Reporting
- **Performance-Optimierung**: Next.js und React Best Practices
### Beitrag zum Gesamtsystem
Das Frontend-System ergänzt Till Tomczaks Backend-Infrastructure um:
- **Moderne Benutzeroberfläche**: Intuitive Web-UI für alle Stakeholder
- **Advanced Analytics**: Erweiterte Datenauswertung und Visualisierung
- **Cross-Platform-Support**: Responsive Design für alle Endgeräte
- **Export-Funktionen**: PDF/Excel-Reports für Management und Analyse
## 📚 Dokumentation & Support
### Entwickler-Ressourcen
- **Component-Library**: [`src/components/ui/`](src/components/ui/) - Wiederverwendbare UI-Komponenten
- **API-Integration**: [`src/lib/api/`](src/lib/api/) - Backend-Anbindung
- **Analytics-Tools**: [`src/lib/analytics/`](src/lib/analytics/) - Auswertungs-Algorithmen
### Integration mit Backend
- **Backend-APIs**: Till Tomczaks REST-Endpunkte unter `/api/*`
- **Authentifizierung**: Session-basiert über Backend-System
- **Real-time**: WebSocket-Verbindung für Live-Updates
---
**Entwickelt von**: Torben Haack
**Projektart**: Frontend & Analytics für MYP-System
**Framework**: Next.js 14 + TypeScript + React 18
**Integration**: Vollständige Backend-API-Anbindung (Till Tomczak)
**Status**: Produktionsbereit

View File

@@ -1,36 +0,0 @@
#!/bin/bash
# Directory containing the Docker images
IMAGE_DIR="docker/images"
# Load all Docker images from the tar.xz files in the IMAGE_DIR
echo "Loading Docker images from $IMAGE_DIR..."
for image_file in "$IMAGE_DIR"/*.tar.xz; do
if [ -f "$image_file" ]; then
echo "Loading Docker image from $image_file..."
docker load -i "$image_file"
# Check if the image loading was successful
if [ $? -ne 0 ]; then
echo "Error occurred while loading Docker image from $image_file"
exit 1
fi
else
echo "No Docker image tar.xz files found in $IMAGE_DIR."
fi
done
# Execute docker compose
echo "Running docker compose..."
docker compose -f "compose.yml" up -d
# Check if the operation was successful
if [ $? -eq 0 ]; then
echo "Docker compose executed successfully"
else
echo "Error occurred while executing docker compose"
exit 1
fi
echo "Deployment completed successfully"

View File

@@ -1,4 +1,5 @@
import { NextRequest, NextResponse } from 'next/server';
import { API_BASE_URL } from '@/utils/api-config';
/**
* Health Check Endpoint für Frontend-Server
@@ -6,8 +7,8 @@ import { NextRequest, NextResponse } from 'next/server';
*/
export async function GET(request: NextRequest) {
try {
// Prüfe Backend-Verbindung
const backendUrl = process.env.NEXT_PUBLIC_API_URL || 'https://raspberrypi';
// Bestimme die Backend-URL basierend auf der Konfiguration
const backendUrl = typeof API_BASE_URL === 'object' ? API_BASE_URL.primary : API_BASE_URL;
let backendStatus = 'unknown';
try {

View File

@@ -2,30 +2,30 @@
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// Basis-URL für Backend-API
// Versucht verschiedene Verbindungsoptionen mit Fallbacks
// Backend läuft auf separatem Server unter https://192.168.0.105:443
const getApiBaseUrl = () => {
// Explizit gesetzte Umgebungsvariable hat höchste Priorität
if (process.env.NEXT_PUBLIC_API_URL) {
return process.env.NEXT_PUBLIC_API_URL;
}
// Im Browser: Verschiedene Verbindungsoptionen versuchen
// Primäre Backend-URL: Separater Server
const BACKEND_SERVER_URL = 'https://192.168.0.105:443';
// Im Browser: Verwende primär den separaten Backend-Server
if (typeof window !== 'undefined') {
// Primäre Verbindungsoption: HTTPS auf Port 443
const hostname = window.location.hostname === 'localhost' ? 'raspberrypi' : window.location.hostname;
// Verbindungsoptionen in Prioritätsreihenfolge
return {
primary: `https://${hostname}`,
primary: BACKEND_SERVER_URL,
fallbacks: [
`https://raspberrypi`,
`https://192.168.0.105`,
`https://192.168.0.105`, // Fallback ohne expliziten Port
`https://raspberrypi`, // Lokaler Raspberry Pi Fallback
]
};
}
// Standardwert für serverseitiges Rendering
return `https://raspberrypi`;
// Standardwert für serverseitiges Rendering: Separater Backend-Server
return BACKEND_SERVER_URL;
};
export const API_BASE_URL = getApiBaseUrl();
@@ -114,15 +114,22 @@ export const testSSLConnection = async (): Promise<boolean> => {
}
};
// Hilfsfunktion zum Erstellen vollständiger API-URLs
export const getFullApiUrl = (endpoint: string): string => {
const baseUrl = typeof API_BASE_URL === 'object' ? API_BASE_URL.primary : API_BASE_URL;
return `${baseUrl}${endpoint}`;
};
// Endpunkte für die verschiedenen Ressourcen
export const API_ENDPOINTS = {
PRINTERS: `/api/printers`,
JOBS: `/api/jobs`,
USERS: `/api/users`,
PRINTERS: getFullApiUrl(`/api/printers`),
JOBS: getFullApiUrl(`/api/jobs`),
USERS: getFullApiUrl(`/api/users`),
HEALTH: getFullApiUrl(`/health`),
// OAuth-spezifische Endpunkte
AUTH: {
LOGIN: `/api/auth/login`,
CALLBACK: `/api/auth/callback`,
LOGIN: getFullApiUrl(`/api/auth/login`),
CALLBACK: getFullApiUrl(`/api/auth/callback`),
}
};