217 lines
10 KiB
TypeScript
217 lines
10 KiB
TypeScript
import { Container, Heading, Badge } from '@/components/ui';
|
|
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
|
import { Metadata } from 'next';
|
|
import { getAllReferences } from '@/lib/references';
|
|
import TrackedLink from '@/components/analytics/TrackedLink';
|
|
import { MapPin, Calendar, Briefcase, Zap, Activity, ShieldCheck, Wrench, CheckCircle2 } from 'lucide-react';
|
|
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
|
import { SITE_URL } from '@/lib/schema';
|
|
import Image from 'next/image';
|
|
import { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';
|
|
import { HeroSection } from '@/components/blocks/HeroSection';
|
|
import { defaultLocations, minorLocations } from '@/lib/map-data';
|
|
import { MDXRemote } from 'next-mdx-remote/rsc';
|
|
|
|
const CustomLi = ({ children, ...props }: any) => {
|
|
let text = '';
|
|
if (typeof children === 'string') text = children;
|
|
else if (Array.isArray(children)) text = children.map((c: any) => typeof c === 'string' ? c : '').join(' ');
|
|
else if (children?.props?.children) text = children.props.children;
|
|
|
|
const lowerText = String(text).toLowerCase();
|
|
|
|
let Icon = CheckCircle2;
|
|
if (lowerText.includes('kabel')) Icon = Zap;
|
|
else if (lowerText.includes('bohrung')) Icon = Activity;
|
|
else if (lowerText.includes('rohr')) Icon = ShieldCheck;
|
|
else if (lowerText.includes('montage')) Icon = Wrench;
|
|
else if (lowerText.includes('vlf') || lowerText.includes('otdr')) Icon = Zap;
|
|
|
|
return (
|
|
<li className="flex items-start gap-3 pl-4 pr-6 py-3.5 bg-neutral-50/50 border border-neutral-100 rounded-xl" {...props}>
|
|
<Icon className="w-5 h-5 text-primary shrink-0 mt-0.5" />
|
|
<span className="text-sm font-medium text-neutral-700 leading-snug [&>p]:mb-0">{children}</span>
|
|
</li>
|
|
);
|
|
};
|
|
|
|
const mdxComponents = {
|
|
ul: (props: any) => <ul className="grid grid-cols-1 gap-2 mb-6" {...props} />,
|
|
li: CustomLi,
|
|
p: (props: any) => <p className="text-neutral-600 text-sm mb-4 leading-relaxed" {...props} />,
|
|
};
|
|
|
|
interface PageProps {
|
|
params: Promise<{
|
|
locale: string;
|
|
}>;
|
|
}
|
|
|
|
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
const { locale } = await params;
|
|
if (locale !== 'de' && locale !== 'en') return {};
|
|
|
|
return {
|
|
title: 'Referenzen | E-TIB Gruppe',
|
|
description: 'Erfolgreich abgeschlossene Projekte der E-TIB Gruppe im Bereich Kabelnetzbau, Spülbohrtechnik und Netzinfrastruktur.',
|
|
alternates: {
|
|
canonical: `${SITE_URL}/${locale}/referenzen`,
|
|
languages: {
|
|
de: `${SITE_URL}/de/referenzen`,
|
|
en: `${SITE_URL}/en/referenzen`,
|
|
'x-default': `${SITE_URL}/en/referenzen`,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
function cleanLocation(location?: string): string {
|
|
if (!location) return '';
|
|
// Remove 5 digit postal codes
|
|
let cleaned = location.replace(/\b\d{5}\b/g, '');
|
|
// Clean up multiple spaces, slashes and commas
|
|
cleaned = cleaned.replace(/\s*,\s*/g, ', ').replace(/\s*\/\s*/g, ' / ').trim();
|
|
// Remove leading/trailing slash/comma residues
|
|
cleaned = cleaned.replace(/^[,/\s]+|[,/\s]+$/g, '').trim();
|
|
return cleaned || location;
|
|
}
|
|
|
|
export default async function ReferenzenOverview(props: { params: Promise<{ locale: string }> }) {
|
|
const { locale } = await props.params;
|
|
setRequestLocale(locale);
|
|
const t = await getTranslations('StandardPage');
|
|
|
|
const references = await getAllReferences(locale);
|
|
|
|
const allLocations = [...defaultLocations, ...minorLocations];
|
|
const enrichedLocations = allLocations.map(loc => {
|
|
if (loc.type === 'project') {
|
|
const ref = references.find(r => r.slug === loc.id);
|
|
if (ref) {
|
|
return {
|
|
...loc,
|
|
featuredImage: ref.frontmatter.featuredImage,
|
|
details: [
|
|
ref.frontmatter.client || 'Kunde',
|
|
ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear().toString(),
|
|
],
|
|
};
|
|
}
|
|
}
|
|
return loc;
|
|
});
|
|
|
|
return (
|
|
<div className="flex flex-col min-h-screen bg-neutral-50 pb-8 md:pb-24">
|
|
{/* Map Section */}
|
|
<InteractiveGermanyMap
|
|
isHero={true}
|
|
badge={locale === 'en' ? 'Our References' : 'Unsere Referenzen'}
|
|
title={locale === 'en' ? 'Successfully realized projects.' : 'Erfolgreich umgesetzte Projekte.'}
|
|
description={locale === 'en' ? 'From broadband expansion to complex 110kV lines: A selection of our nationwide projects where we have created infrastructure for the future.' : 'Vom Breitbandausbau bis zur komplexen 110kV-Trasse: Ein Auszug unserer bundesweiten Projekte, bei denen wir Infrastruktur für die Zukunft geschaffen haben.'}
|
|
locations={enrichedLocations}
|
|
stats={[
|
|
{ value: '100', suffix: '%', label: locale === 'en' ? 'Nationwide Reach' : 'Überregionale Reichweite' },
|
|
{ value: '200', suffix: '+', label: locale === 'en' ? 'Completed Projects' : 'Abgeschlossene Projekte' }
|
|
]}
|
|
/>
|
|
|
|
{/* Main Content Area */}
|
|
<Container className="relative z-20 mt-16 md:mt-24">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
|
|
{references.map((ref) => (
|
|
<div
|
|
key={ref.slug}
|
|
id={ref.slug}
|
|
className="flex flex-col bg-white border border-neutral-100 rounded-3xl overflow-hidden shadow-sm block scroll-mt-32"
|
|
>
|
|
<div className="flex flex-col h-full relative">
|
|
{/* Image Section */}
|
|
<div className="relative h-64 w-full bg-[#f4f4f5] overflow-hidden shrink-0 border-b border-neutral-100">
|
|
{ref.frontmatter.featuredImage ? (
|
|
<Image
|
|
src={ref.frontmatter.featuredImage}
|
|
alt={ref.frontmatter.title}
|
|
fill
|
|
className="object-cover transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] filter grayscale-[20%]"
|
|
/>
|
|
) : (
|
|
<div className="absolute inset-0 bg-[#f4f4f5] flex items-center justify-center">
|
|
<Image src="/assets/logo.png" alt="E-TIB Logo" width={80} height={80} className="opacity-20 grayscale" />
|
|
</div>
|
|
)}
|
|
<div className="absolute inset-0 bg-black/5" />
|
|
|
|
{/* Location Badge */}
|
|
<div className="absolute top-4 left-4 z-10">
|
|
<span className="bg-white/95 backdrop-blur-md text-neutral-dark px-3 py-1.5 rounded-sm text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 shadow-sm border border-neutral-200">
|
|
<MapPin className="w-3 h-3 text-primary" />
|
|
{cleanLocation(ref.frontmatter.location)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content Section */}
|
|
<div className="flex flex-col flex-grow p-6 md:p-8 bg-white">
|
|
<h3 className="text-xl md:text-2xl font-bold font-heading text-neutral-dark mb-6 leading-[1.2]">
|
|
{ref.frontmatter.title}
|
|
</h3>
|
|
|
|
{/* Project Details / Content */}
|
|
{ref.content && (
|
|
<div className="flex-grow mb-6">
|
|
<MDXRemote source={ref.content} components={mdxComponents} />
|
|
</div>
|
|
)}
|
|
|
|
{/* Technical Meta Data */}
|
|
<div className="flex flex-col gap-3 mt-auto border-t border-neutral-100 pt-6 relative z-10">
|
|
<div className="min-w-0">
|
|
<span className="block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1">{t('client')}</span>
|
|
<span className="flex items-start gap-2 text-sm font-semibold text-neutral-700 leading-tight">
|
|
<Briefcase className="w-3.5 h-3.5 text-primary shrink-0 mt-[2px]" />
|
|
<span className="line-clamp-2">{ref.frontmatter.client || t('client')}</span>
|
|
</span>
|
|
</div>
|
|
<div className="min-w-0">
|
|
<span className="block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1">{t('period')}</span>
|
|
<span className="flex items-start gap-2 text-sm font-semibold text-neutral-700 leading-tight">
|
|
<Calendar className="w-3.5 h-3.5 text-primary shrink-0 mt-[2px]" />
|
|
<span className="line-clamp-2">{ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear()}</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Support Section */}
|
|
<div className="mt-8 md:mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group">
|
|
<div className="absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3" />
|
|
<div className="relative z-10 max-w-2xl">
|
|
<h3 className="text-2xl md:text-3xl font-bold mb-4">{t('nextProjectTitle')}</h3>
|
|
<p className="text-lg text-white/70 mb-8">{t('nextProjectDesc')}</p>
|
|
<TrackedLink
|
|
href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}
|
|
className={getButtonClasses('accent', 'lg')}
|
|
eventProperties={{
|
|
location: 'referenzen_support_cta',
|
|
}}
|
|
>
|
|
<span className="relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark">
|
|
{t('contactUs')}
|
|
<span className="ml-2 transition-transform group-hover/btn:translate-x-1">
|
|
→
|
|
</span>
|
|
</span>
|
|
<ButtonOverlay variant="accent" />
|
|
</TrackedLink>
|
|
</div>
|
|
</div>
|
|
</Container>
|
|
</div>
|
|
);
|
|
}
|