Add friends' birthdays feature: Implement API endpoint to retrieve friends' birthdays for a specified year, enhance calendar service to handle visibility checks, and update CalendarView to display birthday events with distinct styling. This update improves user experience by allowing users to view important dates of their friends.

This commit is contained in:
Torsten Schulz (local)
2026-01-30 14:59:32 +01:00
parent 7635355e94
commit f65d3385ec
4 changed files with 219 additions and 6 deletions

View File

@@ -64,11 +64,12 @@
<div
v-for="event in getEventsForDate(day.date)"
:key="event.id"
class="event-item"
:class="['event-item', { 'birthday-event': event.isBirthday }]"
:style="{ backgroundColor: getCategoryColor(event.categoryId) }"
:title="event.title"
@click.stop="editEvent(event)"
>
<span v-if="event.isBirthday" class="birthday-icon">🎂</span>
{{ event.title }}
</div>
</div>
@@ -95,10 +96,11 @@
<div
v-for="event in getEventsForDateAllDay(day.date)"
:key="event.id"
class="all-day-event"
:class="['all-day-event', { 'birthday-event': event.isBirthday }]"
:style="{ backgroundColor: getCategoryColor(event.categoryId) }"
@click="editEvent(event)"
>
<span v-if="event.isBirthday" class="birthday-icon">🎂</span>
{{ event.title }}
</div>
</div>
@@ -152,10 +154,11 @@
<div
v-for="event in getEventsForDateAllDay(day.date)"
:key="event.id"
class="all-day-event"
:class="['all-day-event', { 'birthday-event': event.isBirthday }]"
:style="{ backgroundColor: getCategoryColor(event.categoryId) }"
@click="editEvent(event)"
>
<span v-if="event.isBirthday" class="birthday-icon">🎂</span>
{{ event.title }}
</div>
</div>
@@ -205,10 +208,11 @@
<div
v-for="event in getEventsForDateAllDay(currentDateStr)"
:key="event.id"
class="all-day-event"
:class="['all-day-event', { 'birthday-event': event.isBirthday }]"
:style="{ backgroundColor: getCategoryColor(event.categoryId) }"
@click="editEvent(event)"
>
<span v-if="event.isBirthday" class="birthday-icon">🎂</span>
{{ event.title }}
</div>
</div>
@@ -368,6 +372,7 @@ export default {
// Events storage
events: [],
birthdays: [],
loading: false,
saving: false,
@@ -513,6 +518,7 @@ export default {
return `${year}-${month}-${day}`;
},
navigatePrev() {
const oldYear = this.currentDate.getFullYear();
const newDate = new Date(this.currentDate);
switch (this.currentView) {
case 'month':
@@ -529,8 +535,13 @@ export default {
break;
}
this.currentDate = newDate;
// Reload birthdays if year changed
if (newDate.getFullYear() !== oldYear) {
this.loadBirthdays();
}
},
navigateNext() {
const oldYear = this.currentDate.getFullYear();
const newDate = new Date(this.currentDate);
switch (this.currentView) {
case 'month':
@@ -547,9 +558,18 @@ export default {
break;
}
this.currentDate = newDate;
// Reload birthdays if year changed
if (newDate.getFullYear() !== oldYear) {
this.loadBirthdays();
}
},
goToToday() {
const oldYear = this.currentDate.getFullYear();
this.currentDate = new Date();
// Reload birthdays if year changed
if (this.currentDate.getFullYear() !== oldYear) {
this.loadBirthdays();
}
},
formatHour(hour) {
return `${hour.toString().padStart(2, '0')}:00`;
@@ -638,14 +658,16 @@ export default {
return luminance > 0.5 ? '#000000' : '#ffffff';
},
getEventsForDate(dateStr) {
return this.events.filter(event => {
const allEvents = [...this.events, ...this.birthdays];
return allEvents.filter(event => {
const eventStart = event.startDate;
const eventEnd = event.endDate || event.startDate;
return dateStr >= eventStart && dateStr <= eventEnd;
});
},
getEventsForDateAllDay(dateStr) {
return this.events.filter(event => {
const allEvents = [...this.events, ...this.birthdays];
return allEvents.filter(event => {
if (!event.allDay) return false;
const eventStart = event.startDate;
const eventEnd = event.endDate || event.startDate;
@@ -704,6 +726,9 @@ export default {
});
},
editEvent(event) {
// Birthday events are read-only
if (event.isBirthday) return;
this.editingEvent = event;
this.eventForm = {
title: event.title,
@@ -784,12 +809,29 @@ export default {
try {
const response = await apiClient.get('/api/calendar/events');
this.events = response.data;
await this.loadBirthdays();
} catch (error) {
console.error('Error loading events:', error);
this.events = [];
} finally {
this.loading = false;
}
},
async loadBirthdays() {
try {
const year = this.currentDate.getFullYear();
const response = await apiClient.get(`/api/calendar/birthdays?year=${year}`);
this.birthdays = response.data;
} catch (error) {
console.error('Error loading birthdays:', error);
this.birthdays = [];
}
},
// Combined events (regular events + birthdays)
getAllEvents() {
return [...this.events, ...this.birthdays];
}
}
};
@@ -1047,6 +1089,27 @@ h2 {
&:hover {
opacity: 0.9;
}
&.birthday-event {
cursor: default;
&:hover {
opacity: 1;
}
}
}
.birthday-icon {
margin-right: 4px;
}
.all-day-event.birthday-event,
.time-event.birthday-event {
cursor: default;
&:hover {
opacity: 1;
}
}
// Week & Day View