fix(journal): Modul-Button folgt dem Mandanten des offenen Einsatzes (v1.8.6)
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 45m49s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 45m49s
Ursache: der "Module"-Button hing am Mandanten des Nutzers, nicht am Mandanten des geöffneten Einsatzes. SERVER_ADMIN (ohne eigenen Mandant) oder mandantenfremde Einsätze zeigten daher keinen Button. - /api/tenant/modules: GET/PUT akzeptieren tenantId; SERVER_ADMIN darf den Mandanten des Einsatzes verwalten, andere nur den eigenen (canManage sauber berechnet) - journal-view lädt/speichert Module für den Einsatz-Mandanten (projectTenantId) - ModuleManagerDialog reicht tenantId beim Speichern durch Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lageplan",
|
||||
"version": "1.8.5",
|
||||
"version": "1.8.6",
|
||||
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -3,26 +3,45 @@ import { prisma } from '@/lib/db'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { normalizeModules, DEFAULT_MODULES } from '@/lib/modules'
|
||||
|
||||
/** Cockpit-Modul-Konfiguration des eigenen Mandanten lesen */
|
||||
export async function GET() {
|
||||
/**
|
||||
* Ermittelt den massgeblichen Mandanten für die Modul-Verwaltung.
|
||||
* SERVER_ADMIN darf einen beliebigen Mandanten (z.B. den des offenen Einsatzes) verwalten;
|
||||
* andere nur ihren eigenen. `canManage` sagt, ob dieser Nutzer speichern darf.
|
||||
*/
|
||||
function resolveTenant(user: any, requestedTenantId: string | null): { tenantId: string | null; canManage: boolean } {
|
||||
const isServerAdmin = user.role === 'SERVER_ADMIN'
|
||||
const isTenantAdmin = user.role === 'TENANT_ADMIN'
|
||||
if (isServerAdmin) {
|
||||
// SERVER_ADMIN: nimmt den angefragten Mandanten (Einsatz), sonst den eigenen (falls vorhanden)
|
||||
return { tenantId: requestedTenantId || user.tenantId || null, canManage: true }
|
||||
}
|
||||
// Alle anderen: immer nur der eigene Mandant. Admin dieses Mandanten darf verwalten.
|
||||
return { tenantId: user.tenantId || null, canManage: isTenantAdmin && !!user.tenantId }
|
||||
}
|
||||
|
||||
/** Cockpit-Modul-Konfiguration lesen (Mandant = eigener oder, für SERVER_ADMIN, der des Einsatzes) */
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const user = await getSession()
|
||||
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
||||
|
||||
if (!user.tenantId) {
|
||||
// SERVER_ADMIN ohne Mandant: Standard-Cockpit
|
||||
const requested = request.nextUrl.searchParams.get('tenantId')
|
||||
const { tenantId, canManage } = resolveTenant(user, requested)
|
||||
|
||||
if (!tenantId) {
|
||||
// Kein Mandant auflösbar: Standard-Cockpit, nicht verwaltbar
|
||||
return NextResponse.json({ modules: DEFAULT_MODULES, canManage: false })
|
||||
}
|
||||
|
||||
const tenant = await (prisma as any).tenant.findUnique({
|
||||
where: { id: user.tenantId },
|
||||
where: { id: tenantId },
|
||||
select: { modulesConfig: true },
|
||||
})
|
||||
|
||||
const canManage = user.role === 'SERVER_ADMIN' || user.role === 'TENANT_ADMIN'
|
||||
return NextResponse.json({
|
||||
modules: normalizeModules(tenant?.modulesConfig),
|
||||
canManage,
|
||||
tenantId,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching module config:', error)
|
||||
@@ -30,22 +49,23 @@ export async function GET() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Cockpit-Modul-Konfiguration speichern (nur Admins) */
|
||||
/** Cockpit-Modul-Konfiguration speichern (nur Admins des betroffenen Mandanten) */
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const user = await getSession()
|
||||
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
||||
if (!user.tenantId) return NextResponse.json({ error: 'Kein Mandant zugeordnet' }, { status: 400 })
|
||||
if (user.role !== 'SERVER_ADMIN' && user.role !== 'TENANT_ADMIN') {
|
||||
return NextResponse.json({ error: 'Keine Berechtigung' }, { status: 403 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const { tenantId, canManage } = resolveTenant(user, body?.tenantId || null)
|
||||
|
||||
if (!tenantId) return NextResponse.json({ error: 'Kein Mandant zugeordnet' }, { status: 400 })
|
||||
if (!canManage) return NextResponse.json({ error: 'Keine Berechtigung' }, { status: 403 })
|
||||
|
||||
// normalizeModules verwirft Unbekanntes und stellt eingebaute Module sicher
|
||||
const modules = normalizeModules(body?.modules)
|
||||
|
||||
await (prisma as any).tenant.update({
|
||||
where: { id: user.tenantId },
|
||||
where: { id: tenantId },
|
||||
data: { modulesConfig: modules },
|
||||
})
|
||||
|
||||
|
||||
@@ -1165,6 +1165,7 @@ export default function AppPage() {
|
||||
einsatzNr={(currentProject as any)?.einsatzNr || ''}
|
||||
canEdit={canEditJournal}
|
||||
tenantId={tenant?.id || null}
|
||||
projectTenantId={(currentProject as any)?.tenantId ?? null}
|
||||
tenantName={tenant?.name || ''}
|
||||
tenantLogoUrl={tenant?.id ? `/api/admin/tenants/${tenant.id}/logo/serve` : null}
|
||||
mapRef={mapRef}
|
||||
|
||||
@@ -26,6 +26,8 @@ interface JournalViewProps {
|
||||
journalfuehrer: string
|
||||
canEdit: boolean
|
||||
tenantId?: string | null
|
||||
/** Mandant des OFFENEN Einsatzes (kann von dem des Nutzers abweichen — z.B. SERVER_ADMIN). */
|
||||
projectTenantId?: string | null
|
||||
einsatzNr?: string
|
||||
tenantName?: string
|
||||
tenantLogoUrl?: string | null
|
||||
@@ -67,7 +69,7 @@ function ModuleCard({ icon, name, count, children }: {
|
||||
)
|
||||
}
|
||||
|
||||
export function JournalView({ projectId, projectTitle, projectLocation, mode, einsatzleiter, journalfuehrer, canEdit, tenantId, einsatzNr, tenantName, tenantLogoUrl, mapRef, mapScreenshot: preCapuredScreenshot }: JournalViewProps) {
|
||||
export function JournalView({ projectId, projectTitle, projectLocation, mode, einsatzleiter, journalfuehrer, canEdit, tenantId, projectTenantId, einsatzNr, tenantName, tenantLogoUrl, mapRef, mapScreenshot: preCapuredScreenshot }: JournalViewProps) {
|
||||
const isUebung = mode === 'UEBUNG'
|
||||
const [entries, setEntries] = useState<JournalEntry[]>([])
|
||||
const [checkItems, setCheckItems] = useState<JournalCheckItem[]>([])
|
||||
@@ -145,16 +147,18 @@ export function JournalView({ projectId, projectTitle, projectLocation, mode, ei
|
||||
}
|
||||
}, [projectId])
|
||||
|
||||
// Cockpit-Modul-Konfiguration laden
|
||||
// Cockpit-Modul-Konfiguration laden — Mandant des OFFENEN Einsatzes (Fallback: eigener)
|
||||
const modulesTenantId = projectTenantId || tenantId || null
|
||||
useEffect(() => {
|
||||
fetch('/api/tenant/modules')
|
||||
const url = modulesTenantId ? `/api/tenant/modules?tenantId=${encodeURIComponent(modulesTenantId)}` : '/api/tenant/modules'
|
||||
fetch(url)
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(data => {
|
||||
if (data?.modules) setModules(data.modules)
|
||||
if (data) setCanManageModules(!!data.canManage)
|
||||
})
|
||||
.catch(() => {})
|
||||
}, [tenantId])
|
||||
}, [modulesTenantId])
|
||||
|
||||
// Init check items from templates if none exist (guarded against double-call)
|
||||
const initCheckItems = useCallback(async () => {
|
||||
@@ -847,6 +851,7 @@ export function JournalView({ projectId, projectTitle, projectLocation, mode, ei
|
||||
open={showModuleManager}
|
||||
onOpenChange={setShowModuleManager}
|
||||
modules={modules}
|
||||
tenantId={modulesTenantId}
|
||||
onSaved={setModules}
|
||||
/>
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ interface ModuleManagerDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
modules: CockpitModule[]
|
||||
/** Mandant, dessen Module gespeichert werden (Einsatz-Mandant; für SERVER_ADMIN relevant). */
|
||||
tenantId?: string | null
|
||||
onSaved: (modules: CockpitModule[]) => void
|
||||
}
|
||||
|
||||
@@ -25,7 +27,7 @@ interface ModuleManagerDialogProps {
|
||||
* aus Vorlagen erstellen (Checkliste, Atemschutz, Kräfte, Lagemeldungen, leer).
|
||||
* Gespeichert wird pro Mandant (gilt für alle Einsätze).
|
||||
*/
|
||||
export function ModuleManagerDialog({ open, onOpenChange, modules, onSaved }: ModuleManagerDialogProps) {
|
||||
export function ModuleManagerDialog({ open, onOpenChange, modules, tenantId, onSaved }: ModuleManagerDialogProps) {
|
||||
const [working, setWorking] = useState<CockpitModule[]>(modules)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
@@ -133,7 +135,7 @@ export function ModuleManagerDialog({ open, onOpenChange, modules, onSaved }: Mo
|
||||
const res = await fetch('/api/tenant/modules', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ modules: working }),
|
||||
body: JSON.stringify({ modules: working, tenantId: tenantId || undefined }),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw new Error(data.error || 'Speichern fehlgeschlagen')
|
||||
|
||||
Reference in New Issue
Block a user