diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c74a40f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# 1. Build-Stage +FROM node:18-alpine AS build + +WORKDIR /app + +# package files kopieren +COPY package*.json ./ + +# dependencies installieren +RUN npm install + +# restlichen Code kopieren +COPY . . + +# React App bauen +RUN npm run build + +# 2. Serve-Stage (Nginx) +FROM nginx:alpine + +# build output in nginx kopieren +COPY --from=build /app/build /usr/share/nginx/html + +# optional: eigene nginx config +# COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 5173 + +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index 98f8666..74c27e3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,6 +10,7 @@ import PostsPage from './pages/PostsPage'; import CreateNewsPage from './pages/CreateNewsPage'; import OrganizationPage from './pages/OrganizationPage'; import PushPage from './pages/PushPage'; +import EditOrganizationPage from "./pages/EditOrganizationPage.jsx"; const theme = createTheme({ palette: { @@ -69,10 +70,18 @@ export default function App() { } /> + + + + } + /> + } diff --git a/src/pages/EditOrganizationPage.jsx b/src/pages/EditOrganizationPage.jsx new file mode 100644 index 0000000..fe62356 --- /dev/null +++ b/src/pages/EditOrganizationPage.jsx @@ -0,0 +1,227 @@ +import {useNavigate} from "react-router-dom"; +import {useState} from "react"; +import { + Alert, + Box, Button, + Card, + CardContent, + Checkbox, Chip, CircularProgress, Divider, + FormControlLabel, + IconButton, + Stack, + TextField, + Typography +} from "@mui/material"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; +import StarIcon from "@mui/icons-material/Star"; +import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; +import AddPhotoAlternateOutlinedIcon from "@mui/icons-material/AddPhotoAlternateOutlined"; +import { useParams } from "react-router-dom"; +import { useEffect } from "react"; +import axiosInstance from "../api/axiosInstance.js"; + +const EMPTY_FORM = { + description: '', + phone: '', + email: '', + address: '', + website: '', + contactPerson: '', + active: false, +} + +export default function EditOrganizationPage() { + const navigate = useNavigate(); + const [form, setForm] = useState(EMPTY_FORM); + const [files, setFiles] = useState([]); + const [previews, setPreviews] = useState([]); + const [existingImages, setExistingImages] = useState([]); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(''); + const { id } = useParams(); + const [data, setData] = useState([]); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await axiosInstance.get('/api/organizations/' + id); + const data = response.data; + + setData(data); + + setForm({ + description: data.description || '', + phone: data.phone || '', + email: data.email || '', + address: data.address || '', + website: data.website || '', + contactPerson: data.contactPerson || '', + active: data.active || false, + }); + + setExistingImages(data.images || []); + } catch (err) { + setError(err.response?.data || 'Fehler beim Laden'); + } + }; + fetchData(); + }, [id]); + + const handleChange = (e) => { + const { name, value, type, checked } = e.target; + setForm((prev) => ({ + ...prev, + [name]: type === 'checkbox' ? checked : value + })); + }; + + const handleRemoveExisting = (index) => { + setExistingImages((prev) => prev.filter((_, i) => i !== index)); + }; + + const handleSubmit = (e) => { + /* + Drauf achten alte und neue Bilder zu verwenden + Vielleicht extra Feld für Anzeigebild, anstatt erstes zu nehmen + */ + } + + const handleFileChange = (e) => { + const selected = Array.from(e.target.files); + setFiles((prev) => [...prev, ...selected]); + const newPreviews = selected.map((f) => URL.createObjectURL(f)); + setPreviews((prev) => [...prev, ...newPreviews]); + e.target.value = ''; + }; + + const handleRemoveFile = (index) => { + URL.revokeObjectURL(previews[index]); + setFiles((prev) => prev.filter((_, i) => i !== index)); + setPreviews((prev) => prev.filter((_, i) => i !== index)); + }; + + return ( + + + navigate('/organizations')} size="small"> + + + Organisation bearbeiten + + + + + + + {error && {error}} + + + + + + + + + setForm((prev) => ({ ...prev, active: e.target.checked })) + } + /> + } + label="Aktiv" + /> + + + + {/* Bild-Upload */} + + + Bilder + {files.length > 0 && ( + + Das erste Bild wird als Vorschaubild verwendet + + )} + {existingImages.map((src, i) => ( + + + handleRemoveExisting(i)}> + + + + ))} + + + + {previews.map((src, i) => ( + + + + + {i === 0 && ( + } + label="Vorschaubild" + size="small" + color="primary" + sx={{ height: 20, fontSize: 11 }} + /> + )} + + + {files[i]?.name} + + + handleRemoveFile(i)}> + + + + ))} + + + + + + + + + + + + + + + + ) +} \ No newline at end of file diff --git a/src/pages/OrganizationPage.jsx b/src/pages/OrganizationPage.jsx index bec8c0d..503703c 100644 --- a/src/pages/OrganizationPage.jsx +++ b/src/pages/OrganizationPage.jsx @@ -9,6 +9,8 @@ import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import AddIcon from '@mui/icons-material/Add'; import { Chip } from '@mui/material'; import axiosInstance from '../api/axiosInstance'; +import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; +import {useNavigate} from "react-router-dom"; export default function OrganizationPage() { const [rows, setRows] = useState([]); @@ -18,6 +20,7 @@ export default function OrganizationPage() { const [open, setOpen] = useState(false); const [creating, setCreating] = useState(false); const [creatingError, setCreatingError] = useState(''); + const navigate = useNavigate(); const [createForm, setCreateForm] = useState({ name: '', @@ -83,6 +86,10 @@ export default function OrganizationPage() { } }; + const handleEdit = (id) => { + navigate(`/organizations/${id}/edit`); + } + const columns = [ { field: 'id', headerName: 'ID', width: 70 }, { field: 'name', headerName: 'Name', flex: 1.5 }, @@ -112,13 +119,16 @@ export default function OrganizationPage() { { field: 'actions', headerName: '', - width: 60, + width: 120, sortable: false, renderCell: ({ row }) => ( handleDelete(row.id)}> + handleEdit(row.id)}> + + ), }, diff --git a/src/pages/UsersPage.jsx b/src/pages/UsersPage.jsx index 82e12fb..61dc447 100644 --- a/src/pages/UsersPage.jsx +++ b/src/pages/UsersPage.jsx @@ -193,6 +193,7 @@ export default function UsersPage() { ADMIN REPORTER USER + SITE_OWNER