v1.2.1: Fix Ctrl+Z/Y vertauscht, 401-Fehler TENANT_ADMIN, Symbole groesser, Spenden-Tab verbessert
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lageplan",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -330,23 +330,39 @@ export default function AdminPage() {
|
||||
const fetchData = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const [catRes, iconRes, userRes, tenantRes, projRes] = await Promise.all([
|
||||
const isServerAdmin = user?.role === 'SERVER_ADMIN'
|
||||
|
||||
// Common fetches for all admins
|
||||
const fetches: Promise<Response>[] = [
|
||||
fetch('/api/admin/categories'),
|
||||
fetch('/api/admin/icons'),
|
||||
fetch('/api/admin/users'),
|
||||
fetch('/api/admin/tenants'),
|
||||
fetch('/api/projects'),
|
||||
])
|
||||
]
|
||||
// SERVER_ADMIN-only fetches
|
||||
if (isServerAdmin) {
|
||||
fetches.push(fetch('/api/admin/icons'))
|
||||
fetches.push(fetch('/api/admin/tenants'))
|
||||
}
|
||||
|
||||
const results = await Promise.all(fetches)
|
||||
const [catRes, userRes, projRes] = results
|
||||
|
||||
if (catRes.ok) setCategories((await catRes.json()).categories || [])
|
||||
if (iconRes.ok) setIcons((await iconRes.json()).icons || [])
|
||||
if (userRes.ok) setUsers((await userRes.json()).users || [])
|
||||
if (tenantRes.ok) setTenants((await tenantRes.json()).tenants || [])
|
||||
if (projRes.ok) {
|
||||
const projData = await projRes.json()
|
||||
setAllProjects((projData.projects || []).map((p: any) => ({ id: p.id, title: p.title, location: p.location })))
|
||||
}
|
||||
|
||||
// Load SMTP settings
|
||||
if (isServerAdmin) {
|
||||
const iconRes = results[3]
|
||||
const tenantRes = results[4]
|
||||
if (iconRes.ok) setIcons((await iconRes.json()).icons || [])
|
||||
if (tenantRes.ok) setTenants((await tenantRes.json()).tenants || [])
|
||||
}
|
||||
|
||||
// Load settings (SERVER_ADMIN only)
|
||||
if (isServerAdmin) {
|
||||
try {
|
||||
const smtpRes = await fetch('/api/admin/settings')
|
||||
if (smtpRes.ok) {
|
||||
@@ -371,6 +387,7 @@ export default function AdminPage() {
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error)
|
||||
} finally {
|
||||
@@ -947,22 +964,22 @@ export default function AdminPage() {
|
||||
{catName}
|
||||
<span className="text-xs text-muted-foreground font-normal">({syms.length})</span>
|
||||
</h4>
|
||||
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10 gap-2 mb-4">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-3 mb-4">
|
||||
{syms.map(sym => {
|
||||
const selected = selectedSymbolIds.has(sym.id)
|
||||
return (
|
||||
<div
|
||||
key={sym.id}
|
||||
onClick={() => toggleSelect(sym.id)}
|
||||
className={`relative cursor-pointer border-2 rounded-lg p-2 transition-all hover:shadow-sm ${
|
||||
className={`relative cursor-pointer border-2 rounded-lg p-3 transition-all hover:shadow-sm ${
|
||||
selected ? 'border-blue-500 bg-blue-50 dark:bg-blue-950/30' :
|
||||
sym.isActive ? 'border-transparent hover:border-border' : 'border-transparent opacity-40'
|
||||
}`}
|
||||
>
|
||||
<div className="aspect-square flex items-center justify-center mb-1 bg-muted/50 rounded">
|
||||
<img src={`/api/icons/${sym.id}/image`} alt={sym.name} className="w-10 h-10 object-contain" />
|
||||
<div className="aspect-square flex items-center justify-center mb-1.5 bg-muted/50 rounded">
|
||||
<img src={`/api/icons/${sym.id}/image`} alt={sym.name} className="w-16 h-16 object-contain" />
|
||||
</div>
|
||||
<p className="text-[10px] text-center truncate" title={sym.name}>{sym.name}</p>
|
||||
<p className="text-xs text-center truncate" title={sym.name}>{sym.name}</p>
|
||||
{/* Status dot */}
|
||||
<div className={`absolute top-1.5 right-1.5 w-2 h-2 rounded-full ${sym.isActive ? 'bg-green-500' : 'bg-gray-300'}`} />
|
||||
</div>
|
||||
@@ -1819,24 +1836,41 @@ export default function AdminPage() {
|
||||
{user?.role === 'TENANT_ADMIN' && tenant && (
|
||||
<TabsContent value="donate" className="space-y-6">
|
||||
<div className="border rounded-lg p-6">
|
||||
<h3 className="font-semibold text-lg mb-4">Lageplan unterstützen</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Lageplan ist ein kostenloses Herzensprojekt. Wenn du die Weiterentwicklung unterstützen möchtest,
|
||||
kannst du auf unserer Spendenseite einen freiwilligen Beitrag leisten.
|
||||
<div className="flex items-start gap-4 mb-6">
|
||||
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-red-500 to-red-700 flex items-center justify-center shrink-0">
|
||||
<Heart className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg">Lageplan unterstützen</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Lageplan ist ein kostenloses Open-Source-Projekt — entwickelt von einem aktiven Feuerwehrmann
|
||||
in seiner Freizeit. Ohne Firma, ohne Investoren. Deine Spende hilft, den Betrieb und die
|
||||
Weiterentwicklung zu finanzieren.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-muted/50 rounded-lg p-4 mb-6">
|
||||
<p className="text-sm font-medium mb-2">Wohin fliesst deine Spende?</p>
|
||||
<ul className="text-sm text-muted-foreground space-y-1">
|
||||
<li className="flex items-center gap-2"><Shield className="w-3.5 h-3.5 text-primary shrink-0" /> Server-Hosting in der Schweiz (monatliche Kosten)</li>
|
||||
<li className="flex items-center gap-2"><Settings className="w-3.5 h-3.5 text-primary shrink-0" /> Entwicklung neuer Features und Bugfixes</li>
|
||||
<li className="flex items-center gap-2"><ShieldCheck className="w-3.5 h-3.5 text-primary shrink-0" /> Domain, SSL-Zertifikate und Infrastruktur</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Button asChild>
|
||||
<a href="/spenden" target="_blank" rel="noopener noreferrer">
|
||||
<Heart className="w-4 h-4 mr-2" />
|
||||
Zur Spendenseite
|
||||
Jetzt spenden
|
||||
</a>
|
||||
</Button>
|
||||
<div className="mt-6 pt-4 border-t">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
mit ♥ von Pepe —{' '}
|
||||
<a href="/" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">
|
||||
Über mich & Lageplan
|
||||
<Button variant="outline" asChild>
|
||||
<a href="/" target="_blank" rel="noopener noreferrer">
|
||||
Mehr über Lageplan erfahren
|
||||
</a>
|
||||
</p>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border rounded-lg p-6">
|
||||
|
||||
@@ -1087,10 +1087,11 @@ export default function AppPage() {
|
||||
return
|
||||
}
|
||||
|
||||
// Ctrl/Cmd shortcuts (CH keyboard: Z and Y are swapped)
|
||||
// Ctrl/Cmd shortcuts
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (e.key === 'z') { e.preventDefault(); handleRedo(); return }
|
||||
if (e.key === 'y') { e.preventDefault(); handleUndo(); return }
|
||||
if (e.key === 'z' && e.shiftKey) { e.preventDefault(); handleRedo(); return }
|
||||
if (e.key === 'z') { e.preventDefault(); handleUndo(); return }
|
||||
if (e.key === 'y') { e.preventDefault(); handleRedo(); return }
|
||||
if (e.key === 's') { e.preventDefault(); handleSaveProject(); return }
|
||||
return
|
||||
}
|
||||
@@ -1707,7 +1708,7 @@ export default function AppPage() {
|
||||
))}
|
||||
<div className="font-semibold text-muted-foreground col-span-2 mt-3 mb-0.5">Aktionen</div>
|
||||
{[
|
||||
['Ctrl+Y', 'Rückgängig'], ['Ctrl+Z', 'Wiederholen'],
|
||||
['Ctrl+Z', 'Rückgängig'], ['Ctrl+Y', 'Wiederholen'],
|
||||
['Ctrl+S', 'Speichern'], ['Del', 'Auswahl löschen'],
|
||||
['Esc', 'Abbrechen'], ['?', 'Diese Hilfe'],
|
||||
].map(([key, label]) => (
|
||||
|
||||
Reference in New Issue
Block a user