diff --git a/01-frontend/public/locales/de/translation.json b/01-frontend/public/locales/de/translation.json index cda3bd6..6ab4562 100644 --- a/01-frontend/public/locales/de/translation.json +++ b/01-frontend/public/locales/de/translation.json @@ -142,5 +142,12 @@ "DELIVERED": "Zugesendet", "CANCELLED": "Storniert", "fsImage": "FS-Bild", - "changeCustomer": "Kundendaten ändern" + "changeCustomer": "Kundendaten ändern", + "uploadImage": "Bild Hochladen", + "imageUploadNotice": "Lade das Bild in .webp-Format hoch, damit die Webseite schneller lädt", + "selectImage": "Bildauswahl", + "imageUploadedSuccessfully": "Bild hochgeladen", + "uploading": "Lädt hoch...", + "upload": "Hochladen", + "imageUploadNoticeFs": "Die Auflösung der Farming Station beträgt 720 x 960 px" } \ No newline at end of file diff --git a/01-frontend/public/locales/en/translation.json b/01-frontend/public/locales/en/translation.json index 54bbea9..ac747e6 100644 --- a/01-frontend/public/locales/en/translation.json +++ b/01-frontend/public/locales/en/translation.json @@ -142,5 +142,12 @@ "DELIVERED": "Delivered", "CANCELLED": "Cancelled", "fsImage": "FS-Image", - "changeCustomer": "Change Customer Data" + "changeCustomer": "Change Customer Data", + "uploadImage": "Upload Image", + "imageUploadNotice": "Upload the image in .webp format for better loading performance", + "selectImage": "Select Image", + "imageUploadedSuccessfully": "Uploaded Image Successfully", + "uploading": "Uploading...", + "upload": "Upload", + "imageUploadNoticeFs": "The Resolution of the Farming Station is 720 x 960 px" } \ No newline at end of file diff --git a/01-frontend/src/helper/adminpanel/ItemImageDialog.tsx b/01-frontend/src/helper/adminpanel/ItemImageDialog.tsx new file mode 100644 index 0000000..98b4e3e --- /dev/null +++ b/01-frontend/src/helper/adminpanel/ItemImageDialog.tsx @@ -0,0 +1,230 @@ + +import React, { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + Typography, + Alert, + CircularProgress, + IconButton, +} from '@mui/material'; +import CloudUploadIcon from '@mui/icons-material/CloudUpload'; +import CloseIcon from '@mui/icons-material/Close'; +import { useTranslation } from 'react-i18next'; +import { Item } from '../../components/Item.tsx'; + +interface ItemImageDialogProps { + open: boolean; + onClose: () => void; + item: Item; + onSuccess?: () => void; + isFarmStationImage: boolean; +} + +export default function ItemImageDialog({ open, onClose, item, onSuccess, isFarmStationImage }: ItemImageDialogProps) { + const { t } = useTranslation(); + const [selectedFile, setSelectedFile] = useState(null); + const [preview, setPreview] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + const handleFileSelect = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (file) { + // Validate file type + if (!file.type.startsWith('image/')) { + setError('Please select a valid image file'); + return; + } + + // Validate file size (5MB limit) + if (file.size > 5 * 1024 * 1024) { + setError('File size must be less than 5MB'); + return; + } + + setSelectedFile(file); + setError(null); + + // Create preview + const reader = new FileReader(); + reader.onload = (e) => { + setPreview(e.target?.result as string); + }; + reader.readAsDataURL(file); + } + }; + + const convertFileToBase64 = (file: File): Promise => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const result = reader.result as string; + // Remove the data URL prefix (e.g., "data:image/jpeg;base64,") + const base64 = result.split(',')[1]; + resolve(base64); + }; + reader.onerror = reject; + reader.readAsDataURL(file); + }); + }; + + const handleUpload = async () => { + if (!selectedFile) { + setError('Please select an image file'); + return; + } + + setLoading(true); + setError(null); + + try { + const base64Image = await convertFileToBase64(selectedFile); + + const response = await fetch((isFarmStationImage ? 'http://localhost:8085/farm' : 'http://localhost:8085/image') + '?uuid=' + item.uuid , { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: base64Image, + }); + + if (!response.ok) { + console.error('Failed to upload image:', await response.text()); + } + + setSuccess(true); + onSuccess?.(); + + // Auto-close dialog after 1.5 seconds + setTimeout(() => { + handleClose(); + }, 1500); + + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to upload image'); + } finally { + setLoading(false); + } + }; + + const handleClose = () => { + setSelectedFile(null); + setPreview(null); + setError(null); + setSuccess(false); + setLoading(false); + onClose(); + }; + + return ( + + + {t('uploadImage')} - {item.name} + + + + + + + + {/* Item Info */} + + + {t('item')}: {item.name} + + + UUID: {item.uuid} + + + {t('imageUploadNotice')} + + {isFarmStationImage && + {t('imageUploadNoticeFs')} + } + + + {/* File Upload */} + + + + + + {/* Image Preview */} + {preview && ( + + Preview + + {selectedFile?.name} ({(selectedFile?.size || 0 / 1024).toFixed(1)} KB) + + + )} + + {/* Error Message */} + {error && ( + setError(null)}> + {error} + + )} + + {/* Success Message */} + {success && ( + + {t('imageUploadedSuccessfully')} + + )} + + + + + + + + + ); +} \ No newline at end of file diff --git a/01-frontend/src/helper/adminpanel/ItemsInfo.tsx b/01-frontend/src/helper/adminpanel/ItemsInfo.tsx index 878a4b2..881f567 100644 --- a/01-frontend/src/helper/adminpanel/ItemsInfo.tsx +++ b/01-frontend/src/helper/adminpanel/ItemsInfo.tsx @@ -10,24 +10,40 @@ import {useAccount} from "../AccountProvider.tsx"; import {useQuery} from "@tanstack/react-query"; import {fetchItems} from "../query/Queries.tsx"; import { mapValueToColor } from "../../util/ColorUtil.tsx"; +import ItemImageDialog from "./ItemImageDialog.tsx"; export default function ItemsInfo() { const theme = useTheme(); const {t} = useTranslation(); - function handleIconEdit(item: Item) { - //TODO: implement - console.log("IconEdit", item); - } - function handleFarmStationEdit(item: Item) { - //TODO: implement - console.log("FsEdit", item); - } - - const [rows, setRows] = useState([]); const [selectedRows, setSelectedRows] = useState>(new Set()); + const [editImageDialog, setEditImageDialog] = useState(false); + const [selectedItem, setSelectedItem] = useState(null); + const [isFarmStationImage, setIsFarmStationImage] = useState(false); + + + function handleImageEdit(item: Item) { + setIsFarmStationImage(false); + setSelectedItem(item); + setEditImageDialog(true); + console.log("IconEdit", item); + } + + function handleFarmImageEdit(item: Item) { + setIsFarmStationImage(true); + setSelectedItem(item); + setEditImageDialog(true); + console.log("IconEdit", item); + } + + + + function handleAddItem() { + //TODO: flsp + } + const {user: loginData} = useAccount(); const { data } = useQuery({ @@ -121,9 +137,9 @@ export default function ItemsInfo() { [`& .${gaugeClasses.valueArc}`]: { fill: () => {return mapValueToColor(0, 10, params.row.rating)}, }, - }} text={() => `${params.row.rating}`} /> + }} text={() => `${params.row.rating.toFixed(2)}`} /> }, - { //edit billing information button + { field: "actualPrice", headerName: t('actualPrice'), width: 90, @@ -135,14 +151,14 @@ export default function ItemsInfo() { headerName: t('images'), width: 90, editable: false, - renderCell: params => handleIconEdit(params.row)}> , + renderCell: params => handleImageEdit(params.row)}> , }, { field: 'farmImage', headerName: t('fsImage'), width: 90, editable: false, - renderCell: params => handleFarmStationEdit(params.row)}> , + renderCell: params => handleFarmImageEdit(params.row)}> , } ]; @@ -179,7 +195,7 @@ export default function ItemsInfo() { variant="contained" color="primary" startIcon={} - onClick={handleDeleteSelected} + onClick={handleAddItem} disabled={selectedRows.size === 0} sx={{ marginRight: 1 @@ -196,6 +212,18 @@ export default function ItemsInfo() { return updatedRow; }} /> + {selectedItem && ( + setEditImageDialog(false)} + item={selectedItem} + onSuccess={() => { + // Refresh data or update UI + console.log('Image uploaded successfully'); + }} + isFarmStationImage={isFarmStationImage} + /> + )} ); -} +} \ No newline at end of file