fix: delete n8n old calendar to keep only cal one

This commit is contained in:
Nathan FONTEYNE 2026-07-09 18:15:34 +02:00
parent 63fb6cd45a
commit 6de6cfacf2
9 changed files with 220 additions and 131 deletions

View file

@ -180,21 +180,55 @@ nav#main-nav {
margin-left: auto;
}
.nav-user > a {
color: var(--accent);
text-decoration: none;
font-weight: 500;
.nav-profile {
position: relative;
}
.nav-profile-link {
.nav-profile-trigger {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--text) !important;
background: transparent;
border: none;
padding: 0.3rem 0.4rem;
border-radius: var(--radius-sm);
color: var(--text);
font-weight: 500;
font-size: 0.88rem;
cursor: pointer;
}
.nav-profile-link span { color: var(--text); }
.nav-profile-trigger:hover { background: var(--surface-alt); }
.nav-profile-trigger span { color: var(--text); }
.nav-profile-dropdown {
display: none;
position: absolute;
top: calc(100% + 0.4rem);
right: 0;
min-width: 180px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: var(--shadow);
padding: 0.35rem;
flex-direction: column;
z-index: 30;
}
.nav-profile-dropdown.open { display: flex; }
.nav-profile-dropdown a {
color: var(--text);
text-decoration: none;
font-weight: 500;
font-size: 0.9rem;
padding: 0.5rem 0.6rem;
border-radius: var(--radius-sm);
}
.nav-profile-dropdown a:hover { background: var(--surface-alt); }
#theme-toggle {
border-radius: 999px;
@ -1068,16 +1102,31 @@ a.back-link:hover { color: var(--accent); }
.nav-user {
flex-direction: column;
align-items: flex-start;
align-items: stretch;
gap: 0;
margin-left: 0;
}
.nav-user > a,
.nav-user > .nav-profile-link {
.nav-profile-trigger {
width: 100%;
padding: 0.5rem 0;
border-bottom: 1px solid var(--border);
border-radius: 0;
}
.nav-profile-dropdown {
position: static;
box-shadow: none;
border: none;
padding: 0;
min-width: 0;
}
.nav-profile-dropdown a {
width: 100%;
padding: 0.5rem 0;
border-bottom: 1px solid var(--border);
border-radius: 0;
}
main { padding: 1.25rem 0.9rem 3rem; }
@ -1330,13 +1379,6 @@ a.back-link:hover { color: var(--accent); }
gap: 0.6rem;
}
.calendar-color-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex: 0 0 auto;
}
.calendar-feeds {
display: flex;
flex-direction: column;

View file

@ -56,16 +56,16 @@ function feedRowTemplate(feed) {
`;
}
function calendarPersonRowTemplate(person) {
function calendarUserRowTemplate(user) {
return `
<div class="admin-user-row calendar-person-row" data-person-id="${person.id}">
<div class="admin-user-row calendar-person-row" data-user-id="${user.id}">
<div class="admin-user-identity">
<span class="calendar-color-dot" style="background:${escapeHtml(person.color)}"></span>
<div class="card-title">${escapeHtml(person.name)}</div>
${avatarHtml(user, 'avatar-sm')}
<div class="card-title">${escapeHtml(user.name)}${user.isAdmin ? ' <span class="badge">admin</span>' : ''}</div>
</div>
<div class="calendar-feeds">
${person.feeds.length ? person.feeds.map(feedRowTemplate).join('') : '<p class="empty">Aucun calendrier configuré.</p>'}
<form class="inline-form add-feed-form" data-person-id="${person.id}">
${user.feeds.length ? user.feeds.map(feedRowTemplate).join('') : '<p class="empty">Aucun calendrier configuré.</p>'}
<form class="inline-form add-feed-form" data-user-id="${user.id}">
<input name="label" placeholder="Libellé (optionnel)">
<input name="icsUrl" type="url" placeholder="URL du calendrier (.ics)" required>
<button type="submit" class="secondary icon-btn">+ Ajouter</button>
@ -75,29 +75,29 @@ function calendarPersonRowTemplate(person) {
`;
}
async function loadCalendarPeople() {
const people = await api.get('/api/calendar/people/admin');
async function loadCalendarUsers() {
const users = await api.get('/api/calendar/people/admin');
const container = document.getElementById('calendar-people-list');
container.innerHTML = people.length
? people.map(calendarPersonRowTemplate).join('')
: '<p class="empty">Aucune personne suivie pour le moment.</p>';
container.innerHTML = users.length
? users.map(calendarUserRowTemplate).join('')
: '<p class="empty">Aucun utilisateur pour le moment.</p>';
container.querySelectorAll('.add-feed-form').forEach((form) => {
form.addEventListener('submit', (e) => onAddFeed(e, parseInt(form.dataset.personId, 10)));
form.addEventListener('submit', (e) => onAddFeed(e, parseInt(form.dataset.userId, 10)));
});
container.querySelectorAll('.remove-feed-btn').forEach((btn) => {
btn.addEventListener('click', () => onRemoveFeed(parseInt(btn.dataset.feedId, 10)));
});
}
async function onAddFeed(e, personId) {
async function onAddFeed(e, userId) {
e.preventDefault();
const form = e.target;
const label = form.label.value.trim();
const icsUrl = form.icsUrl.value.trim();
try {
await api.post(`/api/calendar/people/${personId}/feeds`, { label, icsUrl });
await loadCalendarPeople();
await api.post(`/api/calendar/people/${userId}/feeds`, { label, icsUrl });
await loadCalendarUsers();
} catch (err) {
showError(err.message);
}
@ -106,7 +106,7 @@ async function onAddFeed(e, personId) {
async function onRemoveFeed(feedId) {
try {
await api.del(`/api/calendar/feeds/${feedId}`);
await loadCalendarPeople();
await loadCalendarUsers();
} catch (err) {
showError(err.message);
}
@ -197,9 +197,10 @@ async function onSaveSlotSettings(e) {
<h2>Calendriers des membres</h2>
<p class="note">
Chaque personne peut avoir plusieurs calendriers (Google, Outlook, Apple...). L'application ne
conserve jamais le contenu de ces calendriers seul un statut disponible/occupé par créneau est
déduit et enregistré.
Chaque utilisateur de l'application peut avoir plusieurs calendriers (Google, Outlook, Apple...).
Seuls les utilisateurs avec au moins un calendrier configuré apparaissent sur `/calendar.html`.
L'application ne conserve jamais le contenu de ces calendriers seul un statut disponible/occupé
par créneau est déduit et enregistré.
</p>
<div class="panel admin-user-list" id="calendar-people-list">
<p class="empty">Chargement</p>
@ -207,7 +208,7 @@ async function onSaveSlotSettings(e) {
`;
document.getElementById('slot-settings-form').addEventListener('submit', onSaveSlotSettings);
await loadSlotSettingsForm();
await loadCalendarPeople();
await loadCalendarUsers();
} catch (err) {
showError(err.message);
}

View file

@ -177,14 +177,14 @@ function buildFilters() {
const btn = document.getElementById('btn-refresh');
btn.disabled = true;
btn.textContent = 'Actualisation…';
showToast('Déclenchement du workflow n8n…');
showToast('Synchronisation des calendriers…');
try {
await api.post('/api/calendar/refresh', {});
showToast('Workflow en cours — en attente des résultats…');
showToast('Synchronisation en cours — en attente des résultats…');
pollWorkflowStatus(btn);
} catch (err) {
const message = err.message === 'n8n_not_configured'
? "L'actualisation automatique n'est pas configurée (n8n)."
const message = err.message === 'no_feeds_configured'
? "Aucun calendrier n'est configuré — voir la page Administration."
: err.message;
showToast(message, true);
resetRefreshButton(btn);

View file

@ -34,12 +34,16 @@ async function initNav(activePage) {
${me.isAdmin ? `<a href="/admin.html" class="${activePage === 'admin' ? 'active' : ''}">Administration</a>` : ''}
</div>
<div class="nav-user">
${me.authentikAccountUrl ? `<a href="${escapeHtml(me.authentikAccountUrl)}" target="_blank" rel="noopener" title="Gérer mon compte Authentik (mot de passe, etc.)">Mon compte</a>` : ''}
<a class="nav-profile-link" href="/profile.html">
${avatarHtml(me, 'avatar-sm')}
<span>${escapeHtml(me.name)}${me.isAdmin ? ' <span class="badge">admin</span>' : ''}</span>
</a>
<a href="/auth/logout">Se déconnecter</a>
<div class="nav-profile" id="nav-profile">
<button type="button" class="nav-profile-trigger" id="nav-profile-trigger" aria-haspopup="true" aria-expanded="false">
${avatarHtml(me, 'avatar-sm')}
<span>${escapeHtml(me.name)}${me.isAdmin ? ' <span class="badge">admin</span>' : ''}</span>
</button>
<div class="nav-profile-dropdown" id="nav-profile-dropdown">
<a href="/profile.html">Voir profil</a>
<a href="/auth/logout">Se déconnecter</a>
</div>
</div>
</div>
</div>
<div class="nav-controls">
@ -53,6 +57,24 @@ async function initNav(activePage) {
toggle.addEventListener('click', () => menu.classList.toggle('open'));
menu.querySelectorAll('a').forEach((a) => a.addEventListener('click', () => menu.classList.remove('open')));
const profileTrigger = document.getElementById('nav-profile-trigger');
const profileDropdown = document.getElementById('nav-profile-dropdown');
function closeProfileDropdown() {
profileDropdown.classList.remove('open');
profileTrigger.setAttribute('aria-expanded', 'false');
}
profileTrigger.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = profileDropdown.classList.toggle('open');
profileTrigger.setAttribute('aria-expanded', String(isOpen));
});
document.addEventListener('click', (e) => {
if (!document.getElementById('nav-profile').contains(e.target)) closeProfileDropdown();
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeProfileDropdown();
});
updateThemeToggleIcon();
document.getElementById('theme-toggle').addEventListener('click', () => {
applyTheme(currentTheme() === 'dark' ? 'light' : 'dark');