Update calendar localization and enhance CalendarView: Add new entries, edit options, selection info, and form fields in both English and German localization files. Improve CalendarView component with new event dialog and selection handling for a better user experience.

This commit is contained in:
Torsten Schulz (local)
2026-01-30 14:17:04 +01:00
parent 25af538c88
commit 8355f985cd
3 changed files with 778 additions and 34 deletions

View File

@@ -3,6 +3,11 @@
"calendar": {
"title": "Kalender",
"today": "Heute",
"newEntry": "Neuer Eintrag",
"editEntry": "Eintrag bearbeiten",
"selectedDays": "{count} Tage ausgewählt",
"createEventForSelection": "Termin erstellen",
"clearSelection": "Auswahl aufheben",
"views": {
"month": "Monat",
"week": "Woche",
@@ -40,6 +45,31 @@
"oct": "Oktober",
"nov": "November",
"dec": "Dezember"
},
"categories": {
"personal": "Persönlich",
"work": "Arbeit",
"family": "Familie",
"health": "Gesundheit",
"birthday": "Geburtstag",
"holiday": "Urlaub",
"reminder": "Erinnerung",
"other": "Sonstiges"
},
"form": {
"title": "Titel",
"titlePlaceholder": "Titel eingeben...",
"category": "Kategorie",
"startDate": "Startdatum",
"startTime": "Startzeit",
"endDate": "Enddatum",
"endTime": "Endzeit",
"allDay": "Ganztägig",
"description": "Beschreibung",
"descriptionPlaceholder": "Optionale Beschreibung...",
"save": "Speichern",
"cancel": "Abbrechen",
"delete": "Löschen"
}
}
}

View File

@@ -3,6 +3,11 @@
"calendar": {
"title": "Calendar",
"today": "Today",
"newEntry": "New Entry",
"editEntry": "Edit Entry",
"selectedDays": "{count} days selected",
"createEventForSelection": "Create Event",
"clearSelection": "Clear Selection",
"views": {
"month": "Month",
"week": "Week",
@@ -40,6 +45,31 @@
"oct": "October",
"nov": "November",
"dec": "December"
},
"categories": {
"personal": "Personal",
"work": "Work",
"family": "Family",
"health": "Health",
"birthday": "Birthday",
"holiday": "Holiday",
"reminder": "Reminder",
"other": "Other"
},
"form": {
"title": "Title",
"titlePlaceholder": "Enter title...",
"category": "Category",
"startDate": "Start Date",
"startTime": "Start Time",
"endDate": "End Date",
"endTime": "End Time",
"allDay": "All Day",
"description": "Description",
"descriptionPlaceholder": "Optional description...",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete"
}
}
}

View File

