210 lines
8.6 KiB
TypeScript
210 lines
8.6 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import Layout from "../../../components/layout";
|
||
import { Carousel } from 'react-responsive-carousel';
|
||
import "react-responsive-carousel/lib/styles/carousel.min.css"; // requires a loader
|
||
import { GetServerSideProps } from 'next';
|
||
import { Location, UserRole } from "@prisma/client";
|
||
import axiosServer from '../../../src/axiosServer';
|
||
import { useTranslations, createTranslator } from 'next-intl';
|
||
import ReactMarkdown from 'react-markdown';
|
||
import rehypeRaw from 'rehype-raw';
|
||
import remarkGfm from 'remark-gfm';
|
||
// import { getTranslations } from 'next-intl/server';
|
||
|
||
const ViewLocationPage: React.FC<ViewLocationPageProps> = ({ location }) => {
|
||
const [activeTab, setActiveTab] = useState('mainLocation');
|
||
const [activeImage, setActiveImage] = useState(0);
|
||
|
||
const [images, setImages] = useState([]);
|
||
const [mainLocationImageCount, setMainLocationImageCount] = useState(0);
|
||
const t = useTranslations('content');
|
||
|
||
useEffect(() => {
|
||
const mainLocationImages = [location.picture1, location.picture2, location.picture3].filter(Boolean);
|
||
|
||
const backupLocationImages = location.backupLocationImages?.filter(Boolean) ?? [];
|
||
setImages([...mainLocationImages, ...backupLocationImages]);
|
||
setMainLocationImageCount(mainLocationImages.length);
|
||
}, [location.picture1, location.picture2, location.picture3, location.backupLocationImages]);
|
||
|
||
const handleTabChange = (tab: string) => {
|
||
setActiveTab(tab);
|
||
//show the proper image in the carousel
|
||
if (tab === 'backupLocation') {
|
||
setActiveImage(mainLocationImageCount);
|
||
} else {
|
||
setActiveImage(0);
|
||
}
|
||
};
|
||
|
||
const handleCarouselChange = (index) => {
|
||
// Switch to backupLocation tab if the current carousel image index is from the backup location
|
||
if (index >= mainLocationImageCount) {
|
||
setActiveTab('backupLocation');
|
||
} else {
|
||
setActiveTab('mainLocation');
|
||
}
|
||
setActiveImage(index);
|
||
};
|
||
|
||
return (
|
||
<Layout>
|
||
<div className="view-location-page max-w-4xl mx-auto my-8">
|
||
{/* Tabs */}
|
||
<div className="tabs flex border-b">
|
||
{/* Main Location Tab */}
|
||
<button
|
||
className={`tab flex-1 text-lg py-2 px-4 ${activeTab === 'mainLocation' ? 'border-b-4 border-blue-500 text-blue-600 font-semibold' : 'text-gray-600 hover:text-blue-500'}`}
|
||
onClick={() => handleTabChange('mainLocation')}
|
||
>
|
||
{location.title || location.name}
|
||
</button>
|
||
{/* Backup Location Tab */}
|
||
{location.backupLocationId !== null && (
|
||
<button
|
||
className={`tab flex-1 text-lg py-2 px-4 ${activeTab === 'backupLocation' ? 'border-b-4 border-blue-500 text-blue-600 font-semibold' : 'text-gray-600 hover:text-blue-500'}`}
|
||
onClick={() => handleTabChange('backupLocation')}
|
||
>
|
||
При лошо време: <strong>{location.backupLocationName}</strong>
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
{/* Carousel */}
|
||
{images.length > 0 && (
|
||
<Carousel showArrows={true}
|
||
autoPlay={false}
|
||
infiniteLoop={true}
|
||
showThumbs={false}
|
||
onChange={handleCarouselChange}
|
||
selectedItem={activeImage}
|
||
>
|
||
{images.map((src, index) => (
|
||
<div key={index}>
|
||
<img src={src} alt={`Slide ${index + 1}`} />
|
||
</div>
|
||
))}
|
||
</Carousel>
|
||
)}
|
||
|
||
{/* Tab Content */}
|
||
{(location.content || location.backupLocationContent) && (
|
||
<div className="tab-content mt-4">
|
||
|
||
{activeTab === 'mainLocation' && (
|
||
|
||
// <div className="p-4 bg-white shadow rounded-lg" dangerouslySetInnerHTML={{ __html: location.content }} />
|
||
|
||
<div className="p-4 bg-white shadow rounded-lg">
|
||
<ReactMarkdown
|
||
rehypePlugins={[rehypeRaw]}
|
||
remarkPlugins={[remarkGfm]}
|
||
components={{
|
||
img: ({ node, ...props }) => (
|
||
<img {...props} className="max-w-full h-auto my-4" alt="" />
|
||
),
|
||
a: ({ node, ...props }) => (
|
||
<a {...props} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline" />
|
||
)
|
||
}}
|
||
>
|
||
{location.content}
|
||
</ReactMarkdown>
|
||
</div>
|
||
)}
|
||
{activeTab === 'backupLocation' && location.backupLocationContent && (
|
||
<div className="p-4 bg-white shadow rounded-lg" dangerouslySetInnerHTML={{ __html: location.backupLocationContent }} />
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</Layout>
|
||
);
|
||
};
|
||
|
||
const common = require("../../../src/helpers/common");
|
||
|
||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||
const axios = await axiosServer(context);
|
||
|
||
// Get the locale from context or use default
|
||
const locale = context.locale || 'en';
|
||
const messages = (await import(`../../../content/i18n/${locale}.json`)).default;
|
||
|
||
const t = createTranslator({ locale, messages });
|
||
// Function to replace placeholders in HTML content. Placeholders are in the format {key}
|
||
const replacePlaceholders = (content: string) => {
|
||
if (!content) return '';
|
||
const placeholderPattern = /{([^}]+)}/g;
|
||
return content.replace(placeholderPattern, (match, key) => {
|
||
try {
|
||
const translation = t('content.' + key);
|
||
// Check if translation exists and is not empty
|
||
if (translation && translation !== 'content.' + key) {
|
||
return translation;
|
||
}
|
||
// Return formatted placeholder if translation not found
|
||
return `[${locale}:${key}]`;
|
||
} catch (error) {
|
||
// Return formatted placeholder on error
|
||
return `[${locale}:'content.'${key}]`;
|
||
}
|
||
});
|
||
};
|
||
|
||
const prismaClient = common.getPrismaClient();
|
||
const location = await getLocation(prismaClient, context);
|
||
location.content = replacePlaceholders(location.content);
|
||
location.title = t("content.location." + location.name + ".title");
|
||
|
||
if (location.backupLocationId !== null) {
|
||
// const { data: backupLocation } = await axios.get(
|
||
// process.env.NEXT_PUBLIC_PUBLIC_URL + "/api/data/locations/" + location.backupLocationId
|
||
// );
|
||
const backupLocation = await prismaClient.location.findFirst({
|
||
where: {
|
||
id: location.backupLocationId,
|
||
},
|
||
});
|
||
location.backupLocationName = backupLocation.name;
|
||
location.backupLocationContent = backupLocation ? replacePlaceholders(backupLocation.content) : "";
|
||
location.backupLocationImages = backupLocation ? [backupLocation.picture1, backupLocation.picture2, backupLocation.picture3].filter(Boolean) : [];
|
||
}
|
||
|
||
context.res.setHeader("Cache-Control", "s-maxage=1, stale-while-revalidate");
|
||
|
||
return {
|
||
props: {
|
||
location: location,
|
||
},
|
||
};
|
||
};
|
||
|
||
async function getLocation(prismaClient, context) {
|
||
// Try to parse the ID as a number
|
||
const numericId = parseInt(context.params.id);
|
||
let location;
|
||
|
||
// If it's a valid number, search by ID
|
||
if (!isNaN(numericId)) {
|
||
location = await prismaClient.location.findFirst({
|
||
where: {
|
||
id: numericId
|
||
}
|
||
});
|
||
}
|
||
|
||
// If no location found by ID or if ID is not numeric, search by name
|
||
if (!location) {
|
||
location = await prismaClient.location.findFirst({
|
||
where: {
|
||
name: context.params.id
|
||
}
|
||
});
|
||
}
|
||
|
||
return location;
|
||
}
|
||
|
||
export default ViewLocationPage;
|