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
All checks were successful
Deploy production / deploy (push) Successful in 48s
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
54
deploy.sh
54
deploy.sh
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
6
package-lock.json
generated
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user