Docker mit traefik und portainer

This commit is contained in:
rxf
2025-09-22 16:35:59 +02:00
parent 6d04ab93c0
commit a255543da6
64 changed files with 5421 additions and 25 deletions

View File

@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import type { Recipe } from '../services/api';
import { recipeApi, imageApi } from '../services/api';
import FileUpload from './FileUpload';
import './RecipeDetail.css';
// Helper function to convert URLs in text to clickable links
@@ -33,6 +34,8 @@ const RecipeDetail: React.FC = () => {
const [recipe, setRecipe] = useState<Recipe | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [editingImages, setEditingImages] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0);
useEffect(() => {
const loadRecipe = async () => {
@@ -62,6 +65,43 @@ const RecipeDetail: React.FC = () => {
loadRecipe();
}, [id]);
const handleImageUpload = async (files: File[]) => {
if (!recipe || !id) return;
try {
setUploadProgress(0);
await imageApi.uploadImages(parseInt(id), files, setUploadProgress);
// Reload recipe to get updated images
const response = await recipeApi.getRecipe(parseInt(id));
if (response.success) {
setRecipe(response.data);
}
setUploadProgress(0);
} catch (error) {
console.error('Error uploading images:', error);
setError('Fehler beim Hochladen der Bilder');
}
};
const handleImageDelete = async (imageId: number) => {
if (!recipe || !id) return;
try {
await imageApi.deleteImage(imageId);
// Reload recipe to get updated images
const response = await recipeApi.getRecipe(parseInt(id));
if (response.success) {
setRecipe(response.data);
}
} catch (error) {
console.error('Error deleting image:', error);
setError('Fehler beim Löschen des Bildes');
}
};
if (loading) {
return (
<div className="recipe-detail">
@@ -114,6 +154,13 @@ const RecipeDetail: React.FC = () => {
</div>
<div className="recipe-actions">
<button
onClick={() => setEditingImages(!editingImages)}
className="edit-button"
style={{ marginRight: '8px' }}
>
📸 {editingImages ? 'Fertig' : 'Bilder verwalten'}
</button>
<Link to={`/recipes/${recipe.id}/edit`} className="edit-button">
Bearbeiten
</Link>
@@ -174,6 +221,64 @@ const RecipeDetail: React.FC = () => {
</div>
</div>
{/* Image Management Section */}
{editingImages && (
<div className="image-management">
<h3>Bilder verwalten</h3>
{/* Upload new images */}
<div className="upload-section">
<h4>Neue Bilder hochladen</h4>
<FileUpload
onFilesSelected={handleImageUpload}
maxFiles={5}
maxFileSize={5}
disabled={uploadProgress > 0}
/>
{uploadProgress > 0 && uploadProgress < 100 && (
<div className="upload-progress">
<div className="upload-progress-bar" style={{ width: `${uploadProgress}%` }}></div>
<span className="upload-progress-text">{uploadProgress}% hochgeladen</span>
</div>
)}
</div>
{/* Existing images */}
{recipe.images && recipe.images.length > 0 && (
<div className="existing-images">
<h4>Vorhandene Bilder ({recipe.images.length})</h4>
<div className="images-grid">
{recipe.images.map((image, index) => (
<div key={image.id} className="image-item">
<div className="image-preview">
<img
src={imageApi.getImageUrl(image.filePath)}
alt={`Bild ${index + 1}`}
/>
<button
className="delete-image-btn"
onClick={() => handleImageDelete(image.id)}
title="Bild löschen"
>
🗑
</button>
</div>
<div className="image-info">
<span className="image-name">
{image.filePath.split('/').pop()}
</span>
{image.filePath.includes('_0.jpg') && (
<span className="main-image-badge">Hauptbild</span>
)}
</div>
</div>
))}
</div>
</div>
)}
</div>
)}
{/* Two Column Layout for Description/Ingredients and Preparation */}
<div className="recipe-columns">
{/* Left Column - Description and Ingredients */}