Ermögliche das Abrufen von Zeitkorrekturen für ein spezifisches Datum und aktualisiere die Benutzeroberfläche entsprechend
All checks were successful
Deploy production / deploy (push) Successful in 48s

This commit is contained in:
Torsten Schulz (local)
2026-08-26 16:19:39 +02:00
parent 4e6eeb470d
commit 4787700cd0
7 changed files with 77 additions and 77 deletions

View File

@@ -30,7 +30,7 @@ class TimefixController {
async getTodayTimefixes(req, res) { async getTodayTimefixes(req, res) {
try { try {
const userId = req.user.userId; const userId = req.user.userId;
const timefixes = await timefixService.getTodayTimefixes(userId); const timefixes = await timefixService.getTodayTimefixes(userId, req.query.date);
res.json(timefixes); res.json(timefixes);
} catch (error) { } catch (error) {
console.error('Fehler beim Abrufen der Timefixes:', error); console.error('Fehler beim Abrufen der Timefixes:', error);

View File

@@ -386,6 +386,7 @@ class WorklogRepository {
if (startEntry) { if (startEntry) {
pairs.push({ pairs.push({
id: startEntry.id, id: startEntry.id,
end_id: entry.id,
start_time: startEntry.tstamp, start_time: startEntry.tstamp,
end_time: entry.tstamp, end_time: entry.tstamp,
start_state: startEntry.state, start_state: startEntry.state,

View File

@@ -1387,11 +1387,11 @@ class TimeEntryService {
let workEndUTC = new Date(pair.end_time); let workEndUTC = new Date(pair.end_time);
// Prüfe auf Timefix-Korrekturen // Prüfe auf Timefix-Korrekturen
const endFixEntry = allEntries.find(e => { // Direkt über die vom Repository gelieferte Stop-ID nachschlagen.
const action = (typeof e.state === 'string' ? JSON.parse(e.state) : e.state)?.action || e.state; // So wird eine Datumsänderung des Stop-Eintrags zuverlässig erkannt.
return action === 'stop work' && e.relatedTo_id === pair.id; const endFix = pair.end_id
}); ? timefixMap.get(pair.end_id)?.find(f => f.fix_type === 'stop work')
const endFix = endFixEntry ? timefixMap.get(endFixEntry.id)?.find(f => f.fix_type === 'stop work') : null; : null;
// Verwende korrigierte Zeiten falls vorhanden // Verwende korrigierte Zeiten falls vorhanden
const originalStartTime = workStartUTC; const originalStartTime = workStartUTC;

View File

@@ -131,21 +131,21 @@ class TimefixService {
} }
/** /**
* Holt alle Zeitkorrekturen für den heutigen Tag * Holt alle Zeitkorrekturen für ein korrigiertes Datum
* @param {number} userId - Benutzer-ID * @param {number} userId - Benutzer-ID
* @param {string} date - Optionales Datum im Format YYYY-MM-DD
* @returns {Promise<Array>} Array von Zeitkorrekturen * @returns {Promise<Array>} Array von Zeitkorrekturen
*/ */
async getTodayTimefixes(userId) { async getTodayTimefixes(userId, date = null) {
const sequelize = database.sequelize; const sequelize = database.sequelize;
const requestedDate = date || new Date().toISOString().split('T')[0];
// Berechne Start und Ende des heutigen Tages (lokale Zeit) if (!/^\d{4}-\d{2}-\d{2}$/.test(requestedDate)) {
const now = new Date(); throw new Error('Ungültiges Datum für Zeitkorrekturen');
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0); }
const todayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);
// Hole alle Timefixes für heute mit Raw SQL const todayStartStr = `${requestedDate} 00:00:00`;
const todayStartStr = `${todayStart.getFullYear()}-${String(todayStart.getMonth() + 1).padStart(2, '0')}-${String(todayStart.getDate()).padStart(2, '0')} 00:00:00`; const todayEndStr = `${requestedDate} 23:59:59`;
const todayEndStr = `${todayEnd.getFullYear()}-${String(todayEnd.getMonth() + 1).padStart(2, '0')}-${String(todayEnd.getDate()).padStart(2, '0')} 23:59:59`;
const timefixes = await sequelize.query( const timefixes = await sequelize.query(
`SELECT id, user_id, worklog_id, fix_type, fix_date_time `SELECT id, user_id, worklog_id, fix_type, fix_date_time

View File

@@ -185,6 +185,11 @@ copy_project_files() {
print_info "Kopiere Frontend..." print_info "Kopiere Frontend..."
rsync -av --exclude 'node_modules' --exclude 'dist' --exclude '.env*' --delete "$CURRENT_DIR/frontend/" "$FRONTEND_DIR/" rsync -av --exclude 'node_modules' --exclude 'dist' --exclude '.env*' --delete "$CURRENT_DIR/frontend/" "$FRONTEND_DIR/"
# package.json und package-lock.json gehören zum npm-Workspace im Root und
# gelten gemeinsam für Backend und Frontend.
print_info "Kopiere Workspace-Metadaten..."
rsync -av "$CURRENT_DIR/package.json" "$CURRENT_DIR/package-lock.json" "$PROJECT_DIR/"
print_info "Kopiere Root-Dateien..." print_info "Kopiere Root-Dateien..."
rsync -av --exclude 'node_modules' --exclude '.git' --exclude 'frontend' --exclude 'backend' \ rsync -av --exclude 'node_modules' --exclude '.git' --exclude 'frontend' --exclude 'backend' \
"$CURRENT_DIR"/*.{sh,conf,md,js,json,service} "$PROJECT_DIR/" 2>/dev/null || true "$CURRENT_DIR"/*.{sh,conf,md,js,json,service} "$PROJECT_DIR/" 2>/dev/null || true
@@ -480,34 +485,23 @@ do_update() {
print_info "Kopiere Konfigurations-Dateien..." print_info "Kopiere Konfigurations-Dateien..."
rsync -av "$CURRENT_DIR"/*.{sh,conf,md,service} "$PROJECT_DIR/" 2>/dev/null || true rsync -av "$CURRENT_DIR"/*.{sh,conf,md,service} "$PROJECT_DIR/" 2>/dev/null || true
# Backend aktualisieren # Dependencies einmal im Workspace-Root installieren. Das Lockfile enthält
print_info "Aktualisiere Backend Dependencies..." # beide Teilprojekte; getrennte package-lock.json-Dateien sind absichtlich
cd $BACKEND_DIR # nicht vorhanden.
print_info "Aktualisiere Workspace-Dependencies..."
# Prüfe ob package-lock.json existiert cd "$PROJECT_DIR"
if [ ! -f "package-lock.json" ]; then
print_warning "package-lock.json fehlt! Verwende npm install statt npm ci"
npm install --no-audit --no-fund --loglevel=warn
else
npm config set fund false >/dev/null 2>&1 || true npm config set fund false >/dev/null 2>&1 || true
npm config set audit false >/dev/null 2>&1 || true npm config set audit false >/dev/null 2>&1 || true
npm config set progress false >/dev/null 2>&1 || true npm config set progress false >/dev/null 2>&1 || true
npm config set loglevel warn >/dev/null 2>&1 || true npm config set loglevel warn >/dev/null 2>&1 || true
npm ci --include=dev --no-audit --no-fund --loglevel=warn || {
# Backend braucht alle Dependencies (auch dev für Build-Tools) print_error "npm ci (Workspace-Update) fehlgeschlagen"
if ! npm ci --no-audit --no-fund --loglevel=warn 2>&1; then
print_warning "npm ci fehlgeschlagen. Fallback auf npm install..."
rm -rf node_modules
npm install --no-audit --no-fund --loglevel=warn || {
print_error "npm install (Backend Update) fehlgeschlagen"
exit 1 exit 1
} }
fi
fi
# Frontend aktualisieren # Frontend aktualisieren
print_info "Aktualisiere Frontend..." print_info "Aktualisiere Frontend..."
cd $FRONTEND_DIR cd "$FRONTEND_DIR"
# .env.production prüfen/erstellen # .env.production prüfen/erstellen
if [ ! -f ".env.production" ]; then if [ ! -f ".env.production" ]; then
@@ -518,27 +512,6 @@ VITE_API_URL=/api
EOF EOF
fi fi
# Prüfe ob package-lock.json existiert
if [ ! -f "package-lock.json" ]; then
print_warning "package-lock.json fehlt! Verwende npm install statt npm ci"
npm install --no-audit --no-fund --loglevel=warn
else
npm config set fund false >/dev/null 2>&1 || true
npm config set audit false >/dev/null 2>&1 || true
npm config set progress false >/dev/null 2>&1 || true
npm config set loglevel warn >/dev/null 2>&1 || true
# Frontend braucht dev-Dependencies für den Build (vite, etc.)
if ! npm ci --no-audit --no-fund --loglevel=warn 2>&1; then
print_warning "npm ci fehlgeschlagen. Fallback auf npm install..."
rm -rf node_modules
npm install --no-audit --no-fund --loglevel=warn || {
print_error "npm install (Frontend Update) fehlgeschlagen"
exit 1
}
fi
fi
# Sauberer Build # Sauberer Build
rm -rf dist/ rm -rf dist/
npm run build npm run build
@@ -824,4 +797,3 @@ case "${1:-help}" in
esac esac
exit 0 exit 0

View File

@@ -98,16 +98,22 @@
</form> </form>
</div> </div>
<!-- Tabelle der heutigen Zeitkorrekturen --> <!-- Tabelle der Zeitkorrekturen für ein korrigiertes Datum -->
<div class="card table-card"> <div class="card table-card">
<h2>Zeitkorrekturen von heute</h2> <div class="timefix-list-header">
<h2>Zeitkorrekturen</h2>
<label>
Korrigiertes Datum
<input v-model="timefixDate" type="date" @change="loadTimefixes" />
</label>
</div>
<div v-if="loading && timefixes.length === 0" class="loading"> <div v-if="loading && timefixes.length === 0" class="loading">
Lade Zeitkorrekturen... Lade Zeitkorrekturen...
</div> </div>
<div v-else-if="timefixes.length === 0" class="empty-state"> <div v-else-if="timefixes.length === 0" class="empty-state">
Keine Zeitkorrekturen für heute vorhanden. Keine Zeitkorrekturen für dieses Datum vorhanden.
</div> </div>
<table v-else class="timefix-table"> <table v-else class="timefix-table">
@@ -164,13 +170,15 @@ const authStore = useAuthStore()
const timefixes = ref([]) const timefixes = ref([])
const availableEntries = ref([]) const availableEntries = ref([])
const loading = ref(false) const loading = ref(false)
const today = new Date().toISOString().split('T')[0]
const timefixDate = ref(today)
const { showModal, modalConfig, alert, confirm, onConfirm, onCancel } = useModal() const { showModal, modalConfig, alert, confirm, onConfirm, onCancel } = useModal()
const form = ref({ const form = ref({
originalDate: new Date().toISOString().split('T')[0], originalDate: today,
worklogId: '', worklogId: '',
newDate: new Date().toISOString().split('T')[0], newDate: today,
newTime: '', newTime: '',
newAction: '' newAction: ''
}) })
@@ -223,11 +231,11 @@ function onEntrySelected() {
} }
} }
// Lade alle Zeitkorrekturen für heute // Lade alle Zeitkorrekturen für das ausgewählte korrigierte Datum
async function loadTimefixes() { async function loadTimefixes() {
try { try {
loading.value = true loading.value = true
const response = await fetch(`${API_URL}/timefix`, { const response = await fetch(`${API_URL}/timefix?date=${encodeURIComponent(timefixDate.value)}`, {
headers: authStore.getAuthHeaders() headers: authStore.getAuthHeaders()
}) })
@@ -251,6 +259,7 @@ async function createTimefix() {
try { try {
loading.value = true loading.value = true
const correctedDate = form.value.newDate
const response = await fetch(`${API_URL}/timefix`, { const response = await fetch(`${API_URL}/timefix`, {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -271,6 +280,7 @@ async function createTimefix() {
} }
resetForm() resetForm()
timefixDate.value = correctedDate
await Promise.all([ await Promise.all([
loadTimefixes(), loadTimefixes(),
loadWorklogEntries() // Lade auch die Dropdown-Liste neu loadWorklogEntries() // Lade auch die Dropdown-Liste neu
@@ -339,9 +349,9 @@ function formatAction(action) {
// Formular zurücksetzen // Formular zurücksetzen
function resetForm() { function resetForm() {
form.value = { form.value = {
originalDate: new Date().toISOString().split('T')[0], originalDate: today,
worklogId: '', worklogId: '',
newDate: new Date().toISOString().split('T')[0], newDate: today,
newTime: '', newTime: '',
newAction: '' newAction: ''
} }
@@ -464,6 +474,24 @@ input:disabled, select:disabled {
} }
/* Tabelle */ /* Tabelle */
.timefix-list-header {
display: flex;
justify-content: space-between;
align-items: end;
gap: 16px;
margin-bottom: 20px;
}
.timefix-list-header h2 {
margin-bottom: 0;
}
.timefix-list-header label {
display: flex;
flex-direction: column;
margin-bottom: 0;
}
.timefix-table { .timefix-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@@ -515,4 +543,3 @@ input:disabled, select:disabled {
color: #7f8c8d; color: #7f8c8d;
} }
</style> </style>

6
package-lock.json generated
View File

@@ -18,7 +18,7 @@
}, },
"backend": { "backend": {
"name": "timeclock-backend", "name": "timeclock-backend",
"version": "3.0.0", "version": "3.0.1",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
@@ -29,10 +29,10 @@
"express": "^4.18.2", "express": "^4.18.2",
"express-session": "^1.18.0", "express-session": "^1.18.0",
"helmet": "^7.1.0", "helmet": "^7.1.0",
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mysql2": "^3.6.5", "mysql2": "^3.6.5",
"nodemailer": "^7.0.11", "nodemailer": "^7.0.9",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",
"sequelize": "^6.37.7" "sequelize": "^6.37.7"