340 lines
6.9 KiB
Vue
340 lines
6.9 KiB
Vue
<template>
|
|
<div class="calendar-page">
|
|
<div class="card">
|
|
<!-- Navigation -->
|
|
<div class="calendar-header">
|
|
<button @click="previousMonth" class="nav-button">«</button>
|
|
|
|
<div class="month-year-selector">
|
|
<select v-model.number="selectedMonth" @change="loadCalendar" class="month-select">
|
|
<option v-for="(name, index) in monthNames" :key="index" :value="index + 1">
|
|
{{ name }}
|
|
</option>
|
|
</select>
|
|
<input
|
|
type="number"
|
|
v-model.number="selectedYear"
|
|
@change="loadCalendar"
|
|
min="2000"
|
|
max="2100"
|
|
class="year-input"
|
|
>
|
|
</div>
|
|
|
|
<button @click="nextMonth" class="nav-button">»</button>
|
|
</div>
|
|
|
|
<!-- Kalender -->
|
|
<table class="calendar-table" v-if="calendar">
|
|
<thead>
|
|
<tr>
|
|
<th v-for="day in weekDays" :key="day" :title="day">
|
|
{{ day.substring(0, 3) }}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(week, weekIndex) in calendar.weeks" :key="weekIndex">
|
|
<td v-for="(day, dayIndex) in week" :key="dayIndex">
|
|
<div
|
|
class="calendar-day"
|
|
:class="{
|
|
'other-month': !day.isCurrentMonth,
|
|
'same-month': day.isCurrentMonth,
|
|
'today': day.isToday,
|
|
'has-work': day.workedHours,
|
|
'has-vacation': day.vacation,
|
|
'has-holiday': day.holiday,
|
|
'has-sick': day.sick
|
|
}"
|
|
>
|
|
<div class="day-number">{{ day.day }}</div>
|
|
<div v-if="day.workedHours" class="day-info work-info">
|
|
Gearbeitet: {{ formatHours(day.workedHours) }} h
|
|
</div>
|
|
<div v-if="day.vacation" class="day-info vacation-info">
|
|
{{ day.vacation === 'half' ? 'Halber ' : '' }}Urlaub
|
|
</div>
|
|
<div v-if="day.sick" class="day-info sick-info">
|
|
Krank
|
|
</div>
|
|
<div v-if="day.holiday" class="day-info holiday-info">
|
|
{{ day.holiday }}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div v-if="loading" class="loading">Lade Kalender...</div>
|
|
<div v-if="error" class="error">{{ error }}</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from 'vue'
|
|
import { useAuthStore } from '../stores/authStore'
|
|
import { API_BASE_URL } from '@/config/api'
|
|
|
|
const API_URL = API_BASE_URL
|
|
const authStore = useAuthStore()
|
|
const now = new Date()
|
|
const selectedMonth = ref(now.getMonth() + 1)
|
|
const selectedYear = ref(now.getFullYear())
|
|
const calendar = ref(null)
|
|
const loading = ref(false)
|
|
const error = ref(null)
|
|
|
|
const monthNames = [
|
|
'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
|
|
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'
|
|
]
|
|
|
|
const weekDays = [
|
|
'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'
|
|
]
|
|
|
|
// Lade Kalenderdaten
|
|
async function loadCalendar() {
|
|
try {
|
|
loading.value = true
|
|
error.value = null
|
|
|
|
const response = await fetch(
|
|
`${API_URL}/calendar?year=${selectedYear.value}&month=${selectedMonth.value}`,
|
|
{
|
|
headers: {
|
|
'Authorization': `Bearer ${authStore.token}`
|
|
}
|
|
}
|
|
)
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Fehler beim Laden des Kalenders')
|
|
}
|
|
|
|
calendar.value = await response.json()
|
|
} catch (err) {
|
|
console.error('Fehler beim Laden des Kalenders:', err)
|
|
error.value = err.message
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// Navigation
|
|
function previousMonth() {
|
|
if (selectedMonth.value === 1) {
|
|
selectedMonth.value = 12
|
|
selectedYear.value--
|
|
} else {
|
|
selectedMonth.value--
|
|
}
|
|
loadCalendar()
|
|
}
|
|
|
|
function nextMonth() {
|
|
if (selectedMonth.value === 12) {
|
|
selectedMonth.value = 1
|
|
selectedYear.value++
|
|
} else {
|
|
selectedMonth.value++
|
|
}
|
|
loadCalendar()
|
|
}
|
|
|
|
// Formatiere Stunden
|
|
function formatHours(hours) {
|
|
if (!hours || typeof hours !== 'number') {
|
|
return '0:00'
|
|
}
|
|
|
|
const h = Math.floor(hours)
|
|
const m = Math.round((hours % 1) * 60)
|
|
return `${h}:${String(m).padStart(2, '0')}`
|
|
}
|
|
|
|
// Initiales Laden
|
|
onMounted(() => {
|
|
loadCalendar()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.calendar-page {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
|
|
.card {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.calendar-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.nav-button {
|
|
background: #4CAF50;
|
|
color: white;
|
|
border: none;
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
font-size: 20px;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.nav-button:hover {
|
|
background: #45a049;
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.month-year-selector {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
}
|
|
|
|
.month-select,
|
|
.year-input {
|
|
padding: 8px 12px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 4px;
|
|
font-size: 15px;
|
|
font-family: inherit;
|
|
}
|
|
|
|
.month-select {
|
|
min-width: 120px;
|
|
}
|
|
|
|
.year-input {
|
|
width: 80px;
|
|
text-align: center;
|
|
}
|
|
|
|
.month-select:focus,
|
|
.year-input:focus {
|
|
outline: none;
|
|
border-color: #4CAF50;
|
|
}
|
|
|
|
.calendar-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.calendar-table th {
|
|
padding: 10px;
|
|
text-align: center;
|
|
font-weight: 600;
|
|
color: #666;
|
|
font-size: 13px;
|
|
border-bottom: 2px solid #eee;
|
|
}
|
|
|
|
.calendar-table td {
|
|
padding: 2px;
|
|
vertical-align: top;
|
|
height: 100px;
|
|
}
|
|
|
|
.calendar-day {
|
|
height: 100%;
|
|
min-height: 90px;
|
|
padding: 6px;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
background: white;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.calendar-day:hover {
|
|
border-color: #4CAF50;
|
|
box-shadow: 0 2px 4px rgba(76, 175, 80, 0.2);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.calendar-day.other-month {
|
|
background: #f9f9f9;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.calendar-day.today {
|
|
background: #e8f5e9;
|
|
border-color: #4CAF50;
|
|
border-width: 2px;
|
|
}
|
|
|
|
.day-number {
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
margin-bottom: 4px;
|
|
color: #333;
|
|
}
|
|
|
|
.calendar-day.other-month .day-number {
|
|
color: #999;
|
|
}
|
|
|
|
.day-info {
|
|
font-size: 11px;
|
|
padding: 2px 4px;
|
|
margin: 2px 0;
|
|
border-radius: 3px;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.work-info {
|
|
background: #e3f2fd;
|
|
color: #1976d2;
|
|
}
|
|
|
|
.vacation-info {
|
|
background: #fff9c4;
|
|
color: #f57f17;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.sick-info {
|
|
background: #ffebee;
|
|
color: #c62828;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.holiday-info {
|
|
background: #f3e5f5;
|
|
color: #7b1fa2;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.loading {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: #999;
|
|
font-style: italic;
|
|
}
|
|
|
|
.error {
|
|
text-align: center;
|
|
padding: 20px;
|
|
color: #f44336;
|
|
background: #ffebee;
|
|
border-radius: 4px;
|
|
margin-top: 20px;
|
|
}
|
|
</style>
|
|
|