3 Commits
8 changed files with 750 additions and 522 deletions
+3 -3
View File
@@ -105,7 +105,7 @@ export default function Admin() {
type="password"
ref={pinRef}
placeholder="•••••"
className={`w-full bg-white border text-center text-2xl text-ebony font-mono p-3 rounded-xl focus:outline-none ${loginError ? 'border-emergency/50 bg-emergency/5' : 'border-slate-teal/20 focus:border-slate-teal'}`}
className={`w-full bg-white border text-center text-2xl text-ebony font-mono p-3 rounded-xl tracking-widest focus:outline-none ${loginError ? 'border-emergency/50 bg-emergency/5' : 'border-slate-teal/20 focus:border-slate-teal'}`}
onChange={() => setLoginError(false)}
/>
</div>
@@ -123,7 +123,7 @@ export default function Admin() {
const availableTabs = [];
if (currentUser.role === 'Admin') availableTabs.push({ id: 'setup', icon: CalendarRange, label: 'Perioder' });
if (currentUser.role === 'Admin') availableTabs.push({ id: 'notice', icon: BellRing, label: 'Notis' });
if (currentUser.role === 'Admin' || currentUser.role === 'Staff') availableTabs.push({ id: 'notice', icon: BellRing, label: 'Notis' });
if (currentUser.role === 'Admin' || currentUser.role === 'Staff') availableTabs.push({ id: 'today', icon: ClipboardCheck, label: 'Närvaro' });
availableTabs.push({ id: 'report', icon: UsersIcon, label: 'Rapport' });
@@ -166,7 +166,7 @@ export default function Admin() {
)}
{activeTab === 'setup' && currentUser.role === 'Admin' && <SetupTab {...adminState} />}
{activeTab === 'notice' && currentUser.role === 'Admin' && <NoticeTab isOffline={adminState.isOffline} />}
{activeTab === 'notice' && (currentUser.role === 'Admin' || currentUser.role === 'Staff') && <NoticeTab isOffline={adminState.isOffline} />}
{activeTab === 'today' && (currentUser.role === 'Admin' || currentUser.role === 'Staff') && (
<AttendanceTab
periods={adminState.periods}
+32
View File
@@ -0,0 +1,32 @@
// app/components/ui/EmergencyButton.tsx
import { ChevronRight, Siren } from 'lucide-react';
import Link from 'next/link';
interface EmergencyButtonProps {
href: string;
title: string;
}
export function EmergencyButton({ href, title }: EmergencyButtonProps) {
return (
<Link
href={href}
className="group flex items-center justify-between w-full bg-emergency text-eggshell px-5 py-4 rounded-2xl shadow-md hover:shadow-lg hover:brightness-110 active:scale-[0.98] transition-all duration-300 border border-emergency/50"
>
<div className="flex items-center gap-3">
<div className="bg-white/20 p-2 rounded-xl group-hover:bg-white/30 transition-colors">
{/* Ikonen pulserar mjukt för att direkt fånga uppmärksamheten */}
<Siren size={24} className="animate-pulse" />
</div>
<h2 className="text-base sm:text-lg font-black uppercase tracking-widest drop-shadow-sm">
{title}
</h2>
</div>
<ChevronRight
size={24}
className="opacity-70 group-hover:opacity-100 group-hover:translate-x-1 transition-all"
/>
</Link>
);
}
+4
View File
@@ -92,6 +92,10 @@
{
"q": "Var parkerar man om det är fullt?",
"a": "Ransviks övre parkering."
},
{
"q": "Är det tillåtet att tälta här?",
"a": "Nej, förr fanns det en tältruta här men den ligger nu uppe vid stora parkeringen"
}
]
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"isActive": true,
"type": "important",
"type": "warning",
"message": "Glöm inte minst 1 liter vatten, solkräm och myggmedel. Det förväntas bli mycket varmt idag!"
}
+130 -10
View File
@@ -1,34 +1,154 @@
// app/emergency/page.tsx
import { AlertTriangle, FileText, HeartPulse, MapPin, Phone } from 'lucide-react';
"use client";
import { AlertTriangle, CheckCircle, Crosshair, FileText, HeartPulse, Loader2, MapPin, PhoneCall, XCircle, Navigation } from 'lucide-react';
import { useState } from 'react';
import { SafePhoneLink } from '../components/PhoneLinks';
import { PageHeader } from '../components/ui/PageHeader';
import { SectionCard } from '../components/ui/SectionCard';
export default function Emergency() {
const [confirmCall, setConfirmCall] = useState(false);
const [location, setLocation] = useState<{ lat: number, lng: number, acc: number } | null>(null);
const [locLoading, setLocLoading] = useState(false);
const [locError, setLocError] = useState("");
const getLocation = () => {
setLocLoading(true);
setLocError("");
if (!navigator.geolocation) {
setLocError("Din enhet saknar stöd för GPS.");
setLocLoading(false);
return;
}
navigator.geolocation.getCurrentPosition(
(pos) => {
setLocation({
lat: pos.coords.latitude,
lng: pos.coords.longitude,
acc: Math.round(pos.coords.accuracy) // Noggrannhet i meter
});
setLocLoading(false);
},
(err) => {
setLocError("Kunde inte hämta plats. Kontrollera att GPS är aktiverat i telefonen.");
setLocLoading(false);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 0 }
);
};
return (
<div className="space-y-6 animate-fade-in w-full mx-auto">
<PageHeader
title="Vid Nödsituation"
icon={AlertTriangle}
description="Agera lugnt, stanna kvar på platsen och tillkalla hjälp."
variant="emergency" // <-- Sets the red colors and uppercase
variant="emergency"
/>
{/* 112 Card */}
<SectionCard
variant="alert"
title="Ring 112"
icon={Phone}
icon={PhoneCall}
description="Vid olycka, brand eller livshotande tillstånd. Berätta vem du är och vad som har hänt."
>
<div className="bg-white/60 p-4 rounded-xl border border-emergency/20">
<h3 className="font-bold text-emergency flex items-center mb-1 text-xs uppercase tracking-wider">
<MapPin className="mr-2" size={14} /> Uppge din position
<div className="space-y-4">
{/* SÄKER 112-KNAPP */}
<div className="bg-white/40 p-2 rounded-2xl border border-emergency/20">
{!confirmCall ? (
<button
onClick={() => setConfirmCall(true)}
className="w-full flex items-center justify-center gap-3 bg-emergency text-white font-black uppercase tracking-widest text-lg py-4 rounded-xl shadow-sm hover:brightness-110 active:scale-[0.98] transition-all"
>
<PhoneCall size={24} className="animate-pulse" />
Ring 112
</button>
) : (
<div className="flex flex-col sm:flex-row gap-2 animate-fade-in">
<a
href="tel:112"
onClick={() => setTimeout(() => setConfirmCall(false), 2000)} // Återställ efter klick
className="flex-1 flex items-center justify-center gap-2 bg-emergency text-white font-black uppercase tracking-widest text-lg py-4 rounded-xl shadow-md hover:brightness-110 active:scale-[0.98] transition-all ring-4 ring-emergency/30"
>
<CheckCircle size={24} />
Ja, ring nu
</a>
<button
onClick={() => setConfirmCall(false)}
className="sm:w-1/3 flex items-center justify-center gap-2 bg-white text-ebony font-bold uppercase tracking-widest text-sm py-4 rounded-xl shadow-sm border border-emergency/20 hover:bg-eggshell transition-all"
>
<XCircle size={18} />
Avbryt
</button>
</div>
)}
</div>
{/* GPS OCH POSITION */}
<div className="bg-white/60 p-4 md:p-5 rounded-2xl border border-emergency/20">
<h3 className="font-black text-emergency flex items-center mb-2 text-xs uppercase tracking-widest">
<MapPin className="mr-2" size={16} /> Uppge din position
</h3>
<p className="text-ebony font-medium text-sm">
Använd appen <strong>112</strong> eller GPS. Säg att du befinner dig i Kullabergs Naturreservat. Var specifik (t.ex. "Nära fyren" eller "Vid Josefinelust").
<p className="text-ebony font-medium text-sm leading-relaxed mb-4">
Säg att du befinner dig i Kullabergs Naturreservat. Var specifik (t.ex. "Nära fyren" eller "Vid Josefinelust"). Minns du närmsta räddningspunkt?
</p>
<div className="bg-white p-3 rounded-xl border border-emergency/10 shadow-sm">
{location ? (
<div className="space-y-1 animate-fade-in">
<p className="text-xs font-bold text-slate-teal uppercase tracking-widest">Dina koordinater (WGS84)</p>
<p className="font-mono text-lg font-black text-ebony tracking-tight">
{location.lat.toFixed(5)}, {location.lng.toFixed(5)}
</p>
<p className="text-[10px] font-bold text-ebony/50 uppercase">
Noggrannhet: ca {location.acc} meter
</p>
<div className="flex flex-wrap items-center gap-3 mt-4 pt-3 border-t border-slate-teal/5">
<a
href={`https://maps.google.com/?q=${location.lat},${location.lng}`}
target="_blank"
rel="noreferrer"
className="flex items-center gap-1.5 bg-seafoam/10 text-slate-teal px-3 py-1.5 rounded-lg text-xs font-bold hover:bg-seafoam/20 transition-colors"
>
<MapPin size={14} />
Google Maps
</a>
<a
href={`http://maps.apple.com/?ll=${location.lat},${location.lng}&q=Min+Position`}
target="_blank"
rel="noreferrer"
className="flex items-center gap-1.5 bg-seafoam/10 text-slate-teal px-3 py-1.5 rounded-lg text-xs font-bold hover:bg-seafoam/20 transition-colors"
>
<Navigation size={14} />
Apple Maps
</a>
</div>
</div>
) : (
<button
onClick={getLocation}
disabled={locLoading}
className="w-full flex items-center justify-center gap-2 bg-emergency/10 text-emergency hover:bg-emergency/20 font-bold uppercase tracking-widest text-xs py-3 rounded-lg transition-colors disabled:opacity-50"
>
{locLoading ? <Loader2 size={16} className="animate-spin" /> : <Crosshair size={16} />}
{locLoading ? "Söker satelliter..." : "Hämta min exakta position"}
</button>
)}
{locError && (
<p className="text-xs font-bold text-emergency mt-3 animate-fade-in flex items-start gap-1.5">
<AlertTriangle size={14} className="shrink-0" />
{locError}
</p>
)}
</div>
</div>
</div>
</SectionCard>
@@ -45,7 +165,7 @@ export default function Emergency() {
</li>
<li className="flex items-start">
<span className="bg-seafoam text-eggshell font-bold px-2 py-0.5 rounded mr-3 text-xs shrink-0">2</span>
<span>Finns hjärtstartare? Ja, närmaste hjärtstartare finns inne <strong className="text-ebony font-black">Naturum Kullaberg</strong> (vid fyren) under deras öppettider.</span>
<span>Finns hjärtstartare? Ja, närmaste hjärtstartare finns inne <strong className="text-ebony font-black">Naturum Kullaberg</strong> (vid fyren).</span>
</li>
</ul>
</SectionCard>
+7
View File
@@ -6,6 +6,7 @@ import { useEffect, useState } from 'react';
import { readJsonFile } from './actions/jsonEditor';
import { SafePhoneLink } from './components/PhoneLinks';
import { ActionLinkCard } from './components/ui/ActionLinkCard';
import { EmergencyButton } from './components/ui/EmergencyButton';
import { SectionCard } from './components/ui/SectionCard';
const getNoticeConfig = (type: string) => {
@@ -65,6 +66,12 @@ export default function Home() {
)
)}
{/* NÖDKNAPPEN - Ligger utanför griddet så den alltid är fullbredd och i fokus */}
<EmergencyButton
href="/emergency"
title="Nödsituation"
/>
{/* Quick Action Cards now use the glass style */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<ActionLinkCard
+4
View File
@@ -21,6 +21,10 @@ const withSerwist = withSerwistInit({
const nextConfig: NextConfig = {
output: "standalone",
allowedDevOrigins: [
'10.10.0.121',
'localhost',
],
};
export default withSerwist(nextConfig);
+567 -506
View File
File diff suppressed because it is too large Load Diff