V 1.1.0: Recht gut ausgebaut jetzt
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"csv-parse": "^6.1.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0"
|
"react-dom": "^19.2.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -257,6 +257,16 @@ body {
|
|||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.request-cell {
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appointment-cell {
|
||||||
|
min-width: 110px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.client-name {
|
.client-name {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #007bff;
|
color: #007bff;
|
||||||
@@ -267,6 +277,11 @@ body {
|
|||||||
color: #28a745;
|
color: #28a745;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.payment-cell.included-in-sum {
|
||||||
|
color: #0066cc;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.actions-cell {
|
.actions-cell {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -292,14 +307,58 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Style für die Gesamtsumme oberhalb der Liste */
|
/* Style für die Gesamtsumme oberhalb der Liste */
|
||||||
|
.sum-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sum-filter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sum-filter label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-filter-input {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-filter-button {
|
||||||
|
background: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9em;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-filter-button:hover {
|
||||||
|
background: #c82333;
|
||||||
|
}
|
||||||
|
|
||||||
.sum-display {
|
.sum-display {
|
||||||
color: #28a745;
|
color: #28a745;
|
||||||
background-color: #e2f0d9;
|
background-color: #e2f0d9;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tab Navigation */
|
/* Tab Navigation */
|
||||||
@@ -421,6 +480,31 @@ body {
|
|||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-with-unit {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.narrow-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-label {
|
||||||
|
color: #495057;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transport-select {
|
||||||
|
flex: 1;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
.input-group-row {
|
.input-group-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 15px; /* Abstand zwischen den Feldern in einer Reihe */
|
gap: 15px; /* Abstand zwischen den Feldern in einer Reihe */
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ import './AppStyles.css'; // Wiederverwendung der Styles
|
|||||||
const API_URL = 'http://localhost:3002/api/services';
|
const API_URL = 'http://localhost:3002/api/services';
|
||||||
|
|
||||||
const normalizeServiceEntry = (entry) => ({
|
const normalizeServiceEntry = (entry) => ({
|
||||||
|
...entry,
|
||||||
id: entry._id,
|
id: entry._id,
|
||||||
// Sicherstellen, dass alle Date-Felder Date-Objekte sind
|
// Sicherstellen, dass alle Date-Felder Date-Objekte sind
|
||||||
requestDate: entry.requestDate ? new Date(entry.requestDate) : null,
|
requestDate: entry.requestDate ? new Date(entry.requestDate) : null,
|
||||||
appointmentDate: entry.appointmentDate ? new Date(entry.appointmentDate) : null, ...entry
|
appointmentDate: entry.appointmentDate ? new Date(entry.appointmentDate) : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
function ServiceApp() {
|
function ServiceApp() {
|
||||||
@@ -21,22 +22,44 @@ function ServiceApp() {
|
|||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [entryToDeleteId, setEntryToDeleteId] = useState(null);
|
const [entryToDeleteId, setEntryToDeleteId] = useState(null);
|
||||||
const [activeTab, setActiveTab] = useState('liste');
|
const [activeTab, setActiveTab] = useState('liste');
|
||||||
|
const [startDate, setStartDate] = useState(() => {
|
||||||
|
return localStorage.getItem('sumStartDate') || '';
|
||||||
|
});
|
||||||
|
|
||||||
// Berechnet die laufende Summe aller bezahlten Beträge
|
// Startdatum im localStorage speichern, wenn es sich ändert
|
||||||
|
useEffect(() => {
|
||||||
|
if (startDate) {
|
||||||
|
localStorage.setItem('sumStartDate', startDate);
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('sumStartDate');
|
||||||
|
}
|
||||||
|
}, [startDate]);
|
||||||
|
|
||||||
|
// Berechnet die laufende Summe aller bezahlten Beträge ab dem Startdatum
|
||||||
const cumulativeSum = useMemo(() => {
|
const cumulativeSum = useMemo(() => {
|
||||||
return entries.reduce((sum, entry) => sum + entry.paidAmount, 0);
|
return entries
|
||||||
}, [entries]);
|
.filter(entry => {
|
||||||
|
if (!startDate) return true;
|
||||||
|
const entryDate = entry.appointmentDate;
|
||||||
|
const filterDate = new Date(startDate);
|
||||||
|
return entryDate >= filterDate;
|
||||||
|
})
|
||||||
|
.reduce((sum, entry) => sum + entry.paidAmount, 0);
|
||||||
|
}, [entries, startDate]);
|
||||||
|
|
||||||
// --- CRUD OPERATIONEN ---
|
// --- CRUD OPERATIONEN ---
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const fetchEntries = async () => {
|
const fetchEntries = async () => {
|
||||||
// ... (fetch-Logik) ...
|
try {
|
||||||
const response = await fetch(API_URL);
|
const response = await fetch(API_URL);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setEntries(data.map(normalizeServiceEntry));
|
setEntries(data.map(normalizeServiceEntry));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden der Einträge:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchEntries();
|
fetchEntries();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -55,7 +78,7 @@ function ServiceApp() {
|
|||||||
const updateEntry = async (updatedEntry) => {
|
const updateEntry = async (updatedEntry) => {
|
||||||
// ... (PUT-Logik, wie in App.js)
|
// ... (PUT-Logik, wie in App.js)
|
||||||
const mongoId = updatedEntry.id;
|
const mongoId = updatedEntry.id;
|
||||||
const response = await fetch(`${API_URL}/${mongoId}`, {
|
await fetch(`${API_URL}/${mongoId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(updatedEntry),
|
body: JSON.stringify(updatedEntry),
|
||||||
@@ -123,7 +146,31 @@ function ServiceApp() {
|
|||||||
{activeTab === 'liste' && (
|
{activeTab === 'liste' && (
|
||||||
<div className="tab-panel">
|
<div className="tab-panel">
|
||||||
<h2>Einträge ({entries.length})</h2>
|
<h2>Einträge ({entries.length})</h2>
|
||||||
<h3 className="sum-display">Gesamtsumme der Beträge: **{cumulativeSum.toFixed(2)} €**</h3>
|
|
||||||
|
<div className="sum-container">
|
||||||
|
<div className="sum-filter">
|
||||||
|
<label htmlFor="startDate">Summe ab:</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
id="startDate"
|
||||||
|
value={startDate}
|
||||||
|
onChange={(e) => setStartDate(e.target.value)}
|
||||||
|
className="date-filter-input"
|
||||||
|
/>
|
||||||
|
{startDate && (
|
||||||
|
<button
|
||||||
|
onClick={() => setStartDate('')}
|
||||||
|
className="clear-filter-button"
|
||||||
|
title="Filter zurücksetzen"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="sum-display">
|
||||||
|
Gesamtsumme der Beträge: <strong>{cumulativeSum.toFixed(2)} €</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ServiceList
|
<ServiceList
|
||||||
entries={entries}
|
entries={entries}
|
||||||
@@ -132,6 +179,7 @@ function ServiceApp() {
|
|||||||
setActiveTab('eingabe');
|
setActiveTab('eingabe');
|
||||||
}}
|
}}
|
||||||
onDelete={openDeleteModal}
|
onDelete={openDeleteModal}
|
||||||
|
startDate={startDate}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// seniorendienst-frontend/src/components/CSVImport.jsx
|
// seniorendienst-frontend/src/components/CSVImport.jsx
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
|
import { parse } from 'csv-parse/browser/esm/sync';
|
||||||
|
|
||||||
const CSVImport = ({ onImport }) => {
|
const CSVImport = ({ onImport }) => {
|
||||||
const fileInputRef = useRef(null);
|
const fileInputRef = useRef(null);
|
||||||
@@ -13,16 +14,24 @@ const CSVImport = ({ onImport }) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const text = await file.text();
|
const text = await file.text();
|
||||||
const lines = text.split('\n').filter(line => line.trim());
|
|
||||||
|
|
||||||
// Erste Zeile als Header überspringen
|
// Führendes Semikolon aus jeder Zeile entfernen
|
||||||
const dataLines = lines.slice(1);
|
const cleanedText = text
|
||||||
|
.split('\n')
|
||||||
|
.map(line => line.startsWith(';') ? line.substring(1) : line)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
const entries = dataLines.map(line => {
|
// CSV mit csv-parse parsen
|
||||||
// CSV-Zeile parsen (einfaches Komma-getrennt)
|
const records = parse(cleanedText, {
|
||||||
const columns = line.split(',').map(col => col.trim());
|
delimiter: ';',
|
||||||
|
skip_empty_lines: true,
|
||||||
|
from_line: 2, // Erste Zeile (Header) überspringen
|
||||||
|
relax_column_count: true, // Erlaubt unterschiedliche Spaltenanzahlen
|
||||||
|
trim: true,
|
||||||
|
});
|
||||||
|
|
||||||
// Erwartetes Format: Name,Vorname,Straße,PLZ/Ort,Telefon,Email,Anfrage,Termin,Zeit,Fahrtzeit,Strecke,Transport,Dauer,Durchgeführt,Bemerkungen,Bezahlt
|
const entries = records.map(columns => {
|
||||||
|
// Erwartetes Format: Name,Vorname,Straße,PLZ/Ort,Telefon,Email,Anfrage,Termin,Dauer,Fahrtzeit,Strecke,Transport,Bezahlt,Durchgeführt,Bemerkungen
|
||||||
return {
|
return {
|
||||||
name: columns[0] || '',
|
name: columns[0] || '',
|
||||||
firstName: columns[1] || '',
|
firstName: columns[1] || '',
|
||||||
@@ -31,14 +40,14 @@ const CSVImport = ({ onImport }) => {
|
|||||||
phone: columns[4] || '',
|
phone: columns[4] || '',
|
||||||
email: columns[5] || '',
|
email: columns[5] || '',
|
||||||
requestDate: columns[6] ? new Date(columns[6]) : new Date(),
|
requestDate: columns[6] ? new Date(columns[6]) : new Date(),
|
||||||
appointmentDate: columns[7] && columns[8] ? new Date(`${columns[7]}T${columns[8]}`) : new Date(),
|
appointmentDate: columns[7] ? new Date(columns[7]) : new Date(),
|
||||||
|
workDuration: Number(columns[8]) || 0,
|
||||||
travelTime: Number(columns[9]) || 0,
|
travelTime: Number(columns[9]) || 0,
|
||||||
distance: Number(columns[10]) || 0,
|
distance: Number(columns[10]) || 0,
|
||||||
transport: columns[11] || 'Auto',
|
transport: columns[11] || 'Auto',
|
||||||
workDuration: Number(columns[12]) || 0,
|
paidAmount: Number(columns[12]) || 0,
|
||||||
taskDone: columns[13] || '',
|
taskDone: columns[13] || '',
|
||||||
remarks: columns[14] || '',
|
remarks: columns[14] || '',
|
||||||
paidAmount: Number(columns[15]) || 0,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,7 +80,7 @@ const CSVImport = ({ onImport }) => {
|
|||||||
{isProcessing ? '⏳ Importiere...' : '📁 CSV importieren'}
|
{isProcessing ? '⏳ Importiere...' : '📁 CSV importieren'}
|
||||||
</label>
|
</label>
|
||||||
<small className="csv-hint">
|
<small className="csv-hint">
|
||||||
Format: Name,Vorname,Straße,PLZ/Ort,Telefon,Email,Anfrage,Termin,Zeit,Fahrtzeit,Strecke,Transport,Dauer,Durchgeführt,Bemerkungen,Bezahlt
|
Format: Name,Vorname,Straße,PLZ/Ort,Telefon,Email,Anfrage,Termin (YYYY-MM-DD HH:MM),Dauer,Fahrtzeit,Strecke,Transport,Bezahlt,Durchgeführt,Bemerkungen
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,16 +17,18 @@ const formatDateToInput = (date) => {
|
|||||||
|
|
||||||
const ServiceForm = ({ onAddEntry, editingEntry, onUpdateEntry, onCancelEdit, cumulativeSum }) => {
|
const ServiceForm = ({ onAddEntry, editingEntry, onUpdateEntry, onCancelEdit, cumulativeSum }) => {
|
||||||
|
|
||||||
const initialFormData = {
|
const getInitialFormData = () => ({
|
||||||
name: '', firstName: '', street: '', zipCity: '', phone: '', email: '',
|
name: '', firstName: '', street: '', zipCity: '', phone: '', email: '',
|
||||||
requestDate: formatDateToInput(new Date()),
|
requestDate: formatDateToInput(new Date()),
|
||||||
appointmentDate: formatDateToInput(new Date()), appointmentTime: formatTimeToInput(new Date()),
|
appointmentDate: formatDateToInput(new Date()), appointmentTime: formatTimeToInput(new Date()),
|
||||||
travelTime: 0, distance: 0, transport: 'Auto', workDuration: 0,
|
travelTime: 0, distance: 0, transport: 'Auto', workDuration: 0,
|
||||||
taskDone: '', remarks: '', paidAmount: 0,
|
taskDone: '', remarks: '', paidAmount: 0,
|
||||||
};
|
});
|
||||||
const [formData, setFormData] = useState(initialFormData);
|
|
||||||
|
const [formData, setFormData] = useState(getInitialFormData);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Formular mit editingEntry-Daten vorausfüllen
|
||||||
if (editingEntry) {
|
if (editingEntry) {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: editingEntry.name || '',
|
name: editingEntry.name || '',
|
||||||
@@ -47,7 +49,7 @@ const ServiceForm = ({ onAddEntry, editingEntry, onUpdateEntry, onCancelEdit, cu
|
|||||||
paidAmount: editingEntry.paidAmount || 0,
|
paidAmount: editingEntry.paidAmount || 0,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setFormData(initialFormData);
|
setFormData(getInitialFormData());
|
||||||
}
|
}
|
||||||
}, [editingEntry]);
|
}, [editingEntry]);
|
||||||
|
|
||||||
@@ -122,10 +124,19 @@ const ServiceForm = ({ onAddEntry, editingEntry, onUpdateEntry, onCancelEdit, cu
|
|||||||
|
|
||||||
<div className="input-group-row">
|
<div className="input-group-row">
|
||||||
<label>Dauer/Fahrt</label>
|
<label>Dauer/Fahrt</label>
|
||||||
<input type="number" name="workDuration" value={formData.workDuration} onChange={handleChange} placeholder="Arbeitszeit (Min)" required />
|
<div className="input-with-unit">
|
||||||
<input type="number" name="travelTime" value={formData.travelTime} onChange={handleChange} placeholder="Fahrzeit (Min)" required />
|
<input type="number" name="workDuration" value={formData.workDuration} onChange={handleChange} placeholder="Arbeitszeit" required className="narrow-input" />
|
||||||
<input type="number" name="distance" value={formData.distance} onChange={handleChange} placeholder="Strecke (km)" step="0.1" />
|
<span className="unit-label">min</span>
|
||||||
<select name="transport" value={formData.transport} onChange={handleChange}>
|
</div>
|
||||||
|
<div className="input-with-unit">
|
||||||
|
<input type="number" name="travelTime" value={formData.travelTime} onChange={handleChange} placeholder="Fahrzeit" required className="narrow-input" />
|
||||||
|
<span className="unit-label">min</span>
|
||||||
|
</div>
|
||||||
|
<div className="input-with-unit">
|
||||||
|
<input type="number" name="distance" value={formData.distance} onChange={handleChange} placeholder="Strecke" step="0.1" className="narrow-input" />
|
||||||
|
<span className="unit-label">km</span>
|
||||||
|
</div>
|
||||||
|
<select name="transport" value={formData.transport} onChange={handleChange} className="transport-select">
|
||||||
<option value="Auto">Auto</option>
|
<option value="Auto">Auto</option>
|
||||||
<option value="ÖPNV">ÖPNV</option>
|
<option value="ÖPNV">ÖPNV</option>
|
||||||
<option value="Fahrrad">Fahrrad</option>
|
<option value="Fahrrad">Fahrrad</option>
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ import React from 'react';
|
|||||||
|
|
||||||
const formatDateTime = (date) => {
|
const formatDateTime = (date) => {
|
||||||
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
||||||
return 'N/A';
|
return { date: 'N/A', time: '' };
|
||||||
}
|
}
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
const day = String(date.getDate()).padStart(2, '0');
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
const hours = String(date.getHours()).padStart(2, '0');
|
const hours = String(date.getHours()).padStart(2, '0');
|
||||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
return {
|
||||||
|
date: `${year}-${month}-${day}`,
|
||||||
|
time: `${hours}:${minutes}`
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (date) => {
|
const formatDate = (date) => {
|
||||||
@@ -23,12 +26,15 @@ const formatDate = (date) => {
|
|||||||
return `${year}-${month}-${day}`;
|
return `${year}-${month}-${day}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ServiceItem = ({ entry, onEdit, onDelete }) => {
|
const ServiceItem = ({ entry, onEdit, onDelete, startDate }) => {
|
||||||
const {
|
const {
|
||||||
id, name, firstName, street, zipCity,
|
id, name, firstName, street, zipCity,
|
||||||
requestDate, appointmentDate, paidAmount
|
requestDate, appointmentDate, paidAmount
|
||||||
} = entry;
|
} = entry;
|
||||||
|
|
||||||
|
// Prüfen, ob dieser Eintrag in die Summenberechnung eingeht
|
||||||
|
const isIncludedInSum = !startDate || (appointmentDate && appointmentDate >= new Date(startDate));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className="service-row">
|
<tr className="service-row">
|
||||||
<td className="request-cell">{formatDate(requestDate)}</td>
|
<td className="request-cell">{formatDate(requestDate)}</td>
|
||||||
@@ -37,8 +43,20 @@ const ServiceItem = ({ entry, onEdit, onDelete }) => {
|
|||||||
</td>
|
</td>
|
||||||
<td className="address-cell">{street || '-'}</td>
|
<td className="address-cell">{street || '-'}</td>
|
||||||
<td className="address-cell">{zipCity || '-'}</td>
|
<td className="address-cell">{zipCity || '-'}</td>
|
||||||
<td className="appointment-cell">{formatDateTime(appointmentDate)}</td>
|
<td className="appointment-cell">
|
||||||
<td className="payment-cell">{paidAmount.toFixed(2)} €</td>
|
{(() => {
|
||||||
|
const { date, time } = formatDateTime(appointmentDate);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{date}
|
||||||
|
{time && <><br />{time}</>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</td>
|
||||||
|
<td className={`payment-cell ${isIncludedInSum ? 'included-in-sum' : ''}`}>
|
||||||
|
{paidAmount ? paidAmount.toFixed(2) : '0.00'} €
|
||||||
|
</td>
|
||||||
<td className="actions-cell">
|
<td className="actions-cell">
|
||||||
<button
|
<button
|
||||||
className="edit-button"
|
className="edit-button"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ServiceItem from './ServiceItem';
|
import ServiceItem from './ServiceItem';
|
||||||
|
|
||||||
const ServiceList = ({ entries, onEdit, onDelete }) => {
|
const ServiceList = ({ entries, onEdit, onDelete, startDate }) => {
|
||||||
|
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
return <p className="no-appointments-message">Noch keine Service-Einträge vorhanden.</p>;
|
return <p className="no-appointments-message">Noch keine Service-Einträge vorhanden.</p>;
|
||||||
@@ -24,13 +24,18 @@ const ServiceList = ({ entries, onEdit, onDelete }) => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{entries
|
{entries
|
||||||
.sort((a, b) => a.appointmentDate - b.appointmentDate)
|
.sort((a, b) => {
|
||||||
|
const dateA = a.requestDate instanceof Date ? a.requestDate : new Date(a.requestDate);
|
||||||
|
const dateB = b.requestDate instanceof Date ? b.requestDate : new Date(b.requestDate);
|
||||||
|
return dateB - dateA;
|
||||||
|
})
|
||||||
.map(entry => (
|
.map(entry => (
|
||||||
<ServiceItem
|
<ServiceItem
|
||||||
key={entry.id}
|
key={entry.id}
|
||||||
entry={entry}
|
entry={entry}
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
|
startDate={startDate}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user