feat: collab sub-feature toggles and provider icons

- Add admin toggles for individual collab sections (Chat, Notes,
  Polls, What's Next) stored in app_settings
- CollabPanel adapts layout dynamically: chat always fixed 380px,
  remaining panels share space equally
- Mobile: disabled tabs are hidden
- Add Immich and Synology Photos SVG icons to photo provider toggles
- Add Luggage icon to bag tracking sub-toggle
- API: GET/PUT /admin/collab-features endpoints
- i18n: all 15 languages updated

Closes #604
This commit is contained in:
Maurice
2026-04-15 23:53:16 +02:00
parent c8fc21b8bd
commit 099255761c
23 changed files with 370 additions and 41 deletions
+25
View File
@@ -459,6 +459,31 @@ export function updateBagTracking(enabled: boolean) {
return { enabled: !!enabled };
}
// ── Collab Features ───────────────────────────────────────────────────────
const COLLAB_FEATURE_KEYS = ['collab_chat_enabled', 'collab_notes_enabled', 'collab_polls_enabled', 'collab_whatsnext_enabled'] as const;
export function getCollabFeatures() {
const rows = db.prepare("SELECT key, value FROM app_settings WHERE key IN ('collab_chat_enabled', 'collab_notes_enabled', 'collab_polls_enabled', 'collab_whatsnext_enabled')").all() as { key: string; value: string }[];
const map: Record<string, string> = {};
for (const r of rows) map[r.key] = r.value;
return {
chat: map['collab_chat_enabled'] !== 'false',
notes: map['collab_notes_enabled'] !== 'false',
polls: map['collab_polls_enabled'] !== 'false',
whatsnext: map['collab_whatsnext_enabled'] !== 'false',
};
}
export function updateCollabFeatures(features: { chat?: boolean; notes?: boolean; polls?: boolean; whatsnext?: boolean }) {
const mapping: Record<string, string> = { chat: 'collab_chat_enabled', notes: 'collab_notes_enabled', polls: 'collab_polls_enabled', whatsnext: 'collab_whatsnext_enabled' };
const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)");
for (const [feat, key] of Object.entries(mapping)) {
if (features[feat] !== undefined) stmt.run(key, features[feat] ? 'true' : 'false');
}
return getCollabFeatures();
}
// ── Packing Templates ──────────────────────────────────────────────────────
export function listPackingTemplates() {