Initial commit: TimeClock v3 - Node.js/Vue.js Zeiterfassung

Features:
- Backend: Node.js/Express mit MySQL/MariaDB
- Frontend: Vue.js 3 mit Composition API
- UTC-Zeithandling für korrekte Zeiterfassung
- Timewish-basierte Überstundenberechnung
- Wochenübersicht mit Urlaubs-/Krankheits-/Feiertagshandling
- Bereinigtes Arbeitsende (Generell/Woche)
- Überstunden-Offset für historische Daten
- Fixed Layout mit scrollbarem Content
- Kompakte UI mit grünem Theme
This commit is contained in:
Torsten Schulz (local)
2025-10-17 14:11:28 +02:00
commit e95bb4cb76
86 changed files with 19530 additions and 0 deletions

View File

@@ -0,0 +1,338 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
const API_URL = 'http://localhost:3010/api'
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const token = ref(null)
const isAuthenticated = ref(false)
const isLoading = ref(false)
/**
* Token in localStorage speichern
*/
const saveToken = (newToken) => {
token.value = newToken
localStorage.setItem('timeclock_token', newToken)
}
/**
* Token aus localStorage laden
*/
const loadToken = () => {
const savedToken = localStorage.getItem('timeclock_token')
if (savedToken) {
token.value = savedToken
return savedToken
}
return null
}
/**
* Token und Benutzerdaten löschen
*/
const clearAuth = () => {
token.value = null
user.value = null
isAuthenticated.value = false
localStorage.removeItem('timeclock_token')
}
/**
* Registrierung
*/
const register = async (userData) => {
try {
isLoading.value = true
const response = await fetch(`${API_URL}/auth/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Registrierung fehlgeschlagen')
}
return data
} catch (error) {
console.error('Registrierungsfehler:', error)
throw error
} finally {
isLoading.value = false
}
}
/**
* Login
*/
const login = async (credentials) => {
try {
isLoading.value = true
const response = await fetch(`${API_URL}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(credentials)
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Login fehlgeschlagen')
}
// Token speichern
saveToken(data.token)
// Benutzerdaten setzen
user.value = data.user
isAuthenticated.value = true
return data
} catch (error) {
console.error('Login-Fehler:', error)
clearAuth()
throw error
} finally {
isLoading.value = false
}
}
/**
* Logout
*/
const logout = async () => {
try {
if (token.value) {
await fetch(`${API_URL}/auth/logout`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token.value}`
}
})
}
} catch (error) {
console.error('Logout-Fehler:', error)
} finally {
clearAuth()
}
}
/**
* Aktuellen Benutzer laden (für Session-Wiederherstellung)
*/
const fetchCurrentUser = async () => {
try {
const savedToken = loadToken()
if (!savedToken) {
return false
}
isLoading.value = true
const response = await fetch(`${API_URL}/auth/me`, {
headers: {
'Authorization': `Bearer ${savedToken}`
}
})
const data = await response.json()
if (!response.ok) {
// Token ungültig - ausloggen
clearAuth()
return false
}
// Session wiederherstellen
user.value = data.user
isAuthenticated.value = true
return true
} catch (error) {
console.error('Fehler beim Laden des Benutzers:', error)
clearAuth()
return false
} finally {
isLoading.value = false
}
}
/**
* Token validieren
*/
const validateToken = async () => {
try {
const savedToken = loadToken()
if (!savedToken) {
return false
}
const response = await fetch(`${API_URL}/auth/validate`, {
headers: {
'Authorization': `Bearer ${savedToken}`
}
})
if (!response.ok) {
clearAuth()
return false
}
return true
} catch (error) {
console.error('Token-Validierungsfehler:', error)
clearAuth()
return false
}
}
/**
* Passwort-Reset anfordern
*/
const requestPasswordReset = async (email) => {
try {
isLoading.value = true
const response = await fetch(`${API_URL}/auth/request-reset`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Anfrage fehlgeschlagen')
}
return data
} catch (error) {
console.error('Passwort-Reset-Anfrage-Fehler:', error)
throw error
} finally {
isLoading.value = false
}
}
/**
* Passwort zurücksetzen
*/
const resetPassword = async (resetToken, password) => {
try {
isLoading.value = true
const response = await fetch(`${API_URL}/auth/reset-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
token: resetToken,
password
})
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Passwort-Reset fehlgeschlagen')
}
return data
} catch (error) {
console.error('Passwort-Reset-Fehler:', error)
throw error
} finally {
isLoading.value = false
}
}
/**
* Passwort ändern (eingeloggter Benutzer)
*/
const changePassword = async (oldPassword, newPassword) => {
try {
isLoading.value = true
const response = await fetch(`${API_URL}/auth/change-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token.value}`
},
body: JSON.stringify({
oldPassword,
newPassword
})
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Passwort-Änderung fehlgeschlagen')
}
return data
} catch (error) {
console.error('Passwort-Änderungs-Fehler:', error)
throw error
} finally {
isLoading.value = false
}
}
/**
* HTTP-Header mit Authorization zurückgeben
*/
const getAuthHeaders = () => {
const savedToken = token.value || loadToken()
if (savedToken) {
return {
'Authorization': `Bearer ${savedToken}`,
'Content-Type': 'application/json'
}
}
return {
'Content-Type': 'application/json'
}
}
return {
// State
user,
token,
isAuthenticated,
isLoading,
// Actions
register,
login,
logout,
fetchCurrentUser,
validateToken,
requestPasswordReset,
resetPassword,
changePassword,
getAuthHeaders,
loadToken,
saveToken,
clearAuth
}
})