feat: overlapping event layout and calendar event attachments (#107)

- Overlapping timed events in week/day views now render side-by-side using a column-layout algorithm
- Calendar events support optional file attachments (images, PDFs, documents up to 5 MB)
- Attachment images shown in event popup; other files as download links
- Birthday modal redesigned with photo/avatar side-by-side with name/date fields
- DB migration 27: adds attachment_name, attachment_mime, attachment_size, attachment_data columns to calendar_events
- Server-side MIME allowlist and size validation for attachments
- i18n: all 15 locales include new attachment keys (de properly translated)

Co-Authored-By: Rafael Foster <rafaelfoster@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ulas Kalayci
2026-04-29 16:54:02 +02:00
23 changed files with 477 additions and 57 deletions
+70 -1
View File
@@ -287,6 +287,21 @@
background: color-mix(in srgb, var(--module-accent) 16%, white);
}
.birthday-avatar-editor {
width: 84px;
height: 84px;
margin: 0 auto var(--space-3);
padding: 0;
border: none;
border-radius: var(--radius-full);
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background: color-mix(in srgb, var(--module-accent) 16%, white);
cursor: pointer;
}
.birthday-preview__image {
width: 100%;
height: 100%;
@@ -300,7 +315,51 @@
}
.birthday-modal__photo-actions {
margin-top: var(--space-2);
display: flex;
gap: var(--space-2);
justify-content: center;
margin-top: calc(var(--space-1) * -1);
margin-bottom: var(--space-2);
}
.birthday-modal__photo-action {
width: 30px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-full);
border: 1px solid var(--color-border);
background: var(--color-surface);
color: var(--color-text-secondary);
}
.birthday-modal__photo-action svg {
width: 14px;
height: 14px;
}
.birthday-modal__photo-action--danger {
color: var(--color-danger);
}
.birthday-modal__identity {
display: flex;
align-items: flex-start;
gap: var(--space-4);
}
.birthday-modal__photo-wrap {
width: 84px;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
}
.birthday-modal__fields {
flex: 1;
min-width: 0;
}
.birthday-modal__hint {
@@ -308,6 +367,16 @@
font-size: var(--text-sm);
}
@media (max-width: 640px) {
.birthday-modal__identity {
flex-direction: column;
}
.birthday-modal__photo-actions {
justify-content: flex-start;
}
}
@media (max-width: 960px) {
.birthdays-grid {
grid-template-columns: 1fr;
+42 -2
View File
@@ -391,8 +391,6 @@
.week-event {
position: absolute;
left: 2px;
right: 2px;
border-radius: var(--radius-xs);
padding: var(--space-0h) var(--space-1);
font-size: var(--text-xs);
@@ -424,6 +422,29 @@
opacity: 0.85;
}
.event-attachment-preview {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin-top: var(--space-2);
}
.event-attachment-preview img {
width: 100%;
max-height: 180px;
object-fit: cover;
border-radius: var(--radius-md);
border: 1px solid var(--color-border-subtle);
}
.event-attachment-preview a {
display: inline-flex;
align-items: center;
gap: var(--space-2);
color: var(--color-accent);
word-break: break-word;
}
/* --------------------------------------------------------
* Tagesansicht
* -------------------------------------------------------- */
@@ -768,6 +789,25 @@
border-top: 1px solid var(--color-border);
}
.event-popup__attachment {
margin-top: var(--space-2);
}
.event-popup__attachment--image img {
width: 100%;
max-height: 220px;
object-fit: cover;
border-radius: var(--radius-md);
border: 1px solid var(--color-border-subtle);
}
.event-popup__attachment--file {
display: inline-flex;
align-items: center;
gap: var(--space-2);
color: var(--color-accent);
}
/* --------------------------------------------------------
* Ganztägige Ereignisse (oben in Wochen-/Tagesansicht)
* -------------------------------------------------------- */