Files
stechuhr3/frontend/src/views/Holidays.vue

588 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="holidays-page">
<div class="card">
<!-- Formular zum Erstellen eines Feiertags -->
<form @submit.prevent="createHoliday" class="holiday-form">
<h3>Feiertag hinzufügen</h3>
<div class="form-row">
<div class="form-group">
<label for="date">Datum</label>
<input
type="date"
id="date"
v-model="form.date"
required
>
</div>
<div class="form-group">
<label for="hours">Freie Stunden</label>
<input
type="number"
id="hours"
v-model.number="form.hours"
min="0"
max="24"
required
>
</div>
</div>
<div class="form-row">
<div class="form-group full-width">
<label for="description">Beschreibung</label>
<input
type="text"
id="description"
v-model="form.description"
placeholder="z.B. Tag der Deutschen Einheit"
required
>
</div>
</div>
<div class="form-row">
<div class="form-group full-width">
<label>
<input
type="checkbox"
v-model="form.isFederal"
@change="onFederalChange"
>
Bundesfeiertag (gilt für alle Bundesländer)
</label>
</div>
</div>
<div class="form-row" v-if="!form.isFederal">
<div class="form-group full-width">
<label for="states">Bundesländer</label>
<div class="states-grid">
<label
v-for="state in availableStates"
:key="state.id"
class="state-checkbox"
>
<input
type="checkbox"
:value="state.id"
v-model="form.selectedStates"
>
{{ state.name }}
</label>
</div>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" :disabled="loading">
{{ loading ? 'Wird gespeichert...' : 'Feiertag hinzufügen' }}
</button>
</div>
</form>
<hr>
<!-- Zwei-Spalten-Layout für Tabellen -->
<div class="tables-container">
<!-- Zukünftige Feiertage -->
<div class="table-column">
<h3>Zukünftige Feiertage</h3>
<table class="holidays-table">
<thead>
<tr>
<th>Datum</th>
<th>Freie Stunden</th>
<th>Beschreibung</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-if="futureHolidays.length === 0">
<td colspan="4" class="no-data">Keine zukünftigen Feiertage</td>
</tr>
<tr v-for="holiday in futureHolidays" :key="holiday.id">
<td>{{ formatDate(holiday.date) }}</td>
<td>{{ holiday.hours }}</td>
<td>
<div>{{ holiday.description }}</div>
<div v-if="holiday.states && holiday.states.length > 0" class="state-tags">
<span v-for="state in holiday.states" :key="state" class="state-tag">
{{ state }}
</span>
</div>
<div v-else class="federal-tag">Bundesfeiertag</div>
</td>
<td>
<button
@click="deleteHoliday(holiday.id)"
class="btn btn-delete-small"
:disabled="loading"
>
×
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Vergangene Feiertage -->
<div class="table-column">
<h3>Vergangene Feiertage</h3>
<table class="holidays-table">
<thead>
<tr>
<th>Datum</th>
<th>Freie Stunden</th>
<th>Beschreibung</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-if="pastHolidays.length === 0">
<td colspan="4" class="no-data">Keine vergangenen Feiertage</td>
</tr>
<tr v-for="holiday in pastHolidays" :key="holiday.id">
<td>{{ formatDate(holiday.date) }}</td>
<td>{{ holiday.hours }}</td>
<td>
<div>{{ holiday.description }}</div>
<div v-if="holiday.states && holiday.states.length > 0" class="state-tags">
<span v-for="state in holiday.states" :key="state" class="state-tag">
{{ state }}
</span>
</div>
<div v-else class="federal-tag">Bundesfeiertag</div>
</td>
<td>
<button
@click="deleteHoliday(holiday.id)"
class="btn btn-delete-small"
:disabled="loading"
>
×
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Modal-Komponente -->
<Modal
v-if="showModal"
:show="showModal"
:title="modalConfig.title"
:message="modalConfig.message"
:type="modalConfig.type"
:confirmText="modalConfig.confirmText"
:cancelText="modalConfig.cancelText"
@confirm="onConfirm"
@cancel="onCancel"
/>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useAuthStore } from '../stores/authStore'
import { useModal } from '../composables/useModal'
import Modal from '../components/Modal.vue'
import { API_BASE_URL } from '@/config/api'
const API_URL = API_BASE_URL
const authStore = useAuthStore()
const futureHolidays = ref([])
const pastHolidays = ref([])
const availableStates = ref([])
const loading = ref(false)
const { showModal, modalConfig, alert, confirm, onConfirm, onCancel } = useModal()
const form = ref({
date: new Date().toISOString().split('T')[0],
hours: 8,
description: '',
isFederal: true,
selectedStates: []
})
// Lade alle Bundesländer
async function loadStates() {
try {
const response = await fetch('${API_URL}/holidays/states', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
})
if (!response.ok) {
throw new Error('Fehler beim Laden der Bundesländer')
}
availableStates.value = await response.json()
} catch (error) {
console.error('Fehler beim Laden der Bundesländer:', error)
await alert(`Fehler: ${error.message}`, 'Fehler')
}
}
// Lade alle Feiertage
async function loadHolidays() {
try {
loading.value = true
const response = await fetch('${API_URL}/holidays', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
})
if (!response.ok) {
throw new Error('Fehler beim Laden der Feiertage')
}
const data = await response.json()
futureHolidays.value = data.future || []
pastHolidays.value = data.past || []
} catch (error) {
console.error('Fehler beim Laden der Feiertage:', error)
await alert(`Fehler: ${error.message}`, 'Fehler')
} finally {
loading.value = false
}
}
// Wenn "Bundesfeiertag" geändert wird
function onFederalChange() {
if (form.value.isFederal) {
form.value.selectedStates = []
}
}
// Erstelle neuen Feiertag
async function createHoliday() {
if (!form.value.date || !form.value.description) {
await alert('Bitte füllen Sie alle Felder aus', 'Fehlende Angaben')
return
}
// Validierung: Wenn nicht bundesweit, müssen Bundesländer ausgewählt sein
if (!form.value.isFederal && form.value.selectedStates.length === 0) {
await alert('Bitte wählen Sie mindestens ein Bundesland aus oder markieren Sie als Bundesfeiertag', 'Fehlende Angaben')
return
}
try {
loading.value = true
const response = await fetch('${API_URL}/holidays', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authStore.token}`
},
body: JSON.stringify({
date: form.value.date,
hours: form.value.hours,
description: form.value.description,
stateIds: form.value.isFederal ? [] : form.value.selectedStates
})
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Fehler beim Erstellen des Feiertags')
}
// Formular zurücksetzen
resetForm()
// Liste neu laden
await loadHolidays()
} catch (error) {
console.error('Fehler beim Erstellen des Feiertags:', error)
await alert(`Fehler: ${error.message}`, 'Fehler')
} finally {
loading.value = false
}
}
// Lösche Feiertag
async function deleteHoliday(id) {
const confirmed = await confirm('Möchten Sie diesen Feiertag wirklich löschen?', 'Feiertag löschen')
if (!confirmed) {
return
}
try {
loading.value = true
const response = await fetch(`${API_URL}/holidays/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authStore.token}`
}
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Fehler beim Löschen des Feiertags')
}
await loadHolidays()
} catch (error) {
console.error('Fehler beim Löschen des Feiertags:', error)
await alert(`Fehler: ${error.message}`, 'Fehler')
} finally {
loading.value = false
}
}
// Formatiere Datum für Anzeige (DD.MM.YYYY)
function formatDate(dateStr) {
if (!dateStr) return ''
const date = new Date(dateStr + 'T00:00:00')
return date.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
})
}
// Formular zurücksetzen
function resetForm() {
form.value = {
date: new Date().toISOString().split('T')[0],
hours: 8,
description: '',
isFederal: true,
selectedStates: []
}
}
// Initiales Laden
onMounted(async () => {
await Promise.all([
loadStates(),
loadHolidays()
])
})
</script>
<style scoped>
.holidays-page {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.holiday-form h3 {
margin: 0 0 16px 0;
font-size: 18px;
color: #333;
}
.form-row {
display: flex;
gap: 16px;
margin-bottom: 12px;
}
.form-group {
flex: 1;
display: flex;
flex-direction: column;
}
.form-group.full-width {
flex: 1;
}
.form-group label {
font-weight: 500;
margin-bottom: 6px;
font-size: 13px;
color: #333;
}
.form-group input {
padding: 8px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 13px;
font-family: inherit;
}
.form-group input:focus {
outline: none;
border-color: #4CAF50;
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.1);
}
.form-actions {
margin-top: 8px;
}
.states-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 8px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
}
.state-checkbox {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
cursor: pointer;
border-radius: 3px;
transition: background 0.2s;
}
.state-checkbox:hover {
background: #f5f5f5;
}
.state-checkbox input[type="checkbox"] {
cursor: pointer;
}
.state-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 4px;
}
.state-tag {
display: inline-block;
background: #e3f2fd;
color: #1976d2;
padding: 2px 8px;
border-radius: 3px;
font-size: 11px;
font-weight: 500;
}
.federal-tag {
display: inline-block;
background: #f3e5f5;
color: #7b1fa2;
padding: 2px 8px;
border-radius: 3px;
font-size: 11px;
font-weight: 600;
margin-top: 4px;
}
hr {
border: none;
border-top: 1px solid #eee;
margin: 20px 0;
}
.tables-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
}
.table-column h3 {
margin: 0 0 12px 0;
font-size: 16px;
color: #333;
}
.holidays-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.holidays-table th {
text-align: left;
padding: 10px 8px;
background: #f5f5f5;
border-bottom: 2px solid #ddd;
font-weight: 600;
font-size: 12px;
color: #666;
}
.holidays-table th:last-child {
width: 40px;
}
.holidays-table td {
padding: 10px 8px;
border-bottom: 1px solid #eee;
}
.holidays-table tbody tr:hover {
background: #f9f9f9;
}
.no-data {
text-align: center;
color: #999;
font-style: italic;
padding: 20px !important;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
font-family: inherit;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background: linear-gradient(135deg, #4CAF50, #45a049);
color: white;
box-shadow: 0 2px 4px rgba(76, 175, 80, 0.3);
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(76, 175, 80, 0.4);
}
.btn-delete-small {
background: #f44336;
color: white;
padding: 4px 8px;
font-size: 16px;
line-height: 1;
border-radius: 3px;
}
.btn-delete-small:hover:not(:disabled) {
background: #da190b;
}
</style>