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",
|
"name": "lageplan",
|
||||||
"version": "1.8.5",
|
"version": "1.8.6",
|
||||||
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -3,26 +3,45 @@ import { prisma } from '@/lib/db'
|
|||||||
import { getSession } from '@/lib/auth'
|
import { getSession } from '@/lib/auth'
|
||||||
import { normalizeModules, DEFAULT_MODULES } from '@/lib/modules'
|
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 {
|
try {
|
||||||
const user = await getSession()
|
const user = await getSession()
|
||||||
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
||||||
|
|
||||||
if (!user.tenantId) {
|
const requested = request.nextUrl.searchParams.get('tenantId')
|
||||||
// SERVER_ADMIN ohne Mandant: Standard-Cockpit
|
const { tenantId, canManage } = resolveTenant(user, requested)
|
||||||
|
|
||||||
|
if (!tenantId) {
|
||||||
|
// Kein Mandant auflösbar: Standard-Cockpit, nicht verwaltbar
|
||||||
return NextResponse.json({ modules: DEFAULT_MODULES, canManage: false })
|
return NextResponse.json({ modules: DEFAULT_MODULES, canManage: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
const tenant = await (prisma as any).tenant.findUnique({
|
const tenant = await (prisma as any).tenant.findUnique({
|
||||||
where: { id: user.tenantId },
|
where: { id: tenantId },
|
||||||
select: { modulesConfig: true },
|
select: { modulesConfig: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
const canManage = user.role === 'SERVER_ADMIN' || user.role === 'TENANT_ADMIN'
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
modules: normalizeModules(tenant?.modulesConfig),
|
modules: normalizeModules(tenant?.modulesConfig),
|
||||||
canManage,
|
canManage,
|
||||||
|
tenantId,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching module config:', 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) {
|
export async function PUT(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const user = await getSession()
|
const user = await getSession()
|
||||||
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
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 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
|
// normalizeModules verwirft Unbekanntes und stellt eingebaute Module sicher
|
||||||
const modules = normalizeModules(body?.modules)
|
const modules = normalizeModules(body?.modules)
|
||||||
|
|
||||||
await (prisma as any).tenant.update({
|
await (prisma as any).tenant.update({
|
||||||
where: { id: user.tenantId },
|
where: { id: tenantId },
|
||||||
data: { modulesConfig: modules },
|
data: { modulesConfig: modules },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1165,6 +1165,7 @@ export default function AppPage() {
|
|||||||
einsatzNr={(currentProject as any)?.einsatzNr || ''}
|
einsatzNr={(currentProject as any)?.einsatzNr || ''}
|
||||||
canEdit={canEditJournal}
|
canEdit={canEditJournal}
|
||||||
tenantId={tenant?.id || null}
|
tenantId={tenant?.id || null}
|
||||||
|
projectTenantId={(currentProject as any)?.tenantId ?? null}
|
||||||
tenantName={tenant?.name || ''}
|
tenantName={tenant?.name || ''}
|
||||||
tenantLogoUrl={tenant?.id ? `/api/admin/tenants/${tenant.id}/logo/serve` : null}
|
tenantLogoUrl={tenant?.id ? `/api/admin/tenants/${tenant.id}/logo/serve` : null}
|
||||||
mapRef={mapRef}
|
mapRef={mapRef}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ interface JournalViewProps {
|
|||||||
journalfuehrer: string
|
journalfuehrer: string
|
||||||
canEdit: boolean
|
canEdit: boolean
|
||||||
tenantId?: string | null
|
tenantId?: string | null
|
||||||
|
/** Mandant des OFFENEN Einsatzes (kann von dem des Nutzers abweichen — z.B. SERVER_ADMIN). */
|
||||||
|
projectTenantId?: string | null
|
||||||
einsatzNr?: string
|
einsatzNr?: string
|
||||||
tenantName?: string
|
tenantName?: string
|
||||||
tenantLogoUrl?: string | null
|
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 isUebung = mode === 'UEBUNG'
|
||||||
const [entries, setEntries] = useState<JournalEntry[]>([])
|
const [entries, setEntries] = useState<JournalEntry[]>([])
|
||||||
const [checkItems, setCheckItems] = useState<JournalCheckItem[]>([])
|
const [checkItems, setCheckItems] = useState<JournalCheckItem[]>([])
|
||||||
@@ -145,16 +147,18 @@ export function JournalView({ projectId, projectTitle, projectLocation, mode, ei
|
|||||||
}
|
}
|
||||||
}, [projectId])
|
}, [projectId])
|
||||||
|
|
||||||
// Cockpit-Modul-Konfiguration laden
|
// Cockpit-Modul-Konfiguration laden — Mandant des OFFENEN Einsatzes (Fallback: eigener)
|
||||||
|
const modulesTenantId = projectTenantId || tenantId || null
|
||||||
useEffect(() => {
|
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(r => r.ok ? r.json() : null)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data?.modules) setModules(data.modules)
|
if (data?.modules) setModules(data.modules)
|
||||||
if (data) setCanManageModules(!!data.canManage)
|
if (data) setCanManageModules(!!data.canManage)
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
}, [tenantId])
|
}, [modulesTenantId])
|
||||||
|
|
||||||
// Init check items from templates if none exist (guarded against double-call)
|
// Init check items from templates if none exist (guarded against double-call)
|
||||||
const initCheckItems = useCallback(async () => {
|
const initCheckItems = useCallback(async () => {
|
||||||
@@ -847,6 +851,7 @@ export function JournalView({ projectId, projectTitle, projectLocation, mode, ei
|
|||||||
open={showModuleManager}
|
open={showModuleManager}
|
||||||
onOpenChange={setShowModuleManager}
|
onOpenChange={setShowModuleManager}
|
||||||
modules={modules}
|
modules={modules}
|
||||||
|
tenantId={modulesTenantId}
|
||||||
onSaved={setModules}
|
onSaved={setModules}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ interface ModuleManagerDialogProps {
|
|||||||
open: boolean
|
open: boolean
|
||||||
onOpenChange: (open: boolean) => void
|
onOpenChange: (open: boolean) => void
|
||||||
modules: CockpitModule[]
|
modules: CockpitModule[]
|
||||||
|
/** Mandant, dessen Module gespeichert werden (Einsatz-Mandant; für SERVER_ADMIN relevant). */
|
||||||
|
tenantId?: string | null
|
||||||
onSaved: (modules: CockpitModule[]) => void
|
onSaved: (modules: CockpitModule[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ interface ModuleManagerDialogProps {
|
|||||||
* aus Vorlagen erstellen (Checkliste, Atemschutz, Kräfte, Lagemeldungen, leer).
|
* aus Vorlagen erstellen (Checkliste, Atemschutz, Kräfte, Lagemeldungen, leer).
|
||||||
* Gespeichert wird pro Mandant (gilt für alle Einsätze).
|
* 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 [working, setWorking] = useState<CockpitModule[]>(modules)
|
||||||
const [isSaving, setIsSaving] = useState(false)
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
@@ -133,7 +135,7 @@ export function ModuleManagerDialog({ open, onOpenChange, modules, onSaved }: Mo
|
|||||||
const res = await fetch('/api/tenant/modules', {
|
const res = await fetch('/api/tenant/modules', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ modules: working }),
|
body: JSON.stringify({ modules: working, tenantId: tenantId || undefined }),
|
||||||
})
|
})
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
if (!res.ok) throw new Error(data.error || 'Speichern fehlgeschlagen')
|
if (!res.ok) throw new Error(data.error || 'Speichern fehlgeschlagen')
|
||||||
|
|||||||
Reference in New Issue
Block a user