diff --git a/app/actions/files.ts b/app/actions/files.ts index 715f755..7ddc38d 100644 --- a/app/actions/files.ts +++ b/app/actions/files.ts @@ -1,10 +1,14 @@ // app/actions/files.ts 'use server'; +import crypto from 'crypto'; import fs from 'fs'; import fsPromises from 'fs/promises'; import path from 'path'; -import crypto from 'crypto'; + +const isProd = process.env.NODE_ENV === "production"; +const DATA_DIR = isProd ? "/app/data" : path.join(process.cwd(), "app/data"); +const FILES_DIR = path.join(DATA_DIR, "files"); const generateFileHash = (filePath: string): Promise => { return new Promise((resolve, reject) => { @@ -19,15 +23,15 @@ const generateFileHash = (filePath: string): Promise => { export async function getLocalFileMeta(fileUrl: string) { try { - const cleanPath = fileUrl.startsWith('/') ? fileUrl.substring(1) : fileUrl; - const fullPath = path.join(process.cwd(), 'public', cleanPath); + const cleanName = decodeURIComponent(fileUrl.split('/').pop() || ''); + const fullPath = path.join(FILES_DIR, cleanName); const stats = await fsPromises.stat(fullPath); const sizeMb = (stats.size / (1024 * 1024)).toFixed(2) + ' MB'; const fileHash = await generateFileHash(fullPath); return { size: sizeMb, version: fileHash }; } catch (error) { - console.error(`Kunde inte läsa filen: ${fileUrl}`, error); + console.error(`Kunde inte läsa filen (actions): ${fileUrl}`, error); return { size: "Okänd", version: "v1" }; } } \ No newline at end of file diff --git a/app/components/ui/Cards.tsx b/app/components/ui/Cards.tsx index 003665f..3a06492 100644 --- a/app/components/ui/Cards.tsx +++ b/app/components/ui/Cards.tsx @@ -30,8 +30,8 @@ export function DocumentCard({ doc, isOffline, isCached }: DocumentCardProps) { target="_blank" rel="noopener noreferrer" className={`group flex items-center justify-between p-4 rounded-2xl border transition-colors ${isOffline && !isCached - ? 'opacity-50 grayscale cursor-not-allowed pointer-events-none border-transparent bg-eggshell/50' - : 'bg-white/50 hover:bg-white border-slate-teal/10' + ? 'opacity-50 grayscale cursor-not-allowed pointer-events-none border-transparent bg-eggshell/50' + : 'bg-white/50 hover:bg-white border-slate-teal/10' }`} >
@@ -74,8 +74,8 @@ export function ExternalLinkCard({ link, isOffline }: ExternalLinkCardProps) { target={isOffline ? '_self' : '_blank'} rel="noopener noreferrer" className={`group flex items-center justify-between p-4 rounded-2xl border transition-colors ${isOffline - ? 'opacity-40 grayscale cursor-not-allowed pointer-events-none bg-eggshell/50 border-transparent' - : 'bg-white/50 hover:bg-white border-slate-teal/10' + ? 'opacity-40 grayscale cursor-not-allowed pointer-events-none bg-eggshell/50 border-transparent' + : 'bg-white/50 hover:bg-white border-slate-teal/10' }`} > {link.title} diff --git a/app/data/documents.json b/app/data/documents.json index 2fd3e43..7a60972 100644 --- a/app/data/documents.json +++ b/app/data/documents.json @@ -3,32 +3,42 @@ { "title": "Turistkarta", "icon": "Map", - "file": "/files/Turistkarta - Kullaberg.pdf" + "file": "Turistkarta - Kullaberg.pdf" }, { "title": "Orienteringskarta", "icon": "MapPlus", - "file": "/files/Orienteringskarta - Kullaberg.pdf" + "file": "Orienteringskarta - Kullaberg.pdf" }, { "title": "Badplatser att besöka", "icon": "WavesLadder", - "file": "/files/Badplatser på Kullaberg.pdf" + "file": "Badplatser på Kullaberg.pdf" }, { "title": "Vandringsrutter", "icon": "MapPinned", - "file": "/files/Vandringsrutter.pdf" + "file": "Vandringsrutter.pdf" }, { "title": "Underlag för guidediplomering", "icon": "BookCopy", - "file": "/files/Underlag för guidediplomering.pdf" + "file": "Underlag för guidediplomering.pdf" }, { "title": "Destinationskunskap Kullahalvön", "icon": "BookCopy", - "file": "/files/Destinationskunskap.pdf" + "file": "Destinationskunskap.pdf" + }, + { + "title": "Tjänstgöringsrapport", + "icon": "BookCopy", + "file": "Tjänstgöringsrapport 2026.pdf" + }, + { + "title": "Information om ditt sommarjobb", + "icon": "BookCopy", + "file": "Information om ditt sommarjobb 2026.pdf" } ], "links": [ @@ -48,6 +58,10 @@ "title": "Skåneleden", "url": "https://www.skaneleden.se/en" }, + { + "title": "RSNV Brandriskprognos", + "url": "https://rsnv.se/brandriskprognos/" + }, { "title": "Väderprognos Mölle", "url": "https://www.smhi.se/vader/prognoser-och-varningar/vaderprognos/q/H%C3%B6gan%C3%A4s/M%C3%B6lle/2691501" diff --git a/public/files/Badplatser på Kullaberg.pdf b/app/data/files/Badplatser på Kullaberg.pdf similarity index 100% rename from public/files/Badplatser på Kullaberg.pdf rename to app/data/files/Badplatser på Kullaberg.pdf diff --git a/public/files/Destinationskunskap.pdf b/app/data/files/Destinationskunskap.pdf similarity index 91% rename from public/files/Destinationskunskap.pdf rename to app/data/files/Destinationskunskap.pdf index 3edc6c5..ca5f016 100644 Binary files a/public/files/Destinationskunskap.pdf and b/app/data/files/Destinationskunskap.pdf differ diff --git a/app/data/files/Information om ditt sommarjobb 2026.pdf b/app/data/files/Information om ditt sommarjobb 2026.pdf new file mode 100644 index 0000000..f033e63 Binary files /dev/null and b/app/data/files/Information om ditt sommarjobb 2026.pdf differ diff --git a/public/files/Orienteringskarta - Kullaberg.pdf b/app/data/files/Orienteringskarta - Kullaberg.pdf similarity index 100% rename from public/files/Orienteringskarta - Kullaberg.pdf rename to app/data/files/Orienteringskarta - Kullaberg.pdf diff --git a/app/data/files/Tjänstgöringsrapport 2026.pdf b/app/data/files/Tjänstgöringsrapport 2026.pdf new file mode 100644 index 0000000..fbde373 Binary files /dev/null and b/app/data/files/Tjänstgöringsrapport 2026.pdf differ diff --git a/public/files/Turistkarta - Kullaberg.pdf b/app/data/files/Turistkarta - Kullaberg.pdf similarity index 100% rename from public/files/Turistkarta - Kullaberg.pdf rename to app/data/files/Turistkarta - Kullaberg.pdf diff --git a/public/files/Underlag för guidediplomering.pdf b/app/data/files/Underlag för guidediplomering.pdf similarity index 100% rename from public/files/Underlag för guidediplomering.pdf rename to app/data/files/Underlag för guidediplomering.pdf diff --git a/public/files/Vandringsrutter.pdf b/app/data/files/Vandringsrutter.pdf similarity index 100% rename from public/files/Vandringsrutter.pdf rename to app/data/files/Vandringsrutter.pdf diff --git a/app/documents/page.tsx b/app/documents/page.tsx index 6aa18ed..230e2d8 100644 --- a/app/documents/page.tsx +++ b/app/documents/page.tsx @@ -19,27 +19,6 @@ const IconMap: Record = { "FileText": FileText }; -const fetchFileMeta = async (url: string) => { - try { - const res = await fetch(url, { method: 'HEAD', cache: 'no-store' }); - const bytes = res.headers.get('content-length'); - const lastModified = res.headers.get('last-modified'); - const etag = res.headers.get('etag'); - - let sizeStr = "Okänd"; - if (bytes) { - const mb = (parseInt(bytes) / (1024 * 1024)).toFixed(2); - sizeStr = `${mb} MB`; - } - const version = etag ? etag.replace(/"/g, '') : - (lastModified ? new Date(lastModified).getTime().toString() : 'v1'); - - return { size: sizeStr, version }; - } catch (error) { - return { size: "Okänd", version: "v1" }; - } -}; - export default function Documents() { const [isHydrated, setIsHydrated] = useState(false); const [isOffline, setIsOffline] = useState(false); @@ -73,7 +52,8 @@ export default function Documents() { const docsWithMeta = await Promise.all( (res.data.docs || []).map(async (doc: DocumentItem) => { const meta = await getLocalFileMeta(doc.file); - const versionedUrl = `${doc.file}?v=${meta.version}`; + const fileNameOnly = doc.file.split('/').pop(); + const versionedUrl = `/files/${fileNameOnly}?v=${meta.version}`; return { ...doc, diff --git a/app/files/[...slug]/route.ts b/app/files/[...slug]/route.ts new file mode 100644 index 0000000..fec3d49 --- /dev/null +++ b/app/files/[...slug]/route.ts @@ -0,0 +1,42 @@ +// app/files/[...slug]/route.ts + +import fs from 'fs'; +import { NextRequest, NextResponse } from 'next/server'; +import path from 'path'; + +const isProd = process.env.NODE_ENV === "production"; +const DATA_DIR = isProd ? "/app/data" : path.join(process.cwd(), "app/data"); +const FILES_DIR = path.join(DATA_DIR, "files"); + +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ slug: string[] }> } +) { + try { + const resolvedParams = await params; + const filename = decodeURIComponent(resolvedParams.slug[resolvedParams.slug.length - 1]); + const filePath = path.join(FILES_DIR, filename); + + if (!fs.existsSync(filePath)) { + console.error(`404 - Filen finns inte på disk: ${filePath}`); + return new NextResponse('Filen hittades inte', { status: 404 }); + } + + const fileBuffer = fs.readFileSync(filePath); + + let contentType = 'application/pdf'; + if (filename.toLowerCase().endsWith('.png')) contentType = 'image/png'; + else if (filename.toLowerCase().endsWith('.jpg') || filename.toLowerCase().endsWith('.jpeg')) contentType = 'image/jpeg'; + else if (filename.toLowerCase().endsWith('.doc') || filename.toLowerCase().endsWith('.docx')) contentType = 'application/msword'; + + return new NextResponse(fileBuffer, { + headers: { + 'Content-Type': contentType, + 'Content-Disposition': `inline; filename="${filename}"` + }, + }); + } catch (error) { + console.error("Fel vid fildelning:", error); + return new NextResponse('Ett internt serverfel uppstod', { status: 500 }); + } +} \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index d1c1661..bf8c2f3 100644 --- a/next.config.ts +++ b/next.config.ts @@ -23,6 +23,7 @@ const nextConfig: NextConfig = { output: "standalone", allowedDevOrigins: [ '10.10.0.121', + '10.11.0.122', 'localhost', ], };