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
+8
View File
@@ -192,6 +192,10 @@ export default function AdminPage(): React.ReactElement {
const [bagTrackingEnabled, setBagTrackingEnabled] = useState<boolean>(false)
useEffect(() => { adminApi.getBagTracking().then(d => setBagTrackingEnabled(d.enabled)).catch(() => {}) }, [])
// Collab features
const [collabFeatures, setCollabFeatures] = useState<{ chat: boolean; notes: boolean; polls: boolean; whatsnext: boolean }>({ chat: true, notes: true, polls: true, whatsnext: true })
useEffect(() => { adminApi.getCollabFeatures().then(d => setCollabFeatures(d)).catch(() => {}) }, [])
// OIDC config
const [oidcConfig, setOidcConfig] = useState<OidcConfig>({ issuer: '', client_id: '', client_secret: '', client_secret_set: false, display_name: '', discovery_url: '' })
const [savingOidc, setSavingOidc] = useState<boolean>(false)
@@ -797,6 +801,10 @@ export default function AdminPage(): React.ReactElement {
const next = !bagTrackingEnabled
setBagTrackingEnabled(next)
try { await adminApi.updateBagTracking(next) } catch { setBagTrackingEnabled(!next) }
}} collabFeatures={collabFeatures} onToggleCollabFeature={async (key: string) => {
const next = { ...collabFeatures, [key]: !collabFeatures[key] }
setCollabFeatures(next)
try { await adminApi.updateCollabFeatures({ [key]: next[key] }) } catch { setCollabFeatures(collabFeatures) }
}} />
</div>
)}
+3 -1
View File
@@ -100,6 +100,7 @@ export default function TripPlannerPage(): React.ReactElement | null {
}, [undo, lastActionLabel, toast])
const [enabledAddons, setEnabledAddons] = useState<Record<string, boolean>>({ packing: true, budget: true, documents: true, collab: false })
const [collabFeatures, setCollabFeatures] = useState<{ chat: boolean; notes: boolean; polls: boolean; whatsnext: boolean }>({ chat: true, notes: true, polls: true, whatsnext: true })
const [tripAccommodations, setTripAccommodations] = useState<Accommodation[]>([])
const [allowedFileTypes, setAllowedFileTypes] = useState<string | null>(null)
const [tripMembers, setTripMembers] = useState<TripMember[]>([])
@@ -116,6 +117,7 @@ export default function TripPlannerPage(): React.ReactElement | null {
const map = {}
data.addons.forEach(a => { map[a.id] = true })
setEnabledAddons({ packing: !!map.packing, budget: !!map.budget, documents: !!map.documents, collab: !!map.collab })
if (data.collabFeatures) setCollabFeatures(data.collabFeatures)
}).catch(() => {})
authApi.getAppConfig().then(config => {
if (config.allowed_file_types) setAllowedFileTypes(config.allowed_file_types)
@@ -956,7 +958,7 @@ export default function TripPlannerPage(): React.ReactElement | null {
{activeTab === 'collab' && (
<div style={{ position: 'absolute', inset: 0, overflow: 'hidden' }}>
<CollabPanel tripId={tripId} tripMembers={tripMembers} />
<CollabPanel tripId={tripId} tripMembers={tripMembers} collabFeatures={collabFeatures} />
</div>
)}
</div>