Web polish from testing feedback + admin role toggle

- style the settings-table dropdowns for the dark theme (were white)
- share-icon picker on one even row (no lopsided wrap)
- more breathing room between colour hex and swatch
- user management: promote/demote admin (new PUT /users/{id}/admin,
  guarded against self-change and removing the last admin)
- calendar management table: clip/ellipsis cells so narrow columns no
  longer overlap (full-width detail rows still wrap)
- centralise default colours: utils.applyTheme now derives every fallback
  from settings-sync.DEFAULT_COLORS (single place for coders to edit)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-14 21:25:01 +02:00
parent 137694ed98
commit 4ab07ddcc3
6 changed files with 80 additions and 22 deletions

View File

@@ -4248,20 +4248,32 @@ async function loadUsers() {
try {
const users = await api.get('/users/');
const list = document.getElementById('users-list');
list.innerHTML = users.map(u =>
`<div class="users-list-item">
const selfId = JSON.parse(localStorage.getItem('user') || '{}').id;
list.innerHTML = users.map(u => {
const isSelf = u.id === selfId;
return `<div class="users-list-item">
<div>
<div class="uname">${escHtml(u.username)}</div>
${u.email ? `<div class="uemail">${escHtml(u.email)}</div>` : ''}
</div>
<div style="display:flex;gap:8px;align-items:center">
${u.is_admin ? '<span class="ubadge">Admin</span>' : ''}
${u.id !== JSON.parse(localStorage.getItem('user')||'{}').id
? `<button class="btn btn-ghost" style="padding:4px 10px;font-size:12px" data-del-user="${u.id}">Löschen</button>`
: ''}
${isSelf ? '' :
`<button class="btn btn-ghost" style="padding:4px 10px;font-size:12px" data-admin-user="${u.id}" data-admin-next="${u.is_admin ? '0' : '1'}">${u.is_admin ? t('users_revoke_admin') : t('users_make_admin')}</button>`}
${isSelf ? '' :
`<button class="btn btn-ghost" style="padding:4px 10px;font-size:12px" data-del-user="${u.id}">${t('delete')}</button>`}
</div>
</div>`
).join('');
</div>`;
}).join('');
list.querySelectorAll('[data-admin-user]').forEach(btn => {
btn.addEventListener('click', async () => {
try {
await api.put(`/users/${btn.dataset.adminUser}/admin`, { is_admin: btn.dataset.adminNext === '1' });
loadUsers();
} catch (e) { showToast(e.message, true); }
});
});
list.querySelectorAll('[data-del-user]').forEach(btn => {
btn.addEventListener('click', async () => {

View File

@@ -212,6 +212,7 @@ const translations = {
// User management
users_add: 'Benutzer hinzufügen', users_is_admin: 'Administrator',
users_make_admin: 'Zu Admin machen', users_revoke_admin: 'Admin entziehen',
// Profile
profile_title: 'Profil', profile_upload: 'Bild hochladen',
@@ -564,6 +565,7 @@ const translations = {
// User management
users_add: 'Add user', users_is_admin: 'Administrator',
users_make_admin: 'Make admin', users_revoke_admin: 'Revoke admin',
// Profile
profile_title: 'Profile', profile_upload: 'Upload image',

View File

@@ -1,3 +1,5 @@
import { DEFAULT_COLORS } from './settings-sync.js';
export function isToday(d) {
const now = new Date();
return d.getFullYear() === now.getFullYear() &&
@@ -96,18 +98,18 @@ const LINE_CONTRAST = {
4: { border: '#5a5a78', light: '#484860' },
};
// Defaults wenn kein Custom-Override gesetzt ist.
// Bewusst hart "weiss auf schwarz" damit man nie unsichtbar landet.
export const DEFAULT_TEXT_COLOR = '#FFFFFF';
export const DEFAULT_LINE_COLOR = '#3A3A52';
export const DEFAULT_BG_COLOR = '#000000';
// Default-Farben: EINZIGE Quelle ist DEFAULT_COLORS in settings-sync.js.
// Dort ändern → wirkt für Reset (Tabelle) und diese Theme-Fallbacks gleichzeitig.
export const DEFAULT_TEXT_COLOR = DEFAULT_COLORS.text_color;
export const DEFAULT_LINE_COLOR = DEFAULT_COLORS.line_color;
export const DEFAULT_BG_COLOR = DEFAULT_COLORS.bg_color;
export function applyTheme(settings) {
const root = document.documentElement;
root.style.setProperty('--primary', settings.primary_color || '#4285f4');
root.style.setProperty('--primary-dim', hexToRgba(settings.primary_color || '#4285f4', 0.15));
root.style.setProperty('--accent', settings.accent_color || '#ea4335');
root.style.setProperty('--today-color', settings.today_color || '#4285f4');
root.style.setProperty('--primary', settings.primary_color || DEFAULT_COLORS.primary_color);
root.style.setProperty('--primary-dim', hexToRgba(settings.primary_color || DEFAULT_COLORS.primary_color, 0.15));
root.style.setProperty('--accent', settings.accent_color || DEFAULT_COLORS.accent_color);
root.style.setProperty('--today-color', settings.today_color || DEFAULT_COLORS.today_color);
// Effektive Farben bestimmen (Override > Default).
let textColor = settings.text_color || DEFAULT_TEXT_COLOR;
@@ -140,8 +142,8 @@ export function applyTheme(settings) {
const hh = settings.hour_height || 44;
root.style.setProperty('--hour-h', hh + 'px');
root.style.setProperty('--month-divider-color', settings.month_divider_color || '#7090c0');
root.style.setProperty('--month-label-color', settings.month_label_color || '#7090c0');
root.style.setProperty('--month-divider-color', settings.month_divider_color || DEFAULT_COLORS.month_divider_color);
root.style.setProperty('--month-label-color', settings.month_label_color || DEFAULT_COLORS.month_label_color);
}
function luminance(hex) {