Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b96f1ffb1 | ||
|
|
0784553017 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lageplan",
|
"name": "lageplan",
|
||||||
"version": "1.0.6",
|
"version": "1.0.8",
|
||||||
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export async function POST(req: NextRequest) {
|
|||||||
const origin = req.headers.get('origin') || req.nextUrl.origin
|
const origin = req.headers.get('origin') || req.nextUrl.origin
|
||||||
|
|
||||||
const session = await stripe.checkout.sessions.create({
|
const session = await stripe.checkout.sessions.create({
|
||||||
payment_method_types: ['card', 'twint'],
|
payment_method_types: ['card', 'twint', 'link'],
|
||||||
mode: 'payment',
|
mode: 'payment',
|
||||||
line_items: [
|
line_items: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -526,10 +526,10 @@ function SupportSection() {
|
|||||||
{/* Tier preview */}
|
{/* Tier preview */}
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-8">
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-8">
|
||||||
{[
|
{[
|
||||||
{ emoji: '\u2615', label: 'Kaffee', amount: 'CHF 5' },
|
{ emoji: '\u2615', label: 'Kaffee', amount: 'CHF 10' },
|
||||||
{ emoji: '\uD83C\uDF55', label: 'Pizza', amount: 'CHF 10' },
|
{ emoji: '\uD83D\uDDA5\uFE0F', label: 'Server', amount: 'CHF 20' },
|
||||||
{ emoji: '\uD83D\uDDA5\uFE0F', label: 'Server', amount: 'CHF 25' },
|
|
||||||
{ emoji: '\uD83D\uDE80', label: 'Feature', amount: 'CHF 50' },
|
{ emoji: '\uD83D\uDE80', label: 'Feature', amount: 'CHF 50' },
|
||||||
|
{ emoji: '\u2764\uFE0F', label: 'Freibetrag', amount: 'Frei' },
|
||||||
].map(tier => (
|
].map(tier => (
|
||||||
<div key={tier.label} className="rounded-xl p-4 border border-gray-100 bg-gray-50">
|
<div key={tier.label} className="rounded-xl p-4 border border-gray-100 bg-gray-50">
|
||||||
<span className="text-2xl block mb-1">{tier.emoji}</span>
|
<span className="text-2xl block mb-1">{tier.emoji}</span>
|
||||||
@@ -540,7 +540,7 @@ function SupportSection() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-gray-500 mb-6">
|
<p className="text-gray-500 mb-6">
|
||||||
Wähle einen Betrag auf unserer Spendenseite — Zahlung sicher via Stripe (Kreditkarte, Twint).
|
Wähle einen Betrag auf unserer Spendenseite — Zahlung sicher via Stripe (Kreditkarte, Twint, Apple Pay, Google Pay).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Link href="/spenden">
|
<Link href="/spenden">
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default function RapportViewerPage({ params }: { params: Promise<{ token:
|
|||||||
{/* Action bar */}
|
{/* Action bar */}
|
||||||
<div className="max-w-[210mm] mx-auto mb-4 flex justify-between items-center px-4">
|
<div className="max-w-[210mm] mx-auto mb-4 flex justify-between items-center px-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<MapPin className="w-5 h-5 text-red-500" />
|
<img src="/logo.svg" alt="Lageplan" className="w-5 h-5 object-contain" />
|
||||||
<span className="font-semibold text-sm">Lageplan — Einsatzrapport</span>
|
<span className="font-semibold text-sm">Lageplan — Einsatzrapport</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -168,8 +168,7 @@ export default function RapportViewerPage({ params }: { params: Promise<{ token:
|
|||||||
<Field label="Alarmzeit" value={d.alarmzeit} mono />
|
<Field label="Alarmzeit" value={d.alarmzeit} mono />
|
||||||
<Field label="Priorität" value={d.prioritaet} last />
|
<Field label="Priorität" value={d.prioritaet} last />
|
||||||
<Field label="Einsatzort / Adresse" value={d.einsatzort} span={2} />
|
<Field label="Einsatzort / Adresse" value={d.einsatzort} span={2} />
|
||||||
<Field label="Koordinaten" value={d.koordinaten} mono />
|
<Field label="Objekt / Gebäude" value={d.objekt} span={2} last />
|
||||||
<Field label="Objekt / Gebäude" value={d.objekt} last />
|
|
||||||
<Field label="Alarmierungsart" value={d.alarmierungsart} span={2} />
|
<Field label="Alarmierungsart" value={d.alarmierungsart} span={2} />
|
||||||
<Field label="Stichwort / Meldebild" value={d.stichwort} span={2} last />
|
<Field label="Stichwort / Meldebild" value={d.stichwort} span={2} last />
|
||||||
</div>
|
</div>
|
||||||
@@ -177,15 +176,9 @@ export default function RapportViewerPage({ params }: { params: Promise<{ token:
|
|||||||
|
|
||||||
{/* 2. Zeitverlauf */}
|
{/* 2. Zeitverlauf */}
|
||||||
<Section num="2" title="Zeitverlauf">
|
<Section num="2" title="Zeitverlauf">
|
||||||
<div className="grid grid-cols-4 border rounded">
|
<div className="grid grid-cols-2 border rounded">
|
||||||
<Field label="Alarmierung" value={d.zeitAlarm} mono highlight />
|
<Field label="Alarmierung" value={d.zeitAlarm} mono highlight />
|
||||||
<Field label="Ausrücken" value={d.zeitAusruecken} mono highlight />
|
<Field label="Eintreffen" value={d.zeitEintreffen} mono highlight last />
|
||||||
<Field label="Eintreffen" value={d.zeitEintreffen} mono highlight />
|
|
||||||
<Field label="Einsatzbereit" value={d.zeitBereit} mono highlight last />
|
|
||||||
<Field label="Feuer unter Kontrolle" value={d.zeitKontrolle} mono highlight />
|
|
||||||
<Field label="Feuer aus" value={d.zeitAus} mono highlight />
|
|
||||||
<Field label="Einrücken" value={d.zeitEinruecken} mono highlight />
|
|
||||||
<Field label="Einsatzende" value={d.zeitEnde} mono highlight last />
|
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,15 @@ import {
|
|||||||
import { Logo } from '@/components/ui/logo'
|
import { Logo } from '@/components/ui/logo'
|
||||||
|
|
||||||
const tiers = [
|
const tiers = [
|
||||||
{ value: 5, label: 'Kaffee', emoji: '\u2615', desc: 'Ein Kaffee für die nächste Coding-Session' },
|
{ value: 10, label: 'Kaffee', emoji: '\u2615', desc: 'Ein Kaffee für die nächste Coding-Session' },
|
||||||
{ value: 10, label: 'Pizza', emoji: '\uD83C\uDF55', desc: 'Pizza-Abend nach einem langen Entwicklungstag' },
|
{ value: 20, label: 'Server', emoji: '\uD83D\uDDA5\uFE0F', desc: 'Hilft die monatlichen Serverkosten zu decken' },
|
||||||
{ value: 25, label: 'Server', emoji: '\uD83D\uDDA5\uFE0F', desc: 'Hilft die monatlichen Serverkosten zu decken' },
|
|
||||||
{ value: 50, label: 'Feature', emoji: '\uD83D\uDE80', desc: 'Finanziert die Entwicklung eines neuen Features' },
|
{ value: 50, label: 'Feature', emoji: '\uD83D\uDE80', desc: 'Finanziert die Entwicklung eines neuen Features' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export default function SpendenPage() {
|
export default function SpendenPage() {
|
||||||
const [amount, setAmount] = useState(10)
|
const [amount, setAmount] = useState(20)
|
||||||
|
const [customAmount, setCustomAmount] = useState('')
|
||||||
|
const [isCustom, setIsCustom] = useState(false)
|
||||||
const [name, setName] = useState('')
|
const [name, setName] = useState('')
|
||||||
const [message, setMessage] = useState('')
|
const [message, setMessage] = useState('')
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
@@ -112,9 +113,9 @@ export default function SpendenPage() {
|
|||||||
{tiers.map(tier => (
|
{tiers.map(tier => (
|
||||||
<button
|
<button
|
||||||
key={tier.value}
|
key={tier.value}
|
||||||
onClick={() => setAmount(tier.value)}
|
onClick={() => { setAmount(tier.value); setIsCustom(false); setCustomAmount('') }}
|
||||||
className={`rounded-xl p-4 text-center transition-all border-2 ${
|
className={`rounded-xl p-4 text-center transition-all border-2 ${
|
||||||
amount === tier.value
|
!isCustom && amount === tier.value
|
||||||
? 'border-red-500 bg-red-50 shadow-md'
|
? 'border-red-500 bg-red-50 shadow-md'
|
||||||
: 'border-gray-100 hover:border-gray-200 hover:bg-gray-50'
|
: 'border-gray-100 hover:border-gray-200 hover:bg-gray-50'
|
||||||
}`}
|
}`}
|
||||||
@@ -124,34 +125,52 @@ export default function SpendenPage() {
|
|||||||
<span className="text-xs text-gray-500 block mt-0.5">{tier.label}</span>
|
<span className="text-xs text-gray-500 block mt-0.5">{tier.label}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
<button
|
||||||
|
onClick={() => setIsCustom(true)}
|
||||||
|
className={`rounded-xl p-4 text-center transition-all border-2 ${
|
||||||
|
isCustom
|
||||||
|
? 'border-red-500 bg-red-50 shadow-md'
|
||||||
|
: 'border-gray-100 hover:border-gray-200 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="text-2xl block mb-1">\u2764\uFE0F</span>
|
||||||
|
<span className="text-lg font-bold text-gray-900">Frei</span>
|
||||||
|
<span className="text-xs text-gray-500 block mt-0.5">Eigener Betrag</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Slider */}
|
{/* Custom amount input */}
|
||||||
<div className="mb-6">
|
{isCustom && (
|
||||||
<div className="flex justify-between items-center mb-2">
|
<div className="mb-8">
|
||||||
<span className="text-sm text-gray-500">Oder wähle einen eigenen Betrag:</span>
|
<label className="block text-sm font-medium text-gray-700 mb-2">Eigener Betrag in CHF</label>
|
||||||
<span className="text-xl font-bold text-red-600">CHF {amount}</span>
|
<div className="relative">
|
||||||
|
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 font-medium">CHF</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="1000"
|
||||||
|
step="1"
|
||||||
|
value={customAmount}
|
||||||
|
onChange={e => {
|
||||||
|
setCustomAmount(e.target.value)
|
||||||
|
const val = parseInt(e.target.value)
|
||||||
|
if (val > 0) setAmount(val)
|
||||||
|
}}
|
||||||
|
placeholder="Betrag eingeben..."
|
||||||
|
className="w-full rounded-lg border border-gray-300 pl-14 pr-4 py-3 text-lg font-bold focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input
|
)}
|
||||||
type="range"
|
|
||||||
min="5"
|
|
||||||
max="200"
|
|
||||||
step="5"
|
|
||||||
value={amount}
|
|
||||||
onChange={e => setAmount(parseInt(e.target.value))}
|
|
||||||
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-red-600"
|
|
||||||
/>
|
|
||||||
<div className="flex justify-between text-xs text-gray-400 mt-1">
|
|
||||||
<span>CHF 5</span>
|
|
||||||
<span>CHF 200</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Current tier description */}
|
{/* Current tier description */}
|
||||||
<div className="bg-gray-50 rounded-xl p-4 mb-6 text-center">
|
{!isCustom && (
|
||||||
<span className="text-3xl">{activeTier.emoji}</span>
|
<div className="bg-gray-50 rounded-xl p-4 mb-6 text-center">
|
||||||
<p className="text-sm text-gray-600 mt-2">{activeTier.desc}</p>
|
<span className="text-3xl">{activeTier.emoji}</span>
|
||||||
</div>
|
<p className="text-sm text-gray-600 mt-2">{activeTier.desc}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Optional name & message */}
|
{/* Optional name & message */}
|
||||||
<div className="space-y-4 mb-6">
|
<div className="space-y-4 mb-6">
|
||||||
@@ -203,14 +222,32 @@ export default function SpendenPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-4 flex flex-wrap items-center justify-center gap-4 text-xs text-gray-400">
|
<div className="mt-4 flex flex-wrap items-center justify-center gap-3 text-xs text-gray-400">
|
||||||
<span className="flex items-center gap-1"><Shield className="w-3.5 h-3.5" /> Sichere Zahlung via Stripe</span>
|
<span className="flex items-center gap-1"><Shield className="w-3.5 h-3.5" /> Sichere Zahlung via Stripe</span>
|
||||||
<span className="flex items-center gap-1"><Check className="w-3.5 h-3.5" /> Kreditkarte & Twint</span>
|
<span className="flex items-center gap-1"><Check className="w-3.5 h-3.5" /> Kreditkarte</span>
|
||||||
|
<span className="flex items-center gap-1"><Check className="w-3.5 h-3.5" /> Twint</span>
|
||||||
|
<span className="flex items-center gap-1"><Check className="w-3.5 h-3.5" /> Apple Pay</span>
|
||||||
|
<span className="flex items-center gap-1"><Check className="w-3.5 h-3.5" /> Google Pay</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Story */}
|
||||||
|
<div className="mt-10 bg-gray-50 rounded-2xl p-6 md:p-8 border border-gray-100">
|
||||||
|
<h3 className="font-bold text-gray-900 mb-3">Die Geschichte hinter Lageplan</h3>
|
||||||
|
<p className="text-sm text-gray-600 leading-relaxed">
|
||||||
|
Lageplan wird seit über 2 Jahren von einem aktiven Feuerwehrmann in seiner Freizeit entwickelt.
|
||||||
|
Die App ist und bleibt <strong>kostenlos</strong> — weil jede Feuerwehr Zugang zu guten Werkzeugen haben soll,
|
||||||
|
unabhängig vom Budget. Es gibt zwar kommerzielle Alternativen, aber die Idee war immer:
|
||||||
|
Ein Werkzeug <em>von der Feuerwehr, für die Feuerwehr</em>.
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-600 leading-relaxed mt-3">
|
||||||
|
Mit deiner Spende hilfst du, dass das so bleibt. Jeder Franken fliesst direkt in
|
||||||
|
Serverkosten und Weiterentwicklung. Danke für deine Unterstützung!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Trust */}
|
{/* Trust */}
|
||||||
<div className="mt-10 text-center">
|
<div className="mt-8 text-center">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
100% deiner Spende fliesst direkt in Serverkosten und Weiterentwicklung.
|
100% deiner Spende fliesst direkt in Serverkosten und Weiterentwicklung.
|
||||||
<br />Kein Unternehmen, keine Investoren — nur ein Feuerwehrmann mit einer Idee.
|
<br />Kein Unternehmen, keine Investoren — nur ein Feuerwehrmann mit einer Idee.
|
||||||
|
|||||||
@@ -948,10 +948,6 @@ export function JournalView({ projectId, projectTitle, projectLocation, einsatzl
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
|
||||||
<label className="text-xs font-semibold text-gray-500 uppercase">Koordinaten</label>
|
|
||||||
<Input value={rapportForm.koordinaten || ''} onChange={e => setRapportForm(f => ({ ...f, koordinaten: e.target.value }))} />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-semibold text-gray-500 uppercase">Alarmierungsart</label>
|
<label className="text-xs font-semibold text-gray-500 uppercase">Alarmierungsart</label>
|
||||||
<Input value={rapportForm.alarmierungsart || ''} onChange={e => setRapportForm(f => ({ ...f, alarmierungsart: e.target.value }))} />
|
<Input value={rapportForm.alarmierungsart || ''} onChange={e => setRapportForm(f => ({ ...f, alarmierungsart: e.target.value }))} />
|
||||||
@@ -964,22 +960,15 @@ export function JournalView({ projectId, projectTitle, projectLocation, einsatzl
|
|||||||
{/* Zeitverlauf */}
|
{/* Zeitverlauf */}
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-semibold text-gray-500 uppercase mb-1 block">Zeitverlauf</label>
|
<label className="text-xs font-semibold text-gray-500 uppercase mb-1 block">Zeitverlauf</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
{[
|
<div>
|
||||||
['zeitAlarm', 'Alarm'],
|
<label className="text-[10px] text-gray-400">Alarm</label>
|
||||||
['zeitAusruecken', 'Ausrücken'],
|
<Input type="time" className="text-sm h-8" value={rapportForm.zeitAlarm || ''} onChange={e => setRapportForm(f => ({ ...f, zeitAlarm: e.target.value }))} />
|
||||||
['zeitEintreffen', 'Eintreffen'],
|
</div>
|
||||||
['zeitBereit', 'Bereit'],
|
<div>
|
||||||
['zeitKontrolle', 'F. u. Kontrolle'],
|
<label className="text-[10px] text-gray-400">Eintreffen</label>
|
||||||
['zeitAus', 'F. aus'],
|
<Input type="time" className="text-sm h-8" value={rapportForm.zeitEintreffen || ''} onChange={e => setRapportForm(f => ({ ...f, zeitEintreffen: e.target.value }))} />
|
||||||
['zeitEinruecken', 'Einrücken'],
|
</div>
|
||||||
['zeitEnde', 'Ende'],
|
|
||||||
].map(([key, label]) => (
|
|
||||||
<div key={key}>
|
|
||||||
<label className="text-[10px] text-gray-400">{label}</label>
|
|
||||||
<Input className="text-sm h-8" value={rapportForm[key] || ''} onChange={e => setRapportForm(f => ({ ...f, [key]: e.target.value }))} placeholder="HH:MM" />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Lagebild */}
|
{/* Lagebild */}
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ export function MapView({
|
|||||||
const measureCoordsRef = useRef<number[][]>([])
|
const measureCoordsRef = useRef<number[][]>([])
|
||||||
const [isMapLoaded, setIsMapLoaded] = useState(false)
|
const [isMapLoaded, setIsMapLoaded] = useState(false)
|
||||||
const [activeBaseLayer, setActiveBaseLayer] = useState<'osm' | 'satellite' | 'swisstopo' | 'swissimage'>('osm')
|
const [activeBaseLayer, setActiveBaseLayer] = useState<'osm' | 'satellite' | 'swisstopo' | 'swissimage'>('osm')
|
||||||
|
const [layerDropdownOpen, setLayerDropdownOpen] = useState(false)
|
||||||
const [measurePointCount, setMeasurePointCount] = useState(0)
|
const [measurePointCount, setMeasurePointCount] = useState(0)
|
||||||
const [measureFinished, setMeasureFinished] = useState(false)
|
const [measureFinished, setMeasureFinished] = useState(false)
|
||||||
const [drawingPointCount, setDrawingPointCount] = useState(0)
|
const [drawingPointCount, setDrawingPointCount] = useState(0)
|
||||||
@@ -2106,11 +2107,12 @@ export function MapView({
|
|||||||
selectedSymbolRef.current.scale = Math.max(0.2, Math.min(10, startScale * ratio))
|
selectedSymbolRef.current.scale = Math.max(0.2, Math.min(10, startScale * ratio))
|
||||||
selectedSymbolRef.current.innerEl.style.fontSize = `${baseFontSize * selectedSymbolRef.current.scale}px`
|
selectedSymbolRef.current.innerEl.style.fontSize = `${baseFontSize * selectedSymbolRef.current.scale}px`
|
||||||
} else {
|
} else {
|
||||||
// For symbols: resize wrapper
|
// For symbols: resize wrapper, use ratio from start to preserve zoom-aware scale
|
||||||
selectedSymbolRef.current.wrapperEl.style.width = `${width}px`
|
selectedSymbolRef.current.wrapperEl.style.width = `${width}px`
|
||||||
selectedSymbolRef.current.wrapperEl.style.height = `${height}px`
|
selectedSymbolRef.current.wrapperEl.style.height = `${height}px`
|
||||||
const baseSize = 32
|
const startW = selectedSymbolRef.current.resizeStartWidth || 1
|
||||||
selectedSymbolRef.current.scale = Math.max(0.1, Math.min(10, width / baseSize))
|
const startScale = selectedSymbolRef.current.resizeStartScale || 1
|
||||||
|
selectedSymbolRef.current.scale = Math.max(0.1, Math.min(10, startScale * (width / startW)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -2155,33 +2157,52 @@ export function MapView({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Layer selector dropdown */}
|
{/* Layer selector dropdown */}
|
||||||
<select
|
<div className="absolute top-3 right-3 z-10">
|
||||||
value={activeBaseLayer}
|
<button
|
||||||
onChange={(e) => {
|
onClick={() => setLayerDropdownOpen(v => !v)}
|
||||||
if (!map.current) return
|
className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-[11px] font-medium shadow-md border backdrop-blur-sm transition-all"
|
||||||
const newLayer = e.target.value as 'osm' | 'satellite' | 'swisstopo' | 'swissimage'
|
style={{
|
||||||
const allLayers: Array<'osm' | 'satellite' | 'swisstopo' | 'swissimage'> = ['osm', 'satellite', 'swisstopo', 'swissimage']
|
background: activeBaseLayer !== 'osm' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.92)',
|
||||||
for (const l of allLayers) {
|
color: activeBaseLayer !== 'osm' ? '#fff' : '#1f2937',
|
||||||
map.current.setLayoutProperty(l, 'visibility', l === newLayer ? 'visible' : 'none')
|
borderColor: activeBaseLayer !== 'osm' ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.1)',
|
||||||
}
|
}}
|
||||||
setActiveBaseLayer(newLayer)
|
>
|
||||||
}}
|
<svg className="w-3.5 h-3.5 opacity-70" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||||||
className="absolute top-3 right-3 z-10 px-2.5 py-1.5 rounded-lg text-xs font-semibold shadow-lg border transition-colors cursor-pointer appearance-none pr-7"
|
{{ osm: 'OpenStreetMap', satellite: 'Satellit', swisstopo: 'Swisstopo', swissimage: 'Luftbild CH' }[activeBaseLayer]}
|
||||||
style={{
|
<svg className={`w-3 h-3 opacity-50 transition-transform ${layerDropdownOpen ? 'rotate-180' : ''}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M6 9l6 6 6-6"/></svg>
|
||||||
background: activeBaseLayer !== 'osm' ? 'rgba(0,0,0,0.75)' : 'rgba(255,255,255,0.95)',
|
</button>
|
||||||
color: activeBaseLayer !== 'osm' ? '#fff' : '#333',
|
{layerDropdownOpen && (
|
||||||
borderColor: activeBaseLayer !== 'osm' ? 'rgba(255,255,255,0.3)' : 'rgba(0,0,0,0.15)',
|
<>
|
||||||
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='${activeBaseLayer !== 'osm' ? 'white' : '%23333'}' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E")`,
|
<div className="fixed inset-0 z-10" onClick={() => setLayerDropdownOpen(false)} />
|
||||||
backgroundRepeat: 'no-repeat',
|
<div className="absolute right-0 mt-1 z-20 min-w-[160px] rounded-lg shadow-xl border overflow-hidden backdrop-blur-md"
|
||||||
backgroundPosition: 'right 6px center',
|
style={{ background: 'rgba(255,255,255,0.95)', borderColor: 'rgba(0,0,0,0.08)' }}>
|
||||||
}}
|
{([
|
||||||
title="Kartenstil wählen"
|
{ key: 'osm', label: 'OpenStreetMap' },
|
||||||
>
|
{ key: 'satellite', label: 'Satellit (Esri)' },
|
||||||
<option value="osm">🗺️ OpenStreetMap</option>
|
{ key: 'swisstopo', label: 'Swisstopo Karte' },
|
||||||
<option value="satellite">🛰️ Satellit (Esri)</option>
|
{ key: 'swissimage', label: 'Luftbild CH' },
|
||||||
<option value="swisstopo">🇨🇭 Swisstopo Karte</option>
|
] as const).map(({ key, label }) => (
|
||||||
<option value="swissimage">🇨🇭 Swisstopo Luftbild</option>
|
<button
|
||||||
</select>
|
key={key}
|
||||||
|
onClick={() => {
|
||||||
|
if (!map.current) return
|
||||||
|
const allLayers: Array<'osm' | 'satellite' | 'swisstopo' | 'swissimage'> = ['osm', 'satellite', 'swisstopo', 'swissimage']
|
||||||
|
for (const l of allLayers) {
|
||||||
|
map.current.setLayoutProperty(l, 'visibility', l === key ? 'visible' : 'none')
|
||||||
|
}
|
||||||
|
setActiveBaseLayer(key)
|
||||||
|
setLayerDropdownOpen(false)
|
||||||
|
}}
|
||||||
|
className={`w-full text-left px-3 py-2 text-[11px] font-medium transition-colors ${activeBaseLayer === key ? 'bg-blue-50 text-blue-700' : 'text-gray-700 hover:bg-gray-50'}`}
|
||||||
|
>
|
||||||
|
{activeBaseLayer === key && <span className="inline-block w-1.5 h-1.5 rounded-full bg-blue-500 mr-2 align-middle" />}
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Zeichnung abschliessen Button (Linie/Polygon/Pfeil) */}
|
{/* Zeichnung abschliessen Button (Linie/Polygon/Pfeil) */}
|
||||||
{(drawMode === 'linestring' || drawMode === 'polygon' || drawMode === 'arrow' || drawMode === 'dangerzone') && drawingPointCount >= 2 && (
|
{(drawMode === 'linestring' || drawMode === 'polygon' || drawMode === 'arrow' || drawMode === 'dangerzone') && drawingPointCount >= 2 && (
|
||||||
|
|||||||
@@ -166,8 +166,7 @@ export function RapportDocument({ data }: { data: RapportData }) {
|
|||||||
</View>
|
</View>
|
||||||
<View style={styles.fieldRow}>
|
<View style={styles.fieldRow}>
|
||||||
<FieldCell label="Einsatzort / Adresse" value={data.einsatzort} width="50%" />
|
<FieldCell label="Einsatzort / Adresse" value={data.einsatzort} width="50%" />
|
||||||
<FieldCell label="Koordinaten" value={data.koordinaten} mono width="25%" />
|
<FieldCell label="Objekt / Gebäude" value={data.objekt} width="50%" />
|
||||||
<FieldCell label="Objekt / Gebäude" value={data.objekt} width="25%" />
|
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.fieldRow}>
|
<View style={styles.fieldRow}>
|
||||||
<FieldCell label="Alarmierungsart" value={data.alarmierungsart} width="50%" />
|
<FieldCell label="Alarmierungsart" value={data.alarmierungsart} width="50%" />
|
||||||
@@ -185,16 +184,8 @@ export function RapportDocument({ data }: { data: RapportData }) {
|
|||||||
</View>
|
</View>
|
||||||
<View style={styles.fieldGrid}>
|
<View style={styles.fieldGrid}>
|
||||||
<View style={styles.fieldRow}>
|
<View style={styles.fieldRow}>
|
||||||
<FieldCell label="Alarmierung" value={data.zeitAlarm} mono highlight width="25%" />
|
<FieldCell label="Alarmierung" value={data.zeitAlarm} mono highlight width="50%" />
|
||||||
<FieldCell label="Ausrücken" value={data.zeitAusruecken} mono highlight width="25%" />
|
<FieldCell label="Eintreffen" value={data.zeitEintreffen} mono highlight width="50%" />
|
||||||
<FieldCell label="Eintreffen" value={data.zeitEintreffen} mono highlight width="25%" />
|
|
||||||
<FieldCell label="Einsatzbereit" value={data.zeitBereit} mono highlight width="25%" />
|
|
||||||
</View>
|
|
||||||
<View style={styles.fieldRow}>
|
|
||||||
<FieldCell label="Feuer unter Kontrolle" value={data.zeitKontrolle} mono highlight width="25%" />
|
|
||||||
<FieldCell label="Feuer aus" value={data.zeitAus} mono highlight width="25%" />
|
|
||||||
<FieldCell label="Einrücken" value={data.zeitEinruecken} mono highlight width="25%" />
|
|
||||||
<FieldCell label="Einsatzende" value={data.zeitEnde} mono highlight width="25%" />
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user