@@ -5,6 +5,9 @@
<!-- Toolbar -->
<div class="calendar-toolbar">
<div class="nav-buttons">
<button @click="openNewEventDialog()" class="btn-new-event">
+ {{ $t('personal.calendar.newEntry') }}
</button>
<button @click="goToToday" class="btn-today">{{ $t('personal.calendar.today') }}</button>
<button @click="navigatePrev" class="btn-nav">&lt;</button>
<button @click="navigateNext" class="btn-nav">&gt;</button>
@@ -23,6 +26,17 @@
</div>
</div>
<!-- Selection info -->
<div v-if="selectedDates.length > 1" class="selection-info">
{{ $t('personal.calendar.selectedDays', { count: selectedDates.length }) }}
<button @click="createEventFromSelection" class="btn-create-from-selection">
{{ $t('personal.calendar.createEventForSelection') }}
</button>
<button @click="clearSelection" class="btn-clear-selection">
{{ $t('personal.calendar.clearSelection') }}
</button>
</div>
<!-- Month View -->
<div v-if="currentView === 'month'" class="calendar-grid month-view">
<div class="weekday-headers">
@@ -37,19 +51,26 @@
:class="['day-cell', {
'other-month': !day.currentMonth,
'today': day.isToday,
'weekend': day.isWeekend
'weekend': day.isWeekend,
'selected': isDateSelected(day.date)
}]"
@click="selectDate(day.date)"
@click="handleDayClick(day.date, $event)"
@mousedown="startDragSelection(day.date)"
@mouseenter="continueDragSelection(day.date)"
@mouseup="endDragSelection"
>
<span class="day-number">{{ day.dayNumber }}</span>
<div class="day-events">
<div
v-for="event in getEventsForDate(day.date)"
:key="event.id"
class="event-dot"
:style="{ backgroundColor: event.color }"
class="event-item"
:style="{ backgroundColor: getCategoryColor(event.categoryId) }"
:title="event.title"
></div>
@click.stop="editEvent(event)"
>
{{ event.title }}
</div>
</div>
</div>
</div>
@@ -78,8 +99,18 @@
v-for="hour in hours"
:key="hour"
:class="['time-slot', { 'current-hour': isCurrentHour(day.date, hour) }]"
@click="createEventAt(day.date, hour)"
></div>
@click="openNewEventDialog(day.date, hour)"
>
<div
v-for="event in getEventsForDateTime(day.date, hour)"
:key="event.id"
class="time-event"
:style="{ backgroundColor: getCategoryColor(event.categoryId) }"
@click.stop="editEvent(event)"
>
{{ event.title }}
</div>
</div>
</div>
</div>
</div>
@@ -108,8 +139,18 @@
v-for="hour in workHours"
:key="hour"
:class="['time-slot', { 'current-hour': isCurrentHour(day.date, hour) }]"
@click="createEventAt(day.date, hour)"
></div>
@click="openNewEventDialog(day.date, hour)"
>
<div
v-for="event in getEventsForDateTime(day.date, hour)"
:key="event.id"
class="time-event"
:style="{ backgroundColor: getCategoryColor(event.categoryId) }"
@click.stop="editEvent(event)"
>
{{ event.title }}
</div>
</div>
</div>
</div>
</div>
@@ -119,7 +160,7 @@
<div v-if="currentView === 'day'" class="calendar-grid day-view">
<div class="day-header">
<div :class="['day-title', { today: isDayToday }]">
{{ $t(`personal.calendar.weekdays.${currentDayData.weekday}`) }},
{{ $t(`personal.calendar.weekdaysFull.${currentDayData.weekday}`) }},
{{ currentDayData.dayNumber }}. {{ $t(`personal.calendar.months.${currentDayData.month}`) }} {{ currentDayData.year }}
</div>
</div>
@@ -134,13 +175,114 @@
<div
v-for="hour in hours"
:key="hour"
:class="['time-slot', { 'current-hour': isCurrentHour(currentDate, hour) }]"
@click="createEventAt(currentDate, hour)"
></div>
:class="['time-slot', { 'current-hour': isCurrentHour(currentDateStr, hour) }]"
@click="openNewEventDialog(currentDateStr, hour)"
>
<div
v-for="event in getEventsForDateTime(currentDateStr, hour)"
:key="event.id"
class="time-event full-width"
:style="{ backgroundColor: getCategoryColor(event.categoryId) }"
@click.stop="editEvent(event)"
>
<span class="event-time">{{ formatEventTime(event) }}</span>
{{ event.title }}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Event Dialog -->
<div v-if="showEventDialog" class="dialog-overlay" @click.self="closeEventDialog">
<div class="event-dialog">
<div class="dialog-header">
<h3>{{ editingEvent ? $t('personal.calendar.editEntry') : $t('personal.calendar.newEntry') }}</h3>
<button @click="closeEventDialog" class="btn-close">&times;</button>
</div>
<div class="dialog-body">
<div class="form-group">
<label>{{ $t('personal.calendar.form.title') }}</label>
<input
v-model="eventForm.title"
type="text"
:placeholder="$t('personal.calendar.form.titlePlaceholder')"
ref="eventTitleInput"
/>
</div>
<div class="form-group">
<label>{{ $t('personal.calendar.form.category') }}</label>
<div class="category-selector">
<button
v-for="cat in categories"
:key="cat.id"
:class="['category-btn', { selected: eventForm.categoryId === cat.id }]"
:style="{
backgroundColor: eventForm.categoryId === cat.id ? cat.color : 'transparent',
borderColor: cat.color,
color: eventForm.categoryId === cat.id ? getContrastColor(cat.color) : cat.color
}"
@click="eventForm.categoryId = cat.id"
>
{{ $t(`personal.calendar.categories.${cat.id}`) }}
</button>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>{{ $t('personal.calendar.form.startDate') }}</label>
<input v-model="eventForm.startDate" type="date" />
</div>
<div class="form-group">
<label>{{ $t('personal.calendar.form.startTime') }}</label>
<input v-model="eventForm.startTime" type="time" />
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>{{ $t('personal.calendar.form.endDate') }}</label>
<input v-model="eventForm.endDate" type="date" />
</div>
<div class="form-group">
<label>{{ $t('personal.calendar.form.endTime') }}</label>
<input v-model="eventForm.endTime" type="time" />
</div>
</div>
<div class="form-group">
<label class="checkbox-label">
<input v-model="eventForm.allDay" type="checkbox" />
{{ $t('personal.calendar.form.allDay') }}
</label>
</div>
<div class="form-group">
<label>{{ $t('personal.calendar.form.description') }}</label>
<textarea
v-model="eventForm.description"
rows="3"
:placeholder="$t('personal.calendar.form.descriptionPlaceholder')"
></textarea>
</div>
</div>
<div class="dialog-footer">
<button v-if="editingEvent" @click="deleteEvent" class="btn-delete">
{{ $t('personal.calendar.form.delete') }}
</button>
<div class="spacer"></div>
<button @click="closeEventDialog" class="btn-cancel">
{{ $t('personal.calendar.form.cancel') }}
</button>
<button @click="saveEvent" class="btn-save" :disabled="!eventForm.title">
{{ $t('personal.calendar.form.save') }}
</button>
</div>
</div>
</div>
</div>
</template>
@@ -159,8 +301,42 @@ export default {
],
weekDays: ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'],
hours: Array.from({ length: 24 }, (_, i) => i),
workHours: Array.from({ length: 12 }, (_, i) => i + 7), // 7:00 - 18:00
events: [] // Placeholder for events
workHours: Array.from({ length: 12 }, (_, i) => i + 7),
// Categories with colors
categories: [
{ id: 'personal', color: '#4CAF50' }, // Green
{ id: 'work', color: '#2196F3' }, // Blue
{ id: 'family', color: '#9C27B0' }, // Purple
{ id: 'health', color: '#F44336' }, // Red
{ id: 'birthday', color: '#FF9800' }, // Orange
{ id: 'holiday', color: '#00BCD4' }, // Cyan
{ id: 'reminder', color: '#795548' }, // Brown
{ id: 'other', color: '#607D8B' } // Gray-Blue
],
// Events storage
events: [],
nextEventId: 1,
// Selection
selectedDates: [],
isDragging: false,
dragStartDate: null,
// Dialog
showEventDialog: false,
editingEvent: null,
eventForm: {
title: '',
categoryId: 'personal',
startDate: '',
startTime: '09:00',
endDate: '',
endTime: '10:00',
allDay: false,
description: ''
}
};
},
computed: {
@@ -171,13 +347,14 @@ export default {
}
return this.currentDate.toLocaleDateString(this.$i18n.locale, options);
},
currentDateStr() {
return this.currentDate.toISOString().split('T')[0];
},
monthDays() {
const year = this.currentDate.getFullYear();
const month = this.currentDate.getMonth();
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
// Find Monday of the first week
let startDate = new Date(firstDay);
const dayOfWeek = firstDay.getDay();
const diff = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
@@ -187,7 +364,6 @@ export default {
const today = new Date();
today.setHours(0, 0, 0, 0);
// Generate 6 weeks (42 days)
for (let i = 0; i < 42; i++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + i);
@@ -209,7 +385,6 @@ export default {
const today = new Date();
today.setHours(0, 0, 0, 0);
// Find Monday of current week
const monday = this.getMonday(this.currentDate);
for (let i = 0; i < 7; i++) {
@@ -229,13 +404,11 @@ export default {
return days;
},
workWeekDaysData() {
// Only Monday to Friday
return this.weekDaysData.slice(0, 5);
},
currentDayData() {
const date = this.currentDate;
const dayIndex = date.getDay();
// Convert Sunday (0) to index 6, Monday (1) to 0, etc.
const weekdayIndex = dayIndex === 0 ? 6 : dayIndex - 1;
return {
@@ -251,6 +424,13 @@ export default {
return this.currentDate.toDateString() === today.toDateString();
}
},
mounted() {
this.loadEvents();
document.addEventListener('mouseup', this.endDragSelection);
},
beforeUnmount() {
document.removeEventListener('mouseup', this.endDragSelection);
},
methods: {
getMonday(date) {
const d = new Date(date);
@@ -305,16 +485,210 @@ export default {
const date = new Date(dateStr);
return date.toDateString() === now.toDateString() && now.getHours() === hour;
},
selectDate(dateStr) {
this.currentDate = new Date(dateStr);
this.currentView = 'day';
// Selection methods
isDateSelected(dateStr) {
return this.selectedDates.includes(dateStr);
},
createEventAt(dateStr, hour) {
// Placeholder for event creation
console.log(`Create event at ${dateStr} ${hour}:00`);
handleDayClick(dateStr, event) {
if (event.shiftKey && this.selectedDates.length > 0) {
// Shift-click: select range
const lastSelected = this.selectedDates[this.selectedDates.length - 1];
this.selectDateRange(lastSelected, dateStr);
} else if (event.ctrlKey || event.metaKey) {
// Ctrl/Cmd-click: toggle selection
const index = this.selectedDates.indexOf(dateStr);
if (index > -1) {
this.selectedDates.splice(index, 1);
} else {
this.selectedDates.push(dateStr);
}
} else if (!this.isDragging) {
// Normal click: open event dialog for this day
this.openNewEventDialog(dateStr);
}
},
startDragSelection(dateStr) {
this.isDragging = true;
this.dragStartDate = dateStr;
this.selectedDates = [dateStr];
},
continueDragSelection(dateStr) {
if (this.isDragging && this.dragStartDate) {
this.selectDateRange(this.dragStartDate, dateStr);
}
},
endDragSelection() {
if (this.isDragging && this.selectedDates.length === 1) {
// Single day was clicked, not dragged
this.selectedDates = [];
}
this.isDragging = false;
this.dragStartDate = null;
},
selectDateRange(startStr, endStr) {
const start = new Date(startStr);
const end = new Date(endStr);
if (start > end) {
[start, end] = [end, start];
}
this.selectedDates = [];
const current = new Date(start);
while (current <= end) {
this.selectedDates.push(current.toISOString().split('T')[0]);
current.setDate(current.getDate() + 1);
}
},
clearSelection() {
this.selectedDates = [];
},
createEventFromSelection() {
if (this.selectedDates.length > 0) {
const sorted = [...this.selectedDates].sort();
this.openNewEventDialog(sorted[0], null, sorted[sorted.length - 1]);
}
},
// Event methods
getCategoryColor(categoryId) {
const cat = this.categories.find(c => c.id === categoryId);
return cat ? cat.color : '#607D8B';
},
getContrastColor(hexColor) {
const r = parseInt(hexColor.slice(1, 3), 16);
const g = parseInt(hexColor.slice(3, 5), 16);
const b = parseInt(hexColor.slice(5, 7), 16);
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
return luminance > 0.5 ? '#000000' : '#ffffff';
},
getEventsForDate(dateStr) {
return this.events.filter(e => e.date === dateStr);
return this.events.filter(event => {
const eventStart = event.startDate;
const eventEnd = event.endDate || event.startDate;
return dateStr >= eventStart && dateStr <= eventEnd;
});
},
getEventsForDateTime(dateStr, hour) {
return this.events.filter(event => {
if (event.allDay) return false;
const eventStart = event.startDate;
const eventEnd = event.endDate || event.startDate;
if (dateStr < eventStart || dateStr > eventEnd) return false;
const startHour = parseInt(event.startTime?.split(':')[0] || '0');
const endHour = parseInt(event.endTime?.split(':')[0] || '23');
if (dateStr === eventStart && dateStr === eventEnd) {
return hour >= startHour && hour < endHour;
} else if (dateStr === eventStart) {
return hour >= startHour;
} else if (dateStr === eventEnd) {
return hour < endHour;
}
return true;
});
},
formatEventTime(event) {
if (event.allDay) return '';
return event.startTime || '';
},
// Dialog methods
openNewEventDialog(dateStr = null, hour = null, endDateStr = null) {
this.editingEvent = null;
const today = new Date().toISOString().split('T')[0];
this.eventForm = {
title: '',
categoryId: 'personal',
startDate: dateStr || today,
startTime: hour !== null ? `${hour.toString().padStart(2, '0')}:00` : '09:00',
endDate: endDateStr || dateStr || today,
endTime: hour !== null ? `${(hour + 1).toString().padStart(2, '0')}:00` : '10:00',
allDay: endDateStr !== null && endDateStr !== dateStr,
description: ''
};
this.showEventDialog = true;
this.selectedDates = [];
this.$nextTick(() => {
this.$refs.eventTitleInput?.focus();
});
},
editEvent(event) {
this.editingEvent = event;
this.eventForm = {
title: event.title,
categoryId: event.categoryId,
startDate: event.startDate,
startTime: event.startTime || '09:00',
endDate: event.endDate || event.startDate,
endTime: event.endTime || '10:00',
allDay: event.allDay || false,
description: event.description || ''
};
this.showEventDialog = true;
},
closeEventDialog() {
this.showEventDialog = false;
this.editingEvent = null;
},
saveEvent() {
if (!this.eventForm.title) return;
const eventData = {
title: this.eventForm.title,
categoryId: this.eventForm.categoryId,
startDate: this.eventForm.startDate,
startTime: this.eventForm.allDay ? null : this.eventForm.startTime,
endDate: this.eventForm.endDate,
endTime: this.eventForm.allDay ? null : this.eventForm.endTime,
allDay: this.eventForm.allDay,
description: this.eventForm.description
};
if (this.editingEvent) {
// Update existing event
const index = this.events.findIndex(e => e.id === this.editingEvent.id);
if (index > -1) {
this.events[index] = { ...eventData, id: this.editingEvent.id };
}
} else {
// Create new event
this.events.push({ ...eventData, id: this.nextEventId++ });
}
this.saveEvents();
this.closeEventDialog();
},
deleteEvent() {
if (this.editingEvent) {
const index = this.events.findIndex(e => e.id === this.editingEvent.id);
if (index > -1) {
this.events.splice(index, 1);
}
this.saveEvents();
this.closeEventDialog();
}
},
// Persistence (localStorage for now, later backend)
saveEvents() {
localStorage.setItem('calendarEvents', JSON.stringify(this.events));
localStorage.setItem('calendarNextId', this.nextEventId.toString());
},
loadEvents() {
const saved = localStorage.getItem('calendarEvents');
if (saved) {
this.events = JSON.parse(saved);
}
const nextId = localStorage.getItem('calendarNextId');
if (nextId) {
this.nextEventId = parseInt(nextId);
}
}
}
};
@@ -348,6 +722,20 @@ h2 {
gap: 8px;
}
.btn-new-event {
padding: 8px 16px;
background: var(--color-primary-green);
color: #000;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
&:hover {
background: var(--color-primary-green-hover);
}
}
.btn-today {
padding: 8px 16px;
background: var(--color-primary-orange);
@@ -410,12 +798,53 @@ h2 {
}
}
// Selection info bar
.selection-info {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px;
background: #e3f2fd;
border: 1px solid #90caf9;
border-radius: 6px;
margin-bottom: 16px;
}
.btn-create-from-selection {
padding: 6px 12px;
background: var(--color-primary-green);
color: #000;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
&:hover {
background: var(--color-primary-green-hover);
}
}
.btn-clear-selection {
padding: 6px 12px;
background: #fff;
color: #666;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
&:hover {
background: #f5f5f5;
}
}
// Month View
.month-view {
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
user-select: none;
}
.weekday-headers {
@@ -439,7 +868,7 @@ h2 {
}
.day-cell {
min-height: 80px;
min-height: 90px;
padding: 8px;
border-right: 1px solid #eee;
border-bottom: 1px solid #eee;
@@ -480,6 +909,14 @@ h2 {
&.weekend {
background: #f9f9f9;
}
&.selected {
background: #bbdefb !important;
&:hover {
background: #90caf9 !important;
}
}
}
.day-number {
@@ -490,15 +927,25 @@ h2 {
.day-events {
display: flex;
flex-wrap: wrap;
flex-direction: column;
gap: 2px;
margin-top: 4px;
overflow: hidden;
}
.event-dot {
width: 8px;
height: 8px;
border-radius: 50%;
.event-item {
padding: 2px 6px;
border-radius: 3px;
font-size: 0.75em;
color: #fff;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
&:hover {
opacity: 0.9;
}
}
// Week & Day View
@@ -608,6 +1055,7 @@ h2 {
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background 0.2s;
position: relative;
&:hover {
background: var(--color-primary-orange-light);
@@ -619,6 +1067,35 @@ h2 {
}
}
.time-event {
position: absolute;
left: 2px;
right: 2px;
top: 2px;
padding: 2px 6px;
border-radius: 3px;
font-size: 0.75em;
color: #fff;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
z-index: 1;
&:hover {
opacity: 0.9;
}
&.full-width {
right: 2px;
}
}
.event-time {
font-weight: 600;
margin-right: 4px;
}
// Day View specifics
.day-header {
padding: 20px;
@@ -636,6 +1113,202 @@ h2 {
}
}
// Dialog
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.event-dialog {
background: #fff;
border-radius: 12px;
width: 90%;
max-width: 500px;
max-height: 90vh;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #eee;
background: var(--color-primary-orange);
h3 {
margin: 0;
font-size: 1.2em;
color: #000;
}
}
.btn-close {
background: transparent;
border: none;
font-size: 1.5em;
cursor: pointer;
color: #000;
padding: 0;
line-height: 1;
&:hover {
opacity: 0.7;
}
}
.dialog-body {
padding: 20px;
overflow-y: auto;
flex: 1;
}
.form-group {
margin-bottom: 16px;
label {
display: block;
margin-bottom: 6px;
font-weight: 500;
color: var(--color-text-primary);
}
input[type="text"],
input[type="date"],
input[type="time"],
textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 1em;
&:focus {
outline: none;
border-color: var(--color-primary-orange);
}
}
textarea {
resize: vertical;
}
}
.form-row {
display: flex;
gap: 16px;
.form-group {
flex: 1;
}
}
.checkbox-label {
display: flex !important;
align-items: center;
gap: 8px;
cursor: pointer;
input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
}
.category-selector {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.category-btn {
padding: 6px 12px;
border: 2px solid;
border-radius: 20px;
cursor: pointer;
font-size: 0.85em;
font-weight: 500;
transition: all 0.2s;
&:hover {
opacity: 0.8;
}
&.selected {
font-weight: 600;
}
}
.dialog-footer {
display: flex;
align-items: center;
padding: 16px 20px;
border-top: 1px solid #eee;
gap: 10px;
}
.spacer {
flex: 1;
}
.btn-delete {
padding: 10px 16px;
background: #f44336;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
&:hover {
background: #d32f2f;
}
}
.btn-cancel {
padding: 10px 16px;
background: #f5f5f5;
color: #666;
border: 1px solid #ddd;
border-radius: 6px;
cursor: pointer;
&:hover {
background: #e0e0e0;
}
}
.btn-save {
padding: 10px 20px;
background: var(--color-primary-green);
color: #000;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
&:hover:not(:disabled) {
background: var(--color-primary-green-hover);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
// Responsive
@media (max-width: 768px) {
.calendar-toolbar {
@@ -645,6 +1318,7 @@ h2 {
.nav-buttons {
justify-content: center;
flex-wrap: wrap;
}
.view-tabs {
@@ -660,5 +1334,15 @@ h2 {
padding: 8px 4px;
font-size: 0.8em;
}
.form-row {
flex-direction: column;
gap: 0;
}
.selection-info {
flex-direction: column;
text-align: center;
}
}
</style>