feat: Präsentationsmodus (Schloss-Button) + Version 1.3.5
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 16m59s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 16m59s
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lageplan",
|
"name": "lageplan",
|
||||||
"version": "1.3.4",
|
"version": "1.3.5",
|
||||||
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export default function AppPage() {
|
|||||||
const [isFullscreen, setIsFullscreen] = useState(false)
|
const [isFullscreen, setIsFullscreen] = useState(false)
|
||||||
const [auditLog, setAuditLog] = useState<{ time: string; action: string }[]>([])
|
const [auditLog, setAuditLog] = useState<{ time: string; action: string }[]>([])
|
||||||
const [isAuditOpen, setIsAuditOpen] = useState(false)
|
const [isAuditOpen, setIsAuditOpen] = useState(false)
|
||||||
|
const [presentationLocked, setPresentationLocked] = useState(false)
|
||||||
|
|
||||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false)
|
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false)
|
||||||
const [lastMapScreenshot, setLastMapScreenshot] = useState<string>('')
|
const [lastMapScreenshot, setLastMapScreenshot] = useState<string>('')
|
||||||
@@ -375,7 +376,7 @@ export default function AppPage() {
|
|||||||
|
|
||||||
const roleCanEdit = user ? (user.role === 'SERVER_ADMIN' || user.role === 'TENANT_ADMIN' || user.role === 'OPERATOR') : false
|
const roleCanEdit = user ? (user.role === 'SERVER_ADMIN' || user.role === 'TENANT_ADMIN' || user.role === 'OPERATOR') : false
|
||||||
// User can only edit if they have the role AND they hold the editing lock (or no one is editing)
|
// User can only edit if they have the role AND they hold the editing lock (or no one is editing)
|
||||||
const canEdit = roleCanEdit && (isEditingByMe || !editingBy)
|
const canEdit = !presentationLocked && roleCanEdit && (isEditingByMe || !editingBy)
|
||||||
const isReadOnly = !!editingBy && !isEditingByMe
|
const isReadOnly = !!editingBy && !isEditingByMe
|
||||||
|
|
||||||
// Auto-save: localStorage persistence + debounced API save + beacon on unload
|
// Auto-save: localStorage persistence + debounced API save + beacon on unload
|
||||||
@@ -806,6 +807,14 @@ export default function AppPage() {
|
|||||||
userRole={user?.role}
|
userRole={user?.role}
|
||||||
onLogout={logout}
|
onLogout={logout}
|
||||||
onStartTour={() => { resetOnboardingTour(); setShowTour(true) }}
|
onStartTour={() => { resetOnboardingTour(); setShowTour(true) }}
|
||||||
|
presentationLocked={presentationLocked}
|
||||||
|
onTogglePresentationLock={() => {
|
||||||
|
const next = !presentationLocked
|
||||||
|
setPresentationLocked(next)
|
||||||
|
if (next && isEditingByMe) {
|
||||||
|
handleStopEditing()
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Offline banner */}
|
{/* Offline banner */}
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ import {
|
|||||||
Shield,
|
Shield,
|
||||||
MapPin,
|
MapPin,
|
||||||
HelpCircle,
|
HelpCircle,
|
||||||
|
Lock,
|
||||||
|
Unlock,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { HoseSettingsDialog } from '@/components/dialogs/hose-settings-dialog'
|
import { HoseSettingsDialog } from '@/components/dialogs/hose-settings-dialog'
|
||||||
import type { Project, DrawFeature } from '@/types'
|
import type { Project, DrawFeature } from '@/types'
|
||||||
@@ -66,6 +68,8 @@ interface TopbarProps {
|
|||||||
userRole?: string
|
userRole?: string
|
||||||
onLogout?: () => void
|
onLogout?: () => void
|
||||||
onStartTour?: () => void
|
onStartTour?: () => void
|
||||||
|
presentationLocked?: boolean
|
||||||
|
onTogglePresentationLock?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Topbar({
|
export function Topbar({
|
||||||
@@ -89,6 +93,8 @@ export function Topbar({
|
|||||||
userRole,
|
userRole,
|
||||||
onLogout,
|
onLogout,
|
||||||
onStartTour,
|
onStartTour,
|
||||||
|
presentationLocked,
|
||||||
|
onTogglePresentationLock,
|
||||||
}: TopbarProps) {
|
}: TopbarProps) {
|
||||||
const [isLoadDialogOpen, setIsLoadDialogOpen] = useState(false)
|
const [isLoadDialogOpen, setIsLoadDialogOpen] = useState(false)
|
||||||
const [isHoseSettingsOpen, setIsHoseSettingsOpen] = useState(false)
|
const [isHoseSettingsOpen, setIsHoseSettingsOpen] = useState(false)
|
||||||
@@ -172,6 +178,16 @@ export function Topbar({
|
|||||||
<span className="hidden lg:inline">{isSaving ? 'Speichern...' : 'Speichern'}</span>
|
<span className="hidden lg:inline">{isSaving ? 'Speichern...' : 'Speichern'}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant={presentationLocked ? 'default' : 'outline'}
|
||||||
|
className={`h-9 md:h-10 px-2 md:px-3 text-sm ${presentationLocked ? 'bg-amber-600 hover:bg-amber-700 text-white border-amber-600' : ''}`}
|
||||||
|
onClick={onTogglePresentationLock}
|
||||||
|
title={presentationLocked ? 'Präsentationsmodus deaktivieren' : 'Präsentationsmodus aktivieren'}
|
||||||
|
>
|
||||||
|
{presentationLocked ? <Lock className="w-5 h-5 md:mr-1" /> : <Unlock className="w-5 h-5 md:mr-1" />}
|
||||||
|
<span className="hidden lg:inline">{presentationLocked ? 'Gesperrt' : 'Frei'}</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="outline" className="h-9 md:h-10 px-2 md:px-3 text-sm" title="Menü">
|
<Button variant="outline" className="h-9 md:h-10 px-2 md:px-3 text-sm" title="Menü">
|
||||||
|
|||||||
Reference in New Issue
Block a user