Delete implemnetier - Alles im frontend
This commit is contained in:
@@ -2,8 +2,8 @@
|
|||||||
"name": "aerzte",
|
"name": "aerzte",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"vdate": "2025-11-23 11:00 UTC",
|
"vdate": "2025-11-23 16:00 UTC",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|||||||
@@ -40,3 +40,34 @@
|
|||||||
.read-the-docs {
|
.read-the-docs {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* In AppStyles.css */
|
||||||
|
|
||||||
|
/* Stellt den Delete-Button in die rechte Ecke des Headers */
|
||||||
|
.appointment-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between; /* Verteilt Checkbox/Label und Button */
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doctor-info {
|
||||||
|
/* Nimmt den verfügbaren Platz ein */
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #dc3545;
|
||||||
|
font-size: 1.2em;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 15px;
|
||||||
|
padding: 0 5px;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button:hover {
|
||||||
|
color: #c82333;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import AppointmentForm from './components/AppointmentForm';
|
import AppointmentForm from './components/AppointmentForm';
|
||||||
import AppointmentList from './components/AppointmentList';
|
import AppointmentList from './components/AppointmentList';
|
||||||
|
import ConfirmationModal from './components/ConfirmationModal';
|
||||||
import './AppStyles.css';
|
import './AppStyles.css';
|
||||||
|
|
||||||
// Die Basis-URL der Express-API
|
// Die Basis-URL der Express-API
|
||||||
@@ -22,6 +23,8 @@ const normalizeAppointment = (appointment) => ({
|
|||||||
function App() {
|
function App() {
|
||||||
const [appointments, setAppointments] = useState([]);
|
const [appointments, setAppointments] = useState([]);
|
||||||
const [editingAppointment, setEditingAppointment] = useState(null);
|
const [editingAppointment, setEditingAppointment] = useState(null);
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [appointmentToDeleteId, setAppointmentToDeleteId] = useState(null);
|
||||||
|
|
||||||
// NEU: Lädt Termine vom Backend beim Start
|
// NEU: Lädt Termine vom Backend beim Start
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -137,6 +140,50 @@ function App() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 4. DELETE LOGIK START
|
||||||
|
|
||||||
|
// 1. Wird vom Löschen-Button in der Liste aufgerufen (öffnet das Modal)
|
||||||
|
const openDeleteModal = (id) => {
|
||||||
|
setAppointmentToDeleteId(id);
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Wird vom 'Abbrechen'-Button des Modals aufgerufen
|
||||||
|
const handleCancelDelete = () => {
|
||||||
|
setAppointmentToDeleteId(null);
|
||||||
|
setIsModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. Wird vom 'Bestätigen'-Button des Modals aufgerufen (führt die Löschung aus)
|
||||||
|
const handleConfirmDelete = async () => {
|
||||||
|
const id = appointmentToDeleteId;
|
||||||
|
|
||||||
|
// Status zurücksetzen, bevor die asynchrone Funktion aufgerufen wird
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setAppointmentToDeleteId(null);
|
||||||
|
|
||||||
|
if (!id) return; // Sicherheit
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_URL}/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status !== 204) throw new Error('Fehler beim Löschen des Termins.');
|
||||||
|
|
||||||
|
// Lokalen State synchronisieren
|
||||||
|
setAppointments(prev => prev.filter(app => app.id !== id));
|
||||||
|
|
||||||
|
if (editingAppointment && editingAppointment.id === id) {
|
||||||
|
setEditingAppointment(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Fehler beim Löschen des Termins:", error);
|
||||||
|
// Optional: Fehlermeldung anzeigen
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ... (startEdit und cancelEdit bleiben unverändert, sie manipulieren nur den lokalen State)
|
// ... (startEdit und cancelEdit bleiben unverändert, sie manipulieren nur den lokalen State)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -153,6 +200,15 @@ function App() {
|
|||||||
appointments={appointments}
|
appointments={appointments}
|
||||||
onToggleDone={toggleDone}
|
onToggleDone={toggleDone}
|
||||||
onEdit={(app) => setEditingAppointment(app)}
|
onEdit={(app) => setEditingAppointment(app)}
|
||||||
|
onDelete={openDeleteModal}
|
||||||
|
/>
|
||||||
|
{/* NEU: MODAL KOMPONENTE */}
|
||||||
|
<ConfirmationModal
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
title="Termin löschen bestätigen"
|
||||||
|
message="Möchten Sie diesen Termin wirklich unwiderruflich löschen? Diese Aktion kann nicht rückgängig gemacht werden."
|
||||||
|
onConfirm={handleConfirmDelete}
|
||||||
|
onCancel={handleCancelDelete}
|
||||||
/>
|
/>
|
||||||
{/* Debug Output kann nun entfernt werden */}
|
{/* Debug Output kann nun entfernt werden */}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const formatDate = (date) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// NEUE PROP: onEdit
|
// NEUE PROP: onEdit
|
||||||
const AppointmentItem = ({ appointment, onToggleDone, onEdit }) => {
|
const AppointmentItem = ({ appointment, onToggleDone, onEdit, onDelete }) => {
|
||||||
const { id, arztName, arztArt, termin, erledigt, bemerkungen } = appointment;
|
const { id, arztName, arztArt, termin, erledigt, bemerkungen } = appointment;
|
||||||
|
|
||||||
const itemClass = erledigt ? 'appointment-item done' : 'appointment-item';
|
const itemClass = erledigt ? 'appointment-item done' : 'appointment-item';
|
||||||
@@ -28,6 +28,16 @@ const AppointmentItem = ({ appointment, onToggleDone, onEdit }) => {
|
|||||||
<label htmlFor={`check-${id}`} className="doctor-info">
|
<label htmlFor={`check-${id}`} className="doctor-info">
|
||||||
<strong>{arztName}</strong> {arztArt && <span className="arzt-art">({arztArt})</span>}
|
<strong>{arztName}</strong> {arztArt && <span className="arzt-art">({arztArt})</span>}
|
||||||
</label>
|
</label>
|
||||||
|
<button
|
||||||
|
className="delete-button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation(); // Verhindert, dass das Bearbeiten ausgelöst wird
|
||||||
|
onDelete(id);
|
||||||
|
}}
|
||||||
|
title="Termin löschen"
|
||||||
|
>
|
||||||
|
🗑️
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="appointment-date">
|
<p className="appointment-date">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import AppointmentItem from './AppointmentItem';
|
import AppointmentItem from './AppointmentItem';
|
||||||
|
|
||||||
// NEUE PROP: onEdit
|
// NEUE PROP: onEdit
|
||||||
const AppointmentList = ({ appointments, onToggleDone, onEdit }) => {
|
const AppointmentList = ({ appointments, onToggleDone, onEdit, onDelete }) => {
|
||||||
|
|
||||||
if (appointments.length === 0) {
|
if (appointments.length === 0) {
|
||||||
return <p className="no-appointments-message">Keine Termine vorhanden. Fügen Sie oben einen neuen Termin hinzu!</p>;
|
return <p className="no-appointments-message">Keine Termine vorhanden. Fügen Sie oben einen neuen Termin hinzu!</p>;
|
||||||
@@ -19,6 +19,7 @@ const AppointmentList = ({ appointments, onToggleDone, onEdit }) => {
|
|||||||
appointment={appointment}
|
appointment={appointment}
|
||||||
onToggleDone={onToggleDone}
|
onToggleDone={onToggleDone}
|
||||||
onEdit={onEdit} // Funktion an das Item weitergeben
|
onEdit={onEdit} // Funktion an das Item weitergeben
|
||||||
|
onDelete={onDelete} // NEU: Übergabe an das Item
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
40
frontend/src/components/ConfirmationModal.jsx
Normal file
40
frontend/src/components/ConfirmationModal.jsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// src/components/ConfirmationModal.jsx
|
||||||
|
import React from 'react';
|
||||||
|
import './ModalStyles.css'; // Wird gleich definiert
|
||||||
|
|
||||||
|
const ConfirmationModal = ({ isOpen, title, message, onConfirm, onCancel }) => {
|
||||||
|
if (!isOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
// Hintergrund-Overlay (schließt das Modal bei Klick außerhalb)
|
||||||
|
<div className="modal-overlay" onClick={onCancel}>
|
||||||
|
<div
|
||||||
|
className="modal-content"
|
||||||
|
// WICHTIG: Stoppt Event-Bubbling, damit Klick auf Content das Modal nicht schließt
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<h3 className="modal-title">{title}</h3>
|
||||||
|
<p className="modal-message">{message}</p>
|
||||||
|
|
||||||
|
<div className="modal-actions">
|
||||||
|
<button
|
||||||
|
className="modal-button cancel"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="modal-button confirm"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Bestätigen und Löschen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmationModal;
|
||||||
74
frontend/src/components/ModalStyles.css
Normal file
74
frontend/src/components/ModalStyles.css
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/* src/components/ModalStyles.css */
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6); /* Dunkles, halbtransparentes Overlay */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000; /* Stellt sicher, dass es über allem liegt */
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 450px;
|
||||||
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(-20px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
color: #dc3545;
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-message {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; /* Buttons nach rechts ausrichten */
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button {
|
||||||
|
padding: 10px 18px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button.cancel {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button.cancel:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button.confirm {
|
||||||
|
background-color: #dc3545; /* Rot für Löschaktion */
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button.confirm:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user