'use client' import { useState, useEffect, useCallback } from 'react' import { Button } from '@/components/ui/button' import { X, ChevronRight, ChevronLeft, SkipForward, MapPin, Pencil, LayoutGrid, Save, Ruler, Users, Keyboard, Rocket, } from 'lucide-react' const TOUR_STORAGE_KEY = 'lageplan-onboarding-completed' interface TourStep { title: string description: string icon?: React.ReactNode targetSelector?: string position?: 'top' | 'bottom' | 'left' | 'right' } const TOUR_STEPS: TourStep[] = [ { title: 'Willkommen bei Lageplan!', icon: , description: 'Lageplan ist deine taktische Lageskizzen-App für den Feuerwehr-Einsatz. Diese kurze Tour zeigt dir die wichtigsten Funktionen. Du kannst sie jederzeit überspringen oder später im Benutzermenü erneut starten.', }, { title: 'Einsatz erstellen', icon: , description: 'Erstelle über «Neuer Einsatz» ein neues Projekt. Gib eine Adresse ein — die Karte fliegt automatisch dorthin. Jeder Einsatz wird separat gespeichert und kann als PDF oder PNG exportiert werden.', targetSelector: '[data-tour="new-project"]', position: 'bottom', }, { title: 'Zeichenwerkzeuge', icon: , description: 'Die Werkzeugleiste links enthält alle Zeichentools: Punkte, Linien, Polygone, Freihand, Pfeile, Text, Radiergummi und mehr. Jedes Tool hat ein Tastenkürzel — drücke «?» für eine Übersicht.', targetSelector: '[data-tour="toolbar"]', position: 'right', }, { title: 'Symbole & Sidebar', icon: , description: 'Rechts findest du über 100 taktische Feuerwehr-Symbole, sortiert nach Kategorien (Wasser, Feuer, Fahrzeuge usw.). Ziehe sie per Drag & Drop auf die Karte. Wechsle zwischen Symbolen und dem Einsatz-Journal.', targetSelector: '[data-tour="sidebar"]', position: 'left', }, { title: 'Speichern & Export', icon: , description: 'Speichere deinen Einsatz mit Ctrl+S oder dem Speichern-Button. Exportiere als PNG (Bild) oder als druckfertiges PDF. Die letzte Kartenansicht wird automatisch gespeichert.', targetSelector: '[data-tour="save"]', position: 'bottom', }, { title: 'Messen & Schlauch-Rechner', icon: , description: 'Mit dem Messwerkzeug (Taste «M») misst du Distanzen direkt auf der Karte. Der Schlauch-Rechner im Admin-Bereich berechnet die benötigten Schlauchlängen und -typen für deinen Einsatz.', }, { title: 'Live-Zusammenarbeit', icon: , description: 'Mehrere Benutzer können gleichzeitig am selben Einsatz arbeiten. Änderungen werden in Echtzeit synchronisiert — ideal für die Einsatzleitung mit mehreren Operateuren.', }, { title: 'Tastenkürzel (CH)', icon: , description: 'Optimiert für Schweizer Tastaturen: Ctrl+Y = Rückgängig, Ctrl+Z = Wiederholen, Del = Löschen, Ctrl+S = Speichern. Drücke «?» oder F1 für die komplette Übersicht aller Kürzel.', }, { title: 'Bereit für den Einsatz!', icon: , description: 'Du bist startklar! Diese Tour kannst du jederzeit über dein Benutzermenü (oben rechts → «Tour starten») erneut aufrufen. Viel Erfolg im Einsatz — Feuer frei!', }, ] interface OnboardingTourProps { forceShow?: boolean onComplete?: () => void } export function OnboardingTour({ forceShow = false, onComplete }: OnboardingTourProps) { const [isVisible, setIsVisible] = useState(false) const [currentStep, setCurrentStep] = useState(0) const [highlightRect, setHighlightRect] = useState(null) useEffect(() => { if (forceShow) { setIsVisible(true) setCurrentStep(0) return } const completed = localStorage.getItem(TOUR_STORAGE_KEY) if (!completed) { // Small delay so the app renders first const timer = setTimeout(() => setIsVisible(true), 1500) return () => clearTimeout(timer) } }, [forceShow]) const updateHighlight = useCallback(() => { const step = TOUR_STEPS[currentStep] if (step.targetSelector) { const el = document.querySelector(step.targetSelector) if (el) { setHighlightRect(el.getBoundingClientRect()) return } } setHighlightRect(null) }, [currentStep]) useEffect(() => { if (!isVisible) return updateHighlight() window.addEventListener('resize', updateHighlight) return () => window.removeEventListener('resize', updateHighlight) }, [isVisible, currentStep, updateHighlight]) const completeTour = useCallback(() => { localStorage.setItem(TOUR_STORAGE_KEY, 'true') setIsVisible(false) onComplete?.() }, [onComplete]) const nextStep = () => { if (currentStep < TOUR_STEPS.length - 1) { setCurrentStep(currentStep + 1) } else { completeTour() } } const prevStep = () => { if (currentStep > 0) setCurrentStep(currentStep - 1) } if (!isVisible) return null const step = TOUR_STEPS[currentStep] const isFirst = currentStep === 0 const isLast = currentStep === TOUR_STEPS.length - 1 // Calculate tooltip position based on highlight const getTooltipStyle = (): React.CSSProperties => { if (!highlightRect) { // Center on screen return { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', } } const pos = step.position || 'bottom' const gap = 12 switch (pos) { case 'bottom': return { position: 'fixed', top: highlightRect.bottom + gap, left: Math.max(16, Math.min(highlightRect.left, window.innerWidth - 360)), } case 'top': return { position: 'fixed', bottom: window.innerHeight - highlightRect.top + gap, left: Math.max(16, Math.min(highlightRect.left, window.innerWidth - 360)), } case 'right': return { position: 'fixed', top: Math.max(16, highlightRect.top), left: highlightRect.right + gap, } case 'left': return { position: 'fixed', top: Math.max(16, highlightRect.top), right: window.innerWidth - highlightRect.left + gap, } default: return { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)' } } } return ( <> {/* Backdrop overlay */}
{/* Highlight cutout */} {highlightRect && (
)} {/* Tooltip card */}
{step.icon}

{step.title}

{step.description}

{/* Progress dots */}
{TOUR_STEPS.map((_, i) => (
))}
{!isFirst && ( )} {isFirst && ( )}
) } /** Reset the onboarding tour so it shows again next time */ export function resetOnboardingTour() { localStorage.removeItem(TOUR_STORAGE_KEY) }