Web: remove version from tab title, fix month event packing, add day overflow popup

- Tab title shows only "Calendarr" (version stays in sidebar/impressum)
- Fix lane assignment in month view: use 2D per-column occupancy grid instead of
  global colEnd tracking; eliminates empty rows caused by non-overlapping events
  being pushed to lower lanes
- "+N weitere" now opens an inline popup listing all events for that day
  (all-day first, then by time); clicking an event opens its detail popup
- Double-click on day cell navigates to day view (was already implemented)
- Remove hardcoded "v18" from login screen impressum button
- Bump version to v49

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-06-10 18:06:54 +02:00
parent 0c8fbb86e8
commit 900abcdb7a
5 changed files with 130 additions and 13 deletions

View File

@@ -733,6 +733,43 @@ a { color: var(--primary); text-decoration: none; }
}
.month-more:hover { color: var(--primary); }
/* Overflow popup ("+N weitere") */
.month-overflow-popup {
position: fixed; z-index: 800;
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: 12px;
box-shadow: var(--shadow-lg);
padding: 10px 0;
min-width: 220px; max-width: 300px;
user-select: none;
}
.mop-header {
font-size: 12px; font-weight: 600;
color: var(--text-2);
padding: 0 12px 8px;
border-bottom: 1px solid var(--border);
margin-bottom: 4px;
}
.mop-row {
display: flex; align-items: center; gap: 7px;
padding: 4px 12px; cursor: pointer;
border-radius: 6px; margin: 0 4px;
}
.mop-row:hover { background: var(--bg-hover); }
.mop-dot {
width: 8px; height: 8px; border-radius: 50%;
flex-shrink: 0;
}
.mop-time {
font-size: 11px; color: var(--text-2);
min-width: 42px; flex-shrink: 0;
}
.mop-title {
font-size: 12px; font-weight: 500; color: var(--text-1);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* ── Week / Day Views ───────────────────────────────────── */
.week-view, .day-view { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: scroll; }
.week-head-sticky {

View File

@@ -80,7 +80,7 @@
<button type="submit" class="btn btn-primary btn-full">Anmelden</button>
</form>
</div>
<button class="impressum-link" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices&nbsp;·&nbsp;v18</button>
<button class="impressum-link" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices</button>
</div>
<!-- ─── MAIN APP ──────────────────────────────────────────── -->

View File

@@ -9,8 +9,8 @@ import { openDatePicker, formatDtDisplay } from './date-picker.js';
import { t, setLang, getLang } from './i18n.js';
import { APP_VERSION } from './version.js';
// Version sofort beim Modul-Load ueberall sichtbar setzen.
document.title = `Calendarr ${APP_VERSION}`;
// Version im Impressum/Sidebar sichtbar, nicht im Tab-Titel.
document.title = 'Calendarr';
document.addEventListener('DOMContentLoaded', () => {
const imp = document.getElementById('impressum-version');
if (imp) imp.textContent = `Calendarr ${APP_VERSION}`;
@@ -543,7 +543,7 @@ function updateTitle() {
titleEl.innerHTML =
`<span class="view-title-main">${main}</span>` +
(year ? `<span class="view-title-year">${year}</span>` : '');
document.title = `Calendarr ${APP_VERSION} - ${fullText}`;
document.title = `Calendarr ${fullText}`;
}
function updateViewButtons() {

View File

@@ -1,2 +1,2 @@
// Increment APP_VERSION with every code change
export const APP_VERSION = 'v48';
export const APP_VERSION = 'v49';

View File

@@ -71,13 +71,21 @@ export function renderMonth(container, currentDate, events, onDayClick, onEventC
return new Date(a.ev.start) - new Date(b.ev.start);
});
// Assign lanes (greedy interval packing)
const lanes = [];
// Assign lanes using a 2D occupancy grid (per-column tracking).
// This prevents gaps: an event in cols 3-6 no longer blocks col 0-2 in the same lane.
const occupiedGrid = []; // occupiedGrid[lane] = Set<col>
rowItems.forEach(item => {
let laneIdx = lanes.findIndex(l => item.colStart >= l.colEnd);
if (laneIdx === -1) { laneIdx = lanes.length; lanes.push({ colEnd: 0 }); }
item.lane = laneIdx;
lanes[laneIdx].colEnd = item.colStart + item.span;
const cols = Array.from({ length: item.span }, (_, i) => item.colStart + i);
let laneIdx = 0;
for (;;) {
if (!occupiedGrid[laneIdx]) occupiedGrid[laneIdx] = new Set();
if (cols.every(c => !occupiedGrid[laneIdx].has(c))) {
cols.forEach(c => occupiedGrid[laneIdx].add(c));
item.lane = laneIdx;
break;
}
laneIdx++;
}
});
// Track overflow per column
@@ -190,11 +198,20 @@ export function renderMonth(container, currentDate, events, onDayClick, onEventC
if (ev) onEventClick(ev, spanEl);
return;
}
// "+N more" → navigate to day view
// "+N more" → show overflow popup with all events for that day
const moreEl = e.target.closest('.month-more');
if (moreEl) {
e.stopPropagation();
onDayClick(new Date(moreEl.dataset.date + 'T00:00:00'), 'navigate');
const dayDate = new Date(moreEl.dataset.date + 'T00:00:00');
const dayEvents = normed
.filter(({ ns, ne }) => ns <= dayDate && ne >= dayDate)
.map(({ ev }) => ev)
.sort((a, b) => {
if (a.allDay && !b.allDay) return -1;
if (!a.allDay && b.allDay) return 1;
return new Date(a.start) - new Date(b.start);
});
showOverflowPopup(moreEl, dayDate, dayEvents, onEventClick);
return;
}
// Column click → select day
@@ -243,3 +260,66 @@ function escHtml(s) {
function escAttr(s) {
return String(s).replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}
function showOverflowPopup(anchor, date, events, onEventClick) {
document.querySelectorAll('.month-overflow-popup').forEach(p => p.remove());
const popup = document.createElement('div');
popup.className = 'month-overflow-popup';
// Header: "Mo, 3. Jul"
const months = t('months');
const dow = t('dow_monday');
const dowIdx = (date.getDay() + 6) % 7; // 0=Mon
const header = document.createElement('div');
header.className = 'mop-header';
header.textContent = `${dow[dowIdx]}, ${date.getDate()}. ${months[date.getMonth()]}`;
popup.appendChild(header);
events.forEach(ev => {
const row = document.createElement('div');
row.className = 'mop-row';
const dot = document.createElement('span');
dot.className = 'mop-dot';
dot.style.background = ev.color || ev.calendarColor || '#4285f4';
const time = document.createElement('span');
time.className = 'mop-time';
time.textContent = ev.allDay ? t('allday_cap') : fmtTime(new Date(ev.start));
const title = document.createElement('span');
title.className = 'mop-title';
title.textContent = ev.title;
row.append(dot, time, title);
row.addEventListener('click', e => {
e.stopPropagation();
popup.remove();
onEventClick(ev, row);
});
popup.appendChild(row);
});
document.body.appendChild(popup);
// Position near anchor, stay in viewport
const r = anchor.getBoundingClientRect();
const pw = popup.offsetWidth || 260;
const ph = popup.offsetHeight || 200;
let left = r.left;
let top = r.bottom + 4;
if (left + pw > window.innerWidth - 8) left = window.innerWidth - pw - 8;
if (top + ph > window.innerHeight - 8) top = r.top - ph - 4;
if (left < 8) left = 8;
if (top < 8) top = 8;
popup.style.left = left + 'px';
popup.style.top = top + 'px';
setTimeout(() => {
document.addEventListener('click', function close() {
popup.remove();
document.removeEventListener('click', close);
});
}, 0);
}