fix(map+edit): erster Zeichen-Punkt sofort sichtbar + Bearbeiten-Status klarer (v1.6.8)

- Zeichnen: gesetzte Eckpunkte (Linie/Polygon/Pfeil/Gefahrenzone) erscheinen
  sofort als Marker beim Klick — bisher sah man auf dem Handy erst ab dem
  zweiten Punkt etwas (Vorschau nur bei Mausbewegung)
- Gemeinsames Bearbeiten: alle drei Zustände explizit in der Statuszeile —
  "Ansichtsmodus frei · Bearbeiten übernehmen", "Sie bearbeiten · freigeben",
  "X bearbeitet · nur Ansicht"

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pepe Ziberi
2026-07-22 23:23:09 +02:00
parent 471690550f
commit eb5da2f0c3
3 changed files with 63 additions and 28 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "lageplan",
"version": "1.6.7",
"version": "1.6.8",
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
"private": true,
"scripts": {

View File

@@ -19,7 +19,7 @@ import { useAuth } from '@/components/providers/auth-provider'
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
import { JournalView } from '@/components/journal/journal-view'
import { Lock, Eye, AlertTriangle, WifiOff, GraduationCap, Map as MapIcon, ClipboardList, LayoutGrid } from 'lucide-react'
import { Lock, Eye, Pencil, AlertTriangle, WifiOff, GraduationCap, Map as MapIcon, ClipboardList, LayoutGrid } from 'lucide-react'
import { CustomDragLayer } from '@/components/map/custom-drag-layer'
import { OnboardingTour, resetOnboardingTour } from '@/components/onboarding/onboarding-tour'
import { useKeyboardShortcuts } from '@/hooks/use-keyboard-shortcuts'
@@ -1038,7 +1038,7 @@ export default function AppPage() {
{/* Kompakte Status-Zeile — eine Zeile mit Chips statt bis zu vier gestapelten Bannern.
Zeigt nur, was gerade zutrifft; im Normalfall (Einsatz, online, verifiziert,
niemand fremd am Zeichnen) erscheint sie gar nicht. */}
{(currentProject?.mode === 'UEBUNG' || isOffline || isReadOnly || isEditingByMe || (user && user.emailVerified === false)) && (
{(currentProject?.mode === 'UEBUNG' || isOffline || isReadOnly || isEditingByMe || (currentProject && roleCanEdit && !presentationLocked) || (user && user.emailVerified === false)) && (
<div data-tour="edit-toggle" className="flex flex-wrap items-center gap-1.5 px-3 py-1 border-b bg-muted/30 text-xs">
{currentProject?.mode === 'UEBUNG' && (
<span className="inline-flex items-center gap-1 rounded-full bg-blue-600 text-white font-semibold px-2 py-0.5">
@@ -1052,17 +1052,27 @@ export default function AppPage() {
)}
{isReadOnly && (
<span className="inline-flex items-center gap-1 rounded-full bg-orange-100 text-orange-800 dark:bg-orange-950/50 dark:text-orange-300 font-medium px-2 py-0.5">
<Eye className="w-3.5 h-3.5" /> <strong className="font-semibold">{editingBy?.name}</strong> bearbeitet · Live-Ansicht
<Lock className="w-3.5 h-3.5" /> <strong className="font-semibold">{editingBy?.name}</strong> bearbeitet gerade · Sie sehen live zu (nur Ansicht)
</span>
)}
{isEditingByMe && (
<span className="inline-flex items-center gap-1 rounded-full bg-green-100 text-green-800 dark:bg-green-950/50 dark:text-green-300 font-medium px-2 py-0.5">
<Lock className="w-3.5 h-3.5" /> Sie bearbeiten
<button onClick={handleStopEditing} disabled={editingLoading} className="ml-1 underline hover:no-underline disabled:opacity-50" title="Karte für andere freigeben (speichert)">
<Pencil className="w-3.5 h-3.5" /> Sie bearbeiten andere sehen nur zu
<button onClick={handleStopEditing} disabled={editingLoading} className="ml-1.5 font-semibold underline hover:no-underline disabled:opacity-50" title="Karte speichern & für andere freigeben">
freigeben
</button>
</span>
)}
{/* Karte ist frei und ich dürfte bearbeiten — expliziter Hinweis + Übernehmen,
damit klar ist, dass man gerade nur zuschaut, bis man selbst startet. */}
{currentProject && roleCanEdit && !isReadOnly && !isEditingByMe && !presentationLocked && (
<span className="inline-flex items-center gap-1 rounded-full bg-muted text-muted-foreground font-medium px-2 py-0.5">
<Eye className="w-3.5 h-3.5" /> Ansichtsmodus frei
<button onClick={ensureEditing} disabled={editingLoading} className="ml-1.5 font-semibold text-foreground underline hover:no-underline disabled:opacity-50" title="Bearbeitung übernehmen — sperrt die Karte für andere">
Bearbeiten übernehmen
</button>
</span>
)}
{user && user.emailVerified === false && (
<span className="inline-flex items-center gap-1 rounded-full bg-amber-100 text-amber-800 dark:bg-amber-950/50 dark:text-amber-300 font-medium px-2 py-0.5">
<AlertTriangle className="w-3.5 h-3.5" /> E-Mail unbestätigt

View File

@@ -943,6 +943,21 @@ export function MapView({
},
})
// Preview vertex points — jeder gesetzte Eckpunkt bekommt sofort einen sichtbaren
// Marker (sonst sieht man beim ersten Klick nichts, v.a. auf dem Handy ohne Hover)
m.addLayer({
id: 'draw-preview-points',
type: 'circle',
source: 'draw-preview',
filter: ['==', ['geometry-type'], 'Point'],
paint: {
'circle-radius': ['coalesce', ['get', 'r'], 5],
'circle-color': ['coalesce', ['get', 'fill'], '#ffffff'],
'circle-stroke-color': ['coalesce', ['get', 'color'], '#2563eb'],
'circle-stroke-width': 2,
},
})
// --- Helper: update preview source ---
function setPreview(data: any) {
const mapRef = map.current
@@ -953,6 +968,34 @@ export function MapView({
function clearPreviewData() {
setPreview({ type: 'FeatureCollection', features: [] })
}
// Baut die Live-Vorschau für mehrpunkt-Werkzeuge (Linie/Polygon/Pfeil/Gefahrenzone):
// gesetzte Eckpunkte als Marker + optionale Gummiband-Linie/Fläche zum Cursor.
function buildVertexPreview(mode: string, verts: [number, number][], hover?: [number, number]) {
if (verts.length === 0) return { type: 'FeatureCollection', features: [] as any[] }
const previewColor = mode === 'dangerzone' ? '#dc2626' : selectedColorRef.current
const feats: any[] = []
const isArea = mode === 'polygon' || mode === 'dangerzone'
// Verbindungsgeometrie (nur wenn mind. 1 Punkt gesetzt)
if (hover) {
if (isArea) {
const ring = [...verts, hover, verts[0]]
feats.push({ type: 'Feature', geometry: { type: 'Polygon', coordinates: [ring] }, properties: { color: previewColor } })
} else {
feats.push({ type: 'Feature', geometry: { type: 'LineString', coordinates: [...verts, hover] }, properties: { color: previewColor } })
}
} else if (verts.length >= 2) {
feats.push({ type: 'Feature', geometry: { type: 'LineString', coordinates: verts }, properties: { color: previewColor } })
}
// Eckpunkt-Marker — der erste etwas grösser/gefüllt, damit der Start klar ist
verts.forEach((v, i) => {
feats.push({
type: 'Feature',
geometry: { type: 'Point', coordinates: v },
properties: { color: previewColor, fill: i === 0 ? previewColor : '#ffffff', r: i === 0 ? 6 : 5 },
})
})
return { type: 'FeatureCollection', features: feats }
}
// --- ALL drawing event handlers registered directly in map load ---
m.on('click', (e: maplibregl.MapMouseEvent) => {
@@ -1102,6 +1145,8 @@ export function MapView({
drawingRef.current.currentCoords.push(coords)
setDrawingPointCount(drawingRef.current.currentCoords.length)
}
// Sofort-Feedback: gesetzte Eckpunkte anzeigen, auch ohne Mausbewegung (Touch!)
setPreview(buildVertexPreview(mode, drawingRef.current.currentCoords as [number, number][]))
return
}
@@ -1255,29 +1300,9 @@ export function MapView({
if (!drawingRef.current.isDrawing && !drawingRef.current.startPoint) return
if (mode === 'linestring' || mode === 'arrow') {
if (mode === 'linestring' || mode === 'arrow' || mode === 'polygon' || mode === 'dangerzone') {
if (drawingRef.current.currentCoords.length > 0) {
setPreview({
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: { type: 'LineString', coordinates: [...drawingRef.current.currentCoords, coords] },
properties: { color },
}],
})
}
} else if (mode === 'polygon' || mode === 'dangerzone') {
if (drawingRef.current.currentCoords.length > 0) {
const pc = [...drawingRef.current.currentCoords, coords, drawingRef.current.currentCoords[0]]
const previewColor = mode === 'dangerzone' ? '#dc2626' : color
setPreview({
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: { type: 'Polygon', coordinates: [pc] },
properties: { color: previewColor },
}],
})
setPreview(buildVertexPreview(mode, drawingRef.current.currentCoords as [number, number][], coords))
}
} else if (mode === 'rectangle' && drawingRef.current.startPoint) {
const s = drawingRef.current.startPoint