This commit is contained in:
Şahan Hasret
2025-11-20 16:50:28 +03:00
parent 08c426f97b
commit c0b7fb463e
15 changed files with 1395 additions and 646 deletions

View File

@@ -1,145 +1,40 @@
'use client';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { dataStore } from '@/lib/dataStore';
import type { Document } from '@/data/documents';
export default function Documents() {
const [documents, setDocuments] = useState<Document[]>([]);
const [selectedCategory, setSelectedCategory] = useState('all');
const categories = [
{ id: 'all', name: 'Tümü', icon: '📁' },
{ id: 'technical', name: 'Teknik Dokümanlar', icon: '📐' },
{ id: 'reports', name: 'Raporlar', icon: '📊' },
{ id: 'permissions', name: 'İzinler', icon: '✅' },
{ id: 'presentations', name: 'Sunumlar', icon: '📽️' },
];
useEffect(() => {
setDocuments(dataStore.getDocuments());
}, []);
const documents = [
{
id: 1,
category: 'technical',
title: 'A2 Metro Hattı Teknik Şartnamesi',
description: 'Proje kapsamındaki tüm teknik detaylar',
date: '15 Ekim 2025',
size: '12.5 MB',
type: 'PDF',
icon: '📐',
},
{
id: 2,
category: 'technical',
title: 'İstasyon Mimari Projesi',
description: 'İstasyon binalarının mimari tasarımı',
date: '12 Ekim 2025',
size: '25.8 MB',
type: 'PDF',
icon: '🏗️',
},
{
id: 3,
category: 'technical',
title: 'Güvenlik Planı ve Prosedürleri',
description: 'İş güvenliği planı ve acil durum prosedürleri',
date: '10 Ekim 2025',
size: '6.8 MB',
type: 'PDF',
icon: '🛡️',
},
{
id: 4,
category: 'technical',
title: 'Malzeme Spesifikasyonları',
description: 'İnşaatta kullanılacak malzeme detayları',
date: '8 Ekim 2025',
size: '9.2 MB',
type: 'PDF',
icon: '🔧',
},
{
id: 5,
category: 'reports',
title: 'ÇED Raporu',
description: 'Çevresel Etki Değerlendirme',
date: '5 Ekim 2025',
size: '8.3 MB',
type: 'PDF',
icon: '🌍',
},
{
id: 6,
category: 'reports',
title: 'Eylül 2025 İlerleme Raporu',
description: 'Aylık proje ilerleme durumu',
date: '1 Ekim 2025',
size: '3.2 MB',
type: 'PDF',
icon: '📈',
},
{
id: 7,
category: 'reports',
title: 'Ağustos 2025 İlerleme Raporu',
description: 'Aylık proje ilerleme durumu',
date: '1 Eylül 2025',
size: '3.1 MB',
type: 'PDF',
icon: '📈',
},
{
id: 8,
category: 'permissions',
title: 'İnşaat Ruhsatı',
description: 'Belediye onaylı inşaat ruhsatı belgesi',
date: '5 Eylül 2025',
size: '1.5 MB',
type: 'PDF',
icon: '✅',
},
{
id: 9,
category: 'permissions',
title: 'Kamulaştırma İzin Belgeleri',
description: 'Proje alanı kamulaştırma işlem belgeleri',
date: '28 Ağustos 2025',
size: '4.7 MB',
type: 'PDF',
icon: '📋',
},
{
id: 10,
category: 'permissions',
title: 'Çalışma İzin Belgeleri',
description: 'İlgili kurumlardan alınan çalışma izinleri',
date: '15 Ağustos 2025',
size: '2.8 MB',
type: 'PDF',
icon: '📄',
},
{
id: 11,
category: 'presentations',
title: 'A2 Metro Hattı Tanıtım Sunumu',
description: 'Genel tanıtım ve proje özeti sunumu',
date: '20 Eylül 2025',
size: '15.6 MB',
type: 'PPTX',
icon: '📽️',
},
{
id: 12,
category: 'presentations',
title: 'Teknik Altyapı Sunumu',
description: 'Tünel ve istasyon altyapı detayları',
date: '15 Eylül 2025',
size: '22.4 MB',
type: 'PPTX',
icon: '🎯',
},
const categories = [
{ id: 'all', name: 'Tümü', icon: '📋' },
{ id: 'ihale', name: 'İhale Belgeleri', icon: '📄' },
{ id: 'teknik', name: 'Teknik Dökümanlar', icon: '📐' },
{ id: 'cevresel', name: 'Çevresel Etki', icon: '🌱' },
{ id: 'raporlar', name: 'İlerleme Raporları', icon: '📊' },
{ id: 'guvenlik', name: 'Güvenlik', icon: '🛡️' },
];
const filteredDocs = selectedCategory === 'all' ? documents : documents.filter(d => d.category === selectedCategory);
const getFileIcon = (type: string) => {
switch (type) {
case 'PDF': return '📕';
case 'DWG': return '📐';
case 'XLSX': return '📊';
case 'DOCX': return '📝';
default: return '📄';
}
};
return (
<div className="min-h-screen bg-[#003366]">
<Header />
@@ -169,7 +64,7 @@ export default function Documents() {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredDocs.map((doc) => (
<div key={doc.id} className="bg-white rounded-2xl p-6">
<div className="text-4xl mb-4">{doc.icon}</div>
<div className="text-4xl mb-4">{getFileIcon(doc.type)}</div>
<h3 className="text-xl font-bold text-[#004B87] mb-2">{doc.title}</h3>
<p className="text-gray-600 text-sm mb-4">{doc.description}</p>
<div className="text-xs text-gray-500 mb-4">

View File

@@ -1,50 +1,41 @@
'use client';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import Image from 'next/image';
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { dataStore, type Camera } from '@/lib/dataStore';
export default function LiveStream() {
const [selectedCamera, setSelectedCamera] = useState(1);
const [cameras, setCameras] = useState<Camera[]>([]);
const cameras = [
{
id: 1,
name: 'Dikimevi İstasyonu - Ana Giriş',
location: 'Dikimevi',
videoUrl: 'https://www.youtube.com/embed/b9q88QDEcKg?autoplay=1',
status: 'online',
viewers: 1243
},
{
id: 2,
name: 'Tuzluçayır İstasyonu - İnşaat Sahası',
location: 'Tuzluçayır',
videoUrl: 'https://www.youtube.com/embed/b9q88QDEcKg?autoplay=1',
status: 'online',
viewers: 856
},
{
id: 3,
name: 'A2 Metro Hattı - Tünel Kazı Çalışması',
location: 'Mamak',
videoUrl: 'https://www.youtube.com/embed/b9q88QDEcKg?autoplay=1',
status: 'online',
viewers: 2134
},
{
id: 4,
name: 'İstasyon Binası İç Mekan',
location: 'Dikimevi',
videoUrl: 'https://www.youtube.com/embed/b9q88QDEcKg?autoplay=1',
status: 'online',
viewers: 534
useEffect(() => {
const loadedCameras = dataStore.getCameras()
.filter(cam => cam.status === 'online')
.sort((a, b) => a.order - b.order);
setCameras(loadedCameras);
if (loadedCameras.length > 0) {
setSelectedCamera(loadedCameras[0].id);
}
];
}, []);
const selectedCam = cameras.find(cam => cam.id === selectedCamera) || cameras[0];
if (!selectedCam) {
return (
<div className="min-h-screen bg-[#003366]">
<Header />
<main className="pt-32 pb-16">
<div className="max-w-7xl mx-auto px-4 text-center">
<p className="text-white text-xl">Şu anda aktif kamera bulunmamaktadır.</p>
</div>
</main>
<Footer />
</div>
);
}
return (
<div className="min-h-screen bg-[#003366]">
<Header />
@@ -92,13 +83,15 @@ export default function LiveStream() {
{selectedCam.name}
</h2>
<div className="flex items-center space-x-4 text-sm text-gray-600">
<div className="flex items-center space-x-1">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
<path fillRule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clipRule="evenodd" />
</svg>
<span>{selectedCam.viewers.toLocaleString('tr-TR')} izleyici</span>
</div>
{selectedCam.viewers && (
<div className="flex items-center space-x-1">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
<path fillRule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clipRule="evenodd" />
</svg>
<span>{selectedCam.viewers.toLocaleString('tr-TR')} izleyici</span>
</div>
)}
<div className="flex items-center space-x-1">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clipRule="evenodd" />

View File

@@ -1,15 +1,21 @@
'use client';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import Image from 'next/image';
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { newsData, categories } from '@/data/news';
import { dataStore } from '@/lib/dataStore';
import { categories, type NewsItem } from '@/data/news';
export default function News() {
const [newsData, setNewsData] = useState<NewsItem[]>([]);
const [selectedCategory, setSelectedCategory] = useState('all');
const [selectedNews, setSelectedNews] = useState<number | null>(null);
useEffect(() => {
setNewsData(dataStore.getNews());
}, []);
const filteredNews = selectedCategory === 'all'
? newsData
: newsData.filter(item => item.category === selectedCategory);

View File

@@ -1,9 +1,19 @@
'use client';
import { useState, useEffect } from 'react';
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { dataStore, type SiteSettings } from '@/lib/dataStore';
export default function Contact() {
const [settings, setSettings] = useState<SiteSettings | null>(null);
useEffect(() => {
setSettings(dataStore.getSiteSettings());
}, []);
if (!settings) return null;
return (
<div className="min-h-screen bg-[#003366]">
<Header />
@@ -35,8 +45,7 @@ export default function Contact() {
<div className="flex-1">
<h3 className="text-lg font-bold text-[#004B87] mb-2">ADRES</h3>
<p className="text-gray-700 leading-relaxed">
Emniyet Mah. Hipodrom Caddesi No: 5<br />
Yenimahalle / Ankara
{settings.contact.address}
</p>
</div>
</div>
@@ -53,10 +62,10 @@ export default function Contact() {
<div className="flex-1">
<h3 className="text-lg font-bold text-[#004B87] mb-2">KEP ADRESİ</h3>
<a
href="mailto:ankarabuyuksehirbelediyesi@hs01.kep.tr"
href={`mailto:${settings.contact.kep}`}
className="text-gray-700 hover:text-[#00B4D8] transition-colors break-all"
>
ankarabuyuksehirbelediyesi@hs01.kep.tr
{settings.contact.kep}
</a>
</div>
</div>
@@ -73,10 +82,10 @@ export default function Contact() {
<div className="flex-1">
<h3 className="text-lg font-bold text-[#004B87] mb-2">TELEFON</h3>
<a
href="tel:+903125071000"
href={`tel:${settings.contact.phone.replace(/\s/g, '')}`}
className="text-xl font-semibold text-gray-700 hover:text-[#00B4D8] transition-colors"
>
+90 (312) 507 10 00
{settings.contact.phone}
</a>
</div>
</div>

View File

@@ -1,86 +1,20 @@
'use client';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import Image from 'next/image';
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { dataStore } from '@/lib/dataStore';
import type { MediaItem } from '@/data/media';
export default function MediaGallery() {
const [mediaItems, setMediaItems] = useState<MediaItem[]>([]);
const [selectedTab, setSelectedTab] = useState<'all' | 'video' | 'photo'>('all');
const [selectedMedia, setSelectedMedia] = useState<number | null>(null);
const mediaItems = [
{
id: 1,
type: 'video',
title: 'A2 Metro Hattı Genel Tanıtım',
thumbnail: 'https://images.pexels.com/photos/17152223/pexels-photo-17152223.jpeg',
videoUrl: 'https://www.youtube.com/embed/b9q88QDEcKg',
date: '15 Ekim 2025',
duration: '5:32',
description: 'A2 Metro Hattı projesinin genel tanıtımı ve istasyonların detayları'
},
{
id: 2,
type: 'photo',
title: 'Dikimevi İstasyonu İnşaat Çalışmaları',
thumbnail: 'https://images.pexels.com/photos/17302615/pexels-photo-17302615.jpeg',
date: '12 Ekim 2025',
description: 'Dikimevi metro istasyonunda devam eden kazı ve inşaat çalışmaları'
},
{
id: 3,
type: 'photo',
title: 'Tuzluçayır İstasyonu Temel Atma',
thumbnail: 'https://images.pexels.com/photos/33950678/pexels-photo-33950678.jpeg',
date: '10 Ekim 2025',
description: 'Tuzluçayır istasyonunun temel atma töreni anları'
},
{
id: 4,
type: 'video',
title: 'Metro İnşaatı İlerleme Raporu',
thumbnail: 'https://images.pexels.com/photos/253647/pexels-photo-253647.jpeg',
videoUrl: 'https://www.youtube.com/embed/b9q88QDEcKg',
date: '8 Ekim 2025',
duration: '8:15',
description: 'Ekim ayı metro inşaatı ilerleme raporu ve gelecek hedefler'
},
{
id: 5,
type: 'photo',
title: 'Modern İstasyon Tasarımları',
thumbnail: 'https://images.pexels.com/photos/17152223/pexels-photo-17152223.jpeg',
date: '5 Ekim 2025',
description: 'Yeni nesil metro istasyonlarının modern iç mekan tasarımları'
},
{
id: 6,
type: 'video',
title: 'Çevre Dostu Metro Projesi',
thumbnail: 'https://images.pexels.com/photos/17302615/pexels-photo-17302615.jpeg',
videoUrl: 'https://www.youtube.com/embed/b9q88QDEcKg',
date: '1 Ekim 2025',
duration: '6:45',
description: 'Metro projesinde kullanılan çevre dostu teknolojiler ve sürdürülebilir yaklaşımlar'
},
{
id: 7,
type: 'photo',
title: 'İşçi Güvenliği Eğitimi',
thumbnail: 'https://images.pexels.com/photos/33950678/pexels-photo-33950678.jpeg',
date: '28 Eylül 2025',
description: 'İnşaat sahalarında iş güvenliği eğitimleri'
},
{
id: 8,
type: 'photo',
title: 'Ray Döşeme Çalışmaları',
thumbnail: 'https://images.pexels.com/photos/253647/pexels-photo-253647.jpeg',
date: '25 Eylül 2025',
description: 'Metro hattında ray döşeme işlemlerinin başlaması'
},
];
useEffect(() => {
setMediaItems(dataStore.getMedia());
}, []);
const filteredMedia = selectedTab === 'all'
? mediaItems

View File

@@ -1,67 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import HeroSlider from "@/components/HeroSlider";
import QuickMenuCards from "@/components/QuickMenuCards";
import LiveStreamSection from "@/components/LiveStreamSection";
import NewsSection from "@/components/NewsSection";
import MetroLine from "@/components/MetroLine";
export default function Home() {
const [showLiveStream, setShowLiveStream] = useState(false);
const [showNews, setShowNews] = useState(false);
const [showDocuments, setShowDocuments] = useState(false);
const [showMediaGallery, setShowMediaGallery] = useState(false);
const [showComplaintForm, setShowComplaintForm] = useState(false);
const [showContact, setShowContact] = useState(false);
// Modal açıldığında yukarı kaydır
useEffect(() => {
if (showLiveStream || showNews || showDocuments || showMediaGallery || showComplaintForm || showContact) {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
}, [showLiveStream, showNews, showDocuments, showMediaGallery, showComplaintForm, showContact]);
return (
<div className="min-h-screen bg-[#003366]">
<Header />
{/* Hero Slider Section */}
<HeroSlider />
{/* Quick Menu Cards */}
<QuickMenuCards
onLiveStreamClick={() => setShowLiveStream(!showLiveStream)}
onNewsClick={() => setShowNews(!showNews)}
onDocumentsClick={() => setShowDocuments(!showDocuments)}
onMediaClick={() => setShowMediaGallery(!showMediaGallery)}
onComplaintClick={() => setShowComplaintForm(!showComplaintForm)}
onContactClick={() => setShowContact(!showContact)}
/>
{/* Live Stream Section */}
<LiveStreamSection
show={showLiveStream}
onClose={() => setShowLiveStream(false)}
/>
{/* News Section */}
<NewsSection
show={showNews}
onClose={() => setShowNews(false)}
showLiveStream={showLiveStream}
/>
{/* Metro Line Section - Ana içerik */}
<main className={`${showLiveStream || showNews || showDocuments || showMediaGallery || showComplaintForm || showContact ? 'pt-8' : 'pt-8 md:pt-64'} pb-16`}>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<MetroLine />
</div>
</main>
<Footer />
</div>
);
}

View File

@@ -25,11 +25,13 @@ export default function Home() {
// Gerçek veriler için state
const [heroSlides, setHeroSlides] = useState(dataStore.getSlider());
const [newsData, setNewsData] = useState(dataStore.getNews());
const [liveStreamConfig, setLiveStreamConfig] = useState(dataStore.getLiveStream());
// Verileri yükle
useEffect(() => {
setHeroSlides(dataStore.getSlider());
setNewsData(dataStore.getNews());
setLiveStreamConfig(dataStore.getLiveStream());
}, []);
// Modal açıldığında yukarı kaydır - KALDIRILDI (kullanıcı deneyimi için)
@@ -290,13 +292,13 @@ export default function Home() {
</div>
{/* Canlı Yayın Video Bölümü */}
{showLiveStream && (
{showLiveStream && liveStreamConfig.active && (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-8 md:pt-32 pb-8">
<div className="bg-white rounded-2xl shadow-2xl p-6 lg:p-8">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center space-x-3">
<div className="w-3 h-3 bg-red-500 rounded-full animate-pulse"></div>
<h2 className="text-2xl font-bold text-[#004B87]">Canlı Yayın</h2>
<h2 className="text-2xl font-bold text-[#004B87]">{liveStreamConfig.title || 'Canlı Yayın'}</h2>
</div>
<button
onClick={() => setShowLiveStream(false)}
@@ -312,8 +314,8 @@ export default function Home() {
<div className="relative w-full" style={{paddingBottom: '56.25%'}}>
<iframe
className="absolute top-0 left-0 w-full h-full rounded-lg"
src="https://www.youtube.com/embed/b9q88QDEcKg?autoplay=1"
title="Canlı Yayın"
src={liveStreamConfig.url}
title={liveStreamConfig.title || 'Canlı Yayın'}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>

View File

@@ -1,65 +1,18 @@
'use client';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { dataStore, type FAQ } from '@/lib/dataStore';
export default function FAQ() {
export default function FAQPage() {
const [openFAQ, setOpenFAQ] = useState<number | null>(null);
const [faqs, setFaqs] = useState<FAQ[]>([]);
const faqs = [
{
id: 1,
question: 'A2 Metro Hattı projesi ne zaman tamamlanacak?',
answer: 'A2 Metro Hattı projesinin 2026 yılı sonunda tamamlanması planlanmaktadır. Proje, Dikimevi-Natoyolu güzergâhında 6.5 kilometre uzunluğunda ve 5 istasyonlu bir metro hattı inşasını kapsamaktadır.'
},
{
id: 2,
question: 'Metro hattı hangi istasyonları kapsayacak?',
answer: 'A2 Metro Hattı aşağıdaki istasyonları içerecektir: Dikimevi İstasyonu, Tuzluçayır İstasyonu, Natoyolu İstasyonu ve diğer ara istasyonlar. Toplam 5 istasyon bulunacaktır.'
},
{
id: 3,
question: 'İnşaat çalışmaları sırasında trafik nasıl etkilenecek?',
answer: 'İnşaat çalışmaları sırasında geçici trafik düzenlemeleri uygulanacaktır. Alternatif güzergâhlar belirlenecek ve yönlendirme levhaları yerleştirilecektir. Vatandaşlarımızın anlayışına sığınıyoruz.'
},
{
id: 4,
question: 'Proje maliyeti ne kadar?',
answer: 'A2 Metro Hattı projesinin toplam maliyeti yaklaşık 2.5 milyar TL olarak belirlenmiştir. Bu maliyet, tünel kazısı, istasyon inşaatı, ray döşeme ve elektrik-elektronik sistemleri kapsamaktadır.'
},
{
id: 5,
question: 'Çevreye etkisi ne olacak?',
answer: 'Proje, çevre dostu teknolojiler kullanılarak gerçekleştirilmektedir. Çevresel Etki Değerlendirme (ÇED) raporu hazırlanmış ve gerekli izinler alınmıştır. İnşaat sırasında toz kontrolü ve gürültü önleme tedbirleri uygulanacaktır.'
},
{
id: 6,
question: 'İstasyonlar hangi özelliklere sahip olacak?',
answer: 'İstasyonlar modern mimari tasarımla, enerji verimliliği, erişilebilirlik ve güvenlik ön planda tutularak inşa edilecektir. Güneş enerjisi panelleri, LED aydınlatma ve akıllı havalandırma sistemleri kullanılacaktır.'
},
{
id: 7,
question: 'İnşaatta hangi teknolojiler kullanılacak?',
answer: 'Proje kapsamında TBM (Tunnel Boring Machine) tünel açma makinesi kullanılacaktır. Bu teknoloji sayesinde kazı çalışmaları daha hızlı ve güvenli şekilde gerçekleştirilecektir.'
},
{
id: 8,
question: 'Proje ilerleme durumu nasıl takip edilebilir?',
answer: 'Bu web sitesi üzerinden canlı yayın, haberler ve medya galerisi bölümlerinden proje ilerleme durumunu takip edebilirsiniz. Ayrıca aylık ilerleme raporları yayınlanmaktadır.'
},
{
id: 9,
question: 'İş güvenliği nasıl sağlanacak?',
answer: 'İnşaat alanında uluslararası standartlarda iş güvenliği tedbirleri uygulanmaktadır. Düzenli güvenlik eğitimleri verilir ve acil durum prosedürleri hazırlanmıştır.'
},
{
id: 10,
question: 'Vatandaşlar proje hakkında nasıl bilgi alabilir?',
answer: 'Bu web sitesi, sosyal medya hesapları ve tanıtım etkinlikleri aracılığıyla proje hakkında detaylı bilgi alınabilir. Ayrıca iletişim bölümünden sorularınızı iletebilirsiniz.'
}
];
useEffect(() => {
setFaqs(dataStore.getFAQs().sort((a, b) => a.order - b.order));
}, []);
const toggleFAQ = (id: number) => {
setOpenFAQ(openFAQ === id ? null : id);

View File

@@ -2,7 +2,7 @@
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { dataStore, type SliderItem } from '@/lib/dataStore';
import { dataStore, type SliderItem, type LiveStreamConfig, type Message, type SiteSettings, type FAQ, type Camera } from '@/lib/dataStore';
import type { NewsItem } from '@/data/news';
import type { MediaItem } from '@/data/media';
import type { Document } from '@/data/documents';
@@ -19,6 +19,11 @@ export default function Dashboard() {
const [mediaItems, setMediaItems] = useState<MediaItem[]>([]);
const [documents, setDocuments] = useState<Document[]>([]);
const [metroStations, setMetroStations] = useState<MetroStation[]>([]);
const [liveStreamConfig, setLiveStreamConfig] = useState<LiveStreamConfig | null>(null);
const [messages, setMessages] = useState<Message[]>([]);
const [siteSettings, setSiteSettings] = useState<SiteSettings | null>(null);
const [faqs, setFaqs] = useState<FAQ[]>([]);
const [cameras, setCameras] = useState<Camera[]>([]);
useEffect(() => {
const token = localStorage.getItem('admin_token');
@@ -37,6 +42,11 @@ export default function Dashboard() {
setMediaItems(dataStore.getMedia());
setDocuments(dataStore.getDocuments());
setMetroStations(dataStore.getMetroStations());
setLiveStreamConfig(dataStore.getLiveStream());
setMessages(dataStore.getMessages());
setSiteSettings(dataStore.getSiteSettings());
setFaqs(dataStore.getFAQs().sort((a, b) => a.order - b.order));
setCameras(dataStore.getCameras().sort((a, b) => a.order - b.order));
};
const handleLogout = () => {
@@ -48,7 +58,7 @@ export default function Dashboard() {
{ title: 'Aktif Slider', value: sliderItems.filter(s => s.active).length.toString(), icon: '🎬', color: 'from-[#004B87] to-[#00B4D8]', change: '+1' },
{ title: 'Toplam Haberler', value: newsItems.length.toString(), icon: '📰', color: 'from-[#00B4D8] to-[#0096C7]', change: '+2' },
{ title: 'Medya İçeriği', value: mediaItems.length.toString(), icon: '📸', color: 'from-[#0096C7] to-[#48CAE4]', change: '+3' },
{ title: 'Metro İstasyonları', value: metroStations.length.toString(), icon: '🚇', color: 'from-[#48CAE4] to-[#90E0EF]', change: '+2' },
{ title: 'Gelen Mesajlar', value: messages.filter(m => !m.read).length.toString(), icon: '✉️', color: 'from-[#48CAE4] to-[#90E0EF]', change: `+${messages.filter(m => !m.read).length}` },
];
const menuItems = [
@@ -58,7 +68,11 @@ export default function Dashboard() {
{ id: 'media', label: 'Medya', icon: '📸' },
{ id: 'documents', label: 'Belgeler', icon: '📄' },
{ id: 'metro-line', label: 'Metro Hattı', icon: '🚇' },
{ id: 'settings', label: 'Ayarlar', icon: '⚙️' },
{ id: 'live-stream', label: 'Canlı Yayın', icon: '📺' },
{ id: 'cameras', label: 'Kameralar', icon: '📹' },
{ id: 'faqs', label: 'SSS Yönetimi', icon: '❓' },
{ id: 'messages', label: 'Gelen Mesajlar', icon: '✉️', badge: messages.filter(m => !m.read).length || undefined },
{ id: 'site-settings', label: 'Site Ayarları', icon: '⚙️' },
];
// Slider management functions
@@ -110,11 +124,11 @@ export default function Dashboard() {
}
};
// Metro station update function
const handleUpdateStation = (id: number, updates: Partial<MetroStation>) => {
dataStore.updateStation(id, updates);
loadData();
};
// Metro station update function (used in modal)
// const handleUpdateStation = (id: number, updates: Partial<MetroStation>) => {
// dataStore.updateStation(id, updates);
// loadData();
// };
// Categories
const categories = [
@@ -134,8 +148,8 @@ export default function Dashboard() {
// Form states
const [selectedNewsCategory, setSelectedNewsCategory] = useState('all');
const [editingNews, setEditingNews] = useState<NewsItem | null>(null);
const [showNewsForm, setShowNewsForm] = useState(false);
// const [editingNews, setEditingNews] = useState<NewsItem | null>(null);
// const [showNewsForm, setShowNewsForm] = useState(false);
const [selectedMediaType, setSelectedMediaType] = useState<'all' | 'video' | 'photo'>('all');
const [selectedDocCategory, setSelectedDocCategory] = useState('all');
@@ -215,7 +229,6 @@ export default function Dashboard() {
description: '',
buttonText: 'Detaylı Bilgi',
buttonLink: '#',
image: '',
active: false
};
setEditingSlide(newSlide);
@@ -239,7 +252,9 @@ export default function Dashboard() {
content: '',
date: new Date().toLocaleDateString('tr-TR'),
image: '',
category: 'announcement',
category: 'announcements',
author: 'Admin',
tags: [],
featured: false
};
setEditingNewsItem(newNews);
@@ -273,7 +288,6 @@ export default function Dashboard() {
title: '',
description: '',
type: 'photo',
url: '',
thumbnail: '',
date: new Date().toLocaleDateString('tr-TR'),
category: 'construction'
@@ -308,10 +322,10 @@ export default function Dashboard() {
id: Math.max(...documents.map(d => d.id), 0) + 1,
title: '',
description: '',
type: 'pdf',
type: 'PDF',
size: '',
date: new Date().toLocaleDateString('tr-TR'),
category: 'technical',
category: 'teknik',
downloadUrl: '#'
};
setEditingDocument(newDoc);
@@ -408,7 +422,12 @@ export default function Dashboard() {
}`}
>
<span className="text-2xl">{item.icon}</span>
<span className="font-medium">{item.label}</span>
<span className="font-medium flex-1 text-left">{item.label}</span>
{item.badge && item.badge > 0 && (
<span className="px-2 py-1 bg-red-500 text-white text-xs rounded-full font-bold">
{item.badge}
</span>
)}
</button>
))}
</nav>
@@ -1289,145 +1308,617 @@ export default function Dashboard() {
</div>
)}
</div>
{/* Features */}
<div className="mt-4 pt-4 border-t">
<p className="text-xs font-semibold text-gray-600 mb-2">ÖZELLİKLER:</p>
<div className="flex flex-wrap gap-2">
{station.features.map((feature, i) => (
<span key={i} className="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded">
{feature}
</span>
))}
</div>
</div>
</div>
</div>
))}
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600">Toplam İstasyon</p>
<p className="text-2xl font-bold text-[#003366] mt-1">{metroStations.length}</p>
</div>
<div className="text-4xl">🚇</div>
</div>
</div>
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600">Tamamlanan</p>
<p className="text-2xl font-bold text-green-600 mt-1">{metroStations.filter(s => s.status === 'completed').length}</p>
</div>
<div className="text-4xl"></div>
</div>
</div>
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600">Devam Eden</p>
<p className="text-2xl font-bold text-blue-600 mt-1">{metroStations.filter(s => s.status === 'in-progress').length}</p>
</div>
<div className="text-4xl">🔄</div>
</div>
</div>
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600">Planlanan</p>
<p className="text-2xl font-bold text-yellow-600 mt-1">{metroStations.filter(s => s.status === 'planned').length}</p>
</div>
<div className="text-4xl">📅</div>
</div>
</div>
</div>
</div>
)}
{/* Settings Section */}
{activeSection === 'settings' && (
{/* Live Stream Management Section */}
{activeSection === 'live-stream' && liveStreamConfig && (
<div className="space-y-6">
<div className="bg-white rounded-xl shadow-sm p-6">
<h3 className="text-2xl font-bold text-[#003366] mb-6">Sistem Ayarları</h3>
<div className="space-y-6">
{/* Site Settings */}
<div className="border-b pb-6">
<h4 className="text-lg font-semibold text-[#004B87] mb-4 flex items-center space-x-2">
<span>🌐</span>
<span>Site Ayarları</span>
</h4>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Site Başlığı</label>
<input type="text" defaultValue="A2 Metro Hattı Projesi" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Site ıklaması</label>
<textarea rows={3} defaultValue="Ankara Büyükşehir Belediyesi A2 Metro Hattı İnşaat Projesi" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"></textarea>
</div>
</div>
<div className="mb-6">
<h3 className="text-2xl font-bold text-[#003366]">Canlı Yayın Yönetimi</h3>
<p className="text-gray-600 mt-1">YouTube canlı yayın URL&apos;sini ve ayarlarını yönetin</p>
</div>
<form onSubmit={(e) => {
e.preventDefault();
dataStore.setLiveStream(liveStreamConfig);
alert('Canlı yayın ayarları kaydedildi!');
}} className="space-y-6">
<div>
<label className="flex items-center space-x-2 mb-4">
<input
type="checkbox"
checked={liveStreamConfig.active}
onChange={(e) => setLiveStreamConfig({ ...liveStreamConfig, active: e.target.checked })}
className="w-5 h-5 text-[#00B4D8] border-gray-300 rounded focus:ring-[#00B4D8]"
/>
<span className="text-sm font-semibold text-gray-700">Canlı Yayın Aktif</span>
</label>
</div>
{/* Admin Settings */}
<div className="border-b pb-6">
<h4 className="text-lg font-semibold text-[#004B87] mb-4 flex items-center space-x-2">
<span>👤</span>
<span>Admin Ayarları</span>
</h4>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Kullanıcı Adı</label>
<input type="text" defaultValue="admin" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Yeni Şifre</label>
<input type="password" placeholder="Boş bırakın değiştirmek istemiyorsanız" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent" />
</div>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Yayın Başlığı
</label>
<input
type="text"
value={liveStreamConfig.title || ''}
onChange={(e) => setLiveStreamConfig({ ...liveStreamConfig, title: e.target.value })}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
placeholder="Canlı Yayın"
/>
</div>
{/* Notification Settings */}
<div className="border-b pb-6">
<h4 className="text-lg font-semibold text-[#004B87] mb-4 flex items-center space-x-2">
<span>🔔</span>
<span>Bildirim Ayarları</span>
</h4>
<div className="space-y-3">
<label className="flex items-center space-x-3">
<input type="checkbox" defaultChecked className="w-5 h-5 text-[#00B4D8] rounded focus:ring-[#00B4D8]" />
<span className="text-sm text-gray-700">Yeni haber eklendiğinde bildir</span>
</label>
<label className="flex items-center space-x-3">
<input type="checkbox" defaultChecked className="w-5 h-5 text-[#00B4D8] rounded focus:ring-[#00B4D8]" />
<span className="text-sm text-gray-700">Belge yüklendiğinde bildir</span>
</label>
<label className="flex items-center space-x-3">
<input type="checkbox" className="w-5 h-5 text-[#00B4D8] rounded focus:ring-[#00B4D8]" />
<span className="text-sm text-gray-700">Günlük rapor gönder</span>
</label>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
YouTube Embed URL *
</label>
<input
type="url"
value={liveStreamConfig.url}
onChange={(e) => setLiveStreamConfig({ ...liveStreamConfig, url: e.target.value })}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
placeholder="https://www.youtube.com/embed/VIDEO_ID"
required
/>
<p className="mt-2 text-sm text-gray-500">
YouTube video URL&apos;sini embed formatında girin. Örnek: https://www.youtube.com/embed/VIDEO_ID
</p>
</div>
{/* Save Button */}
<div className="flex justify-end space-x-3">
<button className="px-6 py-3 border border-gray-300 rounded-lg font-semibold text-gray-700 hover:bg-gray-50 transition-all">
İptal
</button>
<button className="px-6 py-3 bg-linear-to-r from-[#004B87] to-[#00B4D8] text-white rounded-lg font-semibold hover:shadow-lg transition-all">
{/* Preview */}
{liveStreamConfig.url && (
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Önizleme</label>
<div className="relative w-full bg-gray-100 rounded-lg overflow-hidden" style={{paddingBottom: '56.25%'}}>
<iframe
className="absolute top-0 left-0 w-full h-full"
src={liveStreamConfig.url}
title="Önizleme"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
</div>
)}
<div className="flex justify-end pt-4 border-t">
<button
type="submit"
className="px-6 py-3 bg-linear-to-r from-[#004B87] to-[#00B4D8] text-white rounded-lg font-semibold hover:shadow-lg transition-all"
>
Değişiklikleri Kaydet
</button>
</div>
</div>
</form>
</div>
</div>
)}
{activeSection !== 'overview' && activeSection !== 'slider' && activeSection !== 'news' && activeSection !== 'media' && activeSection !== 'documents' && activeSection !== 'metro-line' && activeSection !== 'settings' && (
{/* Messages Section */}
{activeSection === 'messages' && (
<div className="space-y-6">
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between mb-6">
<div>
<h3 className="text-2xl font-bold text-[#003366]">Gelen Mesajlar</h3>
<p className="text-gray-600 mt-1">Kullanıcılardan gelen dilek, öneri ve şikayetler</p>
</div>
<div className="flex items-center space-x-2">
<span className="px-4 py-2 bg-red-100 text-red-700 rounded-lg font-semibold text-sm">
{messages.filter(m => !m.read).length} Okunmamış
</span>
</div>
</div>
{messages.length === 0 ? (
<div className="text-center py-12">
<svg className="w-16 h-16 mx-auto text-gray-400 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
</svg>
<p className="text-gray-500">Henüz mesaj bulunmuyor</p>
</div>
) : (
<div className="space-y-4">
{messages.map((message) => (
<div
key={message.id}
className={`border rounded-lg p-4 transition-all ${
message.read ? 'bg-gray-50 border-gray-200' : 'bg-blue-50 border-blue-200'
}`}
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="flex items-center space-x-3 mb-2">
<h4 className="font-bold text-[#003366]">{message.name}</h4>
<span className={`px-3 py-1 text-xs font-semibold rounded-full ${
message.type === 'sikayet' ? 'bg-red-100 text-red-700' :
message.type === 'oneri' ? 'bg-green-100 text-green-700' :
'bg-blue-100 text-blue-700'
}`}>
{message.type === 'sikayet' ? 'Şikayet' : message.type === 'oneri' ? 'Öneri' : 'Bilgi Talebi'}
</span>
{!message.read && (
<span className="px-2 py-1 bg-red-500 text-white text-xs rounded-full">Yeni</span>
)}
</div>
<p className="text-sm text-gray-600 mb-2">
📧 {message.email} 📱 {message.phone}
</p>
<p className="text-sm font-semibold text-gray-700 mb-2">
Konu: {message.subject}
</p>
<p className="text-sm text-gray-700 bg-white p-3 rounded border">
{message.message}
</p>
<p className="text-xs text-gray-500 mt-2">
{new Date(message.date).toLocaleString('tr-TR')}
</p>
</div>
</div>
<div className="flex space-x-2">
{!message.read && (
<button
onClick={() => {
dataStore.markMessageAsRead(message.id);
loadData();
}}
className="px-4 py-2 bg-[#00B4D8] text-white rounded-lg text-sm font-semibold hover:bg-[#004B87] transition-colors"
>
Okundu İşaretle
</button>
)}
<button
onClick={() => {
if (confirm('Bu mesajı silmek istediğinizden emin misiniz?')) {
dataStore.deleteMessage(message.id);
loadData();
}
}}
className="px-4 py-2 bg-red-500 text-white rounded-lg text-sm font-semibold hover:bg-red-600 transition-colors"
>
Sil
</button>
</div>
</div>
))}
</div>
)}
</div>
</div>
)}
{/* FAQs Section */}
{activeSection === 'faqs' && (
<div className="space-y-6">
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="text-2xl font-bold text-[#003366]">SSS Yönetimi</h3>
<button
onClick={() => {
dataStore.addFAQ({
question: 'Yeni Soru',
answer: 'Cevap buraya yazılacak...',
order: faqs.length + 1
});
loadData();
}}
className="px-4 py-2 bg-[#00B4D8] text-white rounded-lg font-semibold hover:bg-[#0096C7] transition-colors flex items-center space-x-2"
>
<span></span>
<span>Yeni SSS Ekle</span>
</button>
</div>
<div className="space-y-4">
{faqs.map((faq) => (
<div key={faq.id} className="border border-gray-200 rounded-lg p-4">
<div className="space-y-3">
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<label className="block text-sm font-medium text-gray-700 mb-1">Soru</label>
<input
type="text"
value={faq.question}
onChange={(e) => {
dataStore.updateFAQ(faq.id, { question: e.target.value });
loadData();
}}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
/>
</div>
<div className="flex items-center space-x-2">
<input
type="number"
value={faq.order}
onChange={(e) => {
dataStore.updateFAQ(faq.id, { order: parseInt(e.target.value) });
loadData();
}}
className="w-16 px-2 py-2 border border-gray-300 rounded-lg text-center"
title="Sıra"
/>
<button
onClick={() => {
if (confirm('Bu SSS silinsin mi?')) {
dataStore.deleteFAQ(faq.id);
loadData();
}
}}
className="px-3 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors"
>
🗑
</button>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Cevap</label>
<textarea
value={faq.answer}
onChange={(e) => {
dataStore.updateFAQ(faq.id, { answer: e.target.value });
loadData();
}}
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
/>
</div>
</div>
</div>
))}
</div>
{faqs.length === 0 && (
<div className="text-center py-12 text-gray-500">
<p className="text-lg mb-2">Henüz SSS eklenmemiş</p>
<p className="text-sm">Yukarıdaki butonu kullanarak ilk SSS&apos;nizi ekleyin</p>
</div>
)}
</div>
</div>
)}
{/* Cameras Section */}
{activeSection === 'cameras' && (
<div className="space-y-6">
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="text-2xl font-bold text-[#003366]">Kamera Yönetimi</h3>
<button
onClick={() => {
dataStore.addCamera({
name: 'Yeni Kamera',
location: 'Konum',
videoUrl: 'https://www.youtube.com/embed/VIDEO_ID',
status: 'online',
viewers: 0,
order: cameras.length + 1
});
loadData();
}}
className="px-4 py-2 bg-[#00B4D8] text-white rounded-lg font-semibold hover:bg-[#0096C7] transition-colors flex items-center space-x-2"
>
<span></span>
<span>Yeni Kamera Ekle</span>
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{cameras.map((camera) => (
<div key={camera.id} className="border border-gray-200 rounded-lg p-4">
<div className="space-y-3">
<div className="flex items-start justify-between gap-2">
<div className="flex-1">
<label className="block text-sm font-medium text-gray-700 mb-1">Kamera Adı</label>
<input
type="text"
value={camera.name}
onChange={(e) => {
dataStore.updateCamera(camera.id, { name: e.target.value });
loadData();
}}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
/>
</div>
<button
onClick={() => {
if (confirm('Bu kamera silinsin mi?')) {
dataStore.deleteCamera(camera.id);
loadData();
}
}}
className="px-3 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors"
>
🗑
</button>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Konum</label>
<input
type="text"
value={camera.location}
onChange={(e) => {
dataStore.updateCamera(camera.id, { location: e.target.value });
loadData();
}}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Video URL (YouTube Embed)</label>
<input
type="text"
value={camera.videoUrl}
onChange={(e) => {
dataStore.updateCamera(camera.id, { videoUrl: e.target.value });
loadData();
}}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
placeholder="https://www.youtube.com/embed/VIDEO_ID"
/>
</div>
<div className="grid grid-cols-3 gap-2">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Durum</label>
<select
value={camera.status}
onChange={(e) => {
dataStore.updateCamera(camera.id, { status: e.target.value as 'online' | 'offline' });
loadData();
}}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
>
<option value="online">Online</option>
<option value="offline">Offline</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">İzleyici</label>
<input
type="number"
value={camera.viewers || 0}
onChange={(e) => {
dataStore.updateCamera(camera.id, { viewers: parseInt(e.target.value) || 0 });
loadData();
}}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Sıra</label>
<input
type="number"
value={camera.order}
onChange={(e) => {
dataStore.updateCamera(camera.id, { order: parseInt(e.target.value) });
loadData();
}}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
/>
</div>
</div>
</div>
</div>
))}
</div>
{cameras.length === 0 && (
<div className="text-center py-12 text-gray-500">
<p className="text-lg mb-2">Henüz kamera eklenmemiş</p>
<p className="text-sm">Yukarıdaki butonu kullanarak ilk kameranızı ekleyin</p>
</div>
)}
</div>
</div>
)}
{/* Site Settings Section */}
{activeSection === 'site-settings' && siteSettings && (
<div className="space-y-6">
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="mb-6">
<h3 className="text-2xl font-bold text-[#003366]">Site Ayarları</h3>
<p className="text-gray-600 mt-1">İletişim bilgileri ve sosyal medya bağlantılarını yönetin</p>
</div>
<form onSubmit={(e) => {
e.preventDefault();
dataStore.updateSiteSettings(siteSettings);
alert('Site ayarları kaydedildi!');
}} className="space-y-8">
{/* Company Info */}
<div className="border-b pb-6">
<h4 className="text-lg font-bold text-[#003366] mb-4">Şirket Bilgileri</h4>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Kısa Ad</label>
<input
type="text"
value={siteSettings.companyInfo.name}
onChange={(e) => setSiteSettings({
...siteSettings,
companyInfo: { ...siteSettings.companyInfo, name: e.target.value }
})}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Tam Ünvan</label>
<input
type="text"
value={siteSettings.companyInfo.fullName}
onChange={(e) => setSiteSettings({
...siteSettings,
companyInfo: { ...siteSettings.companyInfo, fullName: e.target.value }
})}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Kuruluş Yılı</label>
<input
type="text"
value={siteSettings.companyInfo.foundedYear}
onChange={(e) => setSiteSettings({
...siteSettings,
companyInfo: { ...siteSettings.companyInfo, foundedYear: e.target.value }
})}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
/>
</div>
</div>
</div>
{/* Contact Info */}
<div className="border-b pb-6">
<h4 className="text-lg font-bold text-[#003366] mb-4">İletişim Bilgileri</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Telefon</label>
<input
type="text"
value={siteSettings.contact.phone}
onChange={(e) => setSiteSettings({
...siteSettings,
contact: { ...siteSettings.contact, phone: e.target.value }
})}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">E-posta</label>
<input
type="email"
value={siteSettings.contact.email}
onChange={(e) => setSiteSettings({
...siteSettings,
contact: { ...siteSettings.contact, email: e.target.value }
})}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">KEP Adresi</label>
<input
type="text"
value={siteSettings.contact.kep}
onChange={(e) => setSiteSettings({
...siteSettings,
contact: { ...siteSettings.contact, kep: e.target.value }
})}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-semibold text-gray-700 mb-2">Adres</label>
<textarea
value={siteSettings.contact.address}
onChange={(e) => setSiteSettings({
...siteSettings,
contact: { ...siteSettings.contact, address: e.target.value }
})}
rows={3}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent resize-none"
/>
</div>
</div>
</div>
{/* Social Media */}
<div>
<h4 className="text-lg font-bold text-[#003366] mb-4">Sosyal Medya</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Facebook</label>
<input
type="url"
value={siteSettings.social.facebook}
onChange={(e) => setSiteSettings({
...siteSettings,
social: { ...siteSettings.social, facebook: e.target.value }
})}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
placeholder="https://facebook.com/..."
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Twitter</label>
<input
type="url"
value={siteSettings.social.twitter}
onChange={(e) => setSiteSettings({
...siteSettings,
social: { ...siteSettings.social, twitter: e.target.value }
})}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
placeholder="https://twitter.com/..."
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Instagram</label>
<input
type="url"
value={siteSettings.social.instagram}
onChange={(e) => setSiteSettings({
...siteSettings,
social: { ...siteSettings.social, instagram: e.target.value }
})}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
placeholder="https://instagram.com/..."
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">YouTube</label>
<input
type="url"
value={siteSettings.social.youtube}
onChange={(e) => setSiteSettings({
...siteSettings,
social: { ...siteSettings.social, youtube: e.target.value }
})}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
placeholder="https://youtube.com/..."
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-semibold text-gray-700 mb-2">LinkedIn (Opsiyonel)</label>
<input
type="url"
value={siteSettings.social.linkedin || ''}
onChange={(e) => setSiteSettings({
...siteSettings,
social: { ...siteSettings.social, linkedin: e.target.value }
})}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
placeholder="https://linkedin.com/..."
/>
</div>
</div>
</div>
<div className="flex justify-end pt-4 border-t">
<button
type="submit"
className="px-6 py-3 bg-linear-to-r from-[#004B87] to-[#00B4D8] text-white rounded-lg font-semibold hover:shadow-lg transition-all"
>
Değişiklikleri Kaydet
</button>
</div>
</form>
</div>
</div>
)}
{activeSection !== 'overview' && activeSection !== 'slider' && activeSection !== 'news' && activeSection !== 'media' && activeSection !== 'documents' && activeSection !== 'metro-line' && activeSection !== 'live-stream' && activeSection !== 'cameras' && activeSection !== 'faqs' && activeSection !== 'messages' && activeSection !== 'site-settings' && (
<div className="bg-white rounded-xl shadow-sm p-8 text-center">
<div className="text-6xl mb-4">
{menuItems.find(item => item.id === activeSection)?.icon}
@@ -1495,18 +1986,6 @@ export default function Dashboard() {
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Görsel URL *</label>
<input
type="url"
value={editingSlide.image}
onChange={(e) => setEditingSlide({ ...editingSlide, image: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
placeholder="https://example.com/image.jpg"
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Buton Metni</label>
@@ -1539,7 +2018,7 @@ export default function Dashboard() {
className="w-4 h-4 text-[#00B4D8] border-gray-300 rounded focus:ring-[#00B4D8]"
/>
<label htmlFor="slideActive" className="text-sm font-medium text-gray-700">
Slider\'ı aktif yap (Ana sayfada göster)
Slider&apos;ı aktif yap (Ana sayfada göster)
</label>
</div>
@@ -1640,13 +2119,13 @@ export default function Dashboard() {
<label className="block text-sm font-semibold text-gray-700 mb-2">Kategori *</label>
<select
value={editingNewsItem.category}
onChange={(e) => setEditingNewsItem({ ...editingNewsItem, category: e.target.value })}
onChange={(e) => setEditingNewsItem({ ...editingNewsItem, category: e.target.value as 'construction' | 'announcements' | 'events' })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
required
>
<option value="construction">İnşaat</option>
<option value="announcement">Duyuru</option>
<option value="event">Etkinlik</option>
<option value="announcements">Duyuru</option>
<option value="events">Etkinlik</option>
</select>
</div>
@@ -1773,13 +2252,13 @@ export default function Dashboard() {
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Medya URL *</label>
<label className="block text-sm font-semibold text-gray-700 mb-2">{editingMediaItem.type === 'video' ? 'Video URL *' : 'Thumbnail URL *'}</label>
<input
type="url"
value={editingMediaItem.url}
onChange={(e) => setEditingMediaItem({ ...editingMediaItem, url: e.target.value })}
value={editingMediaItem.type === 'video' ? editingMediaItem.videoUrl || '' : editingMediaItem.thumbnail}
onChange={(e) => setEditingMediaItem(editingMediaItem.type === 'video' ? { ...editingMediaItem, videoUrl: e.target.value } : { ...editingMediaItem, thumbnail: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
placeholder="https://example.com/video.mp4"
placeholder={editingMediaItem.type === 'video' ? "https://www.youtube.com/embed/VIDEO_ID" : "https://example.com/image.jpg"}
required
/>
</div>
@@ -1869,14 +2348,14 @@ export default function Dashboard() {
<label className="block text-sm font-semibold text-gray-700 mb-2">Dosya Tipi *</label>
<select
value={editingDocument.type}
onChange={(e) => setEditingDocument({ ...editingDocument, type: e.target.value })}
onChange={(e) => setEditingDocument({ ...editingDocument, type: e.target.value as 'PDF' | 'DWG' | 'XLSX' | 'DOCX' })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
required
>
<option value="pdf">PDF</option>
<option value="doc">Word</option>
<option value="xls">Excel</option>
<option value="image">Görsel</option>
<option value="PDF">PDF</option>
<option value="DOCX">Word</option>
<option value="XLSX">Excel</option>
<option value="DWG">DWG</option>
</select>
</div>
@@ -1896,14 +2375,15 @@ export default function Dashboard() {
<label className="block text-sm font-semibold text-gray-700 mb-2">Kategori *</label>
<select
value={editingDocument.category}
onChange={(e) => setEditingDocument({ ...editingDocument, category: e.target.value })}
onChange={(e) => setEditingDocument({ ...editingDocument, category: e.target.value as 'ihale' | 'teknik' | 'cevresel' | 'raporlar' | 'guvenlik' })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#00B4D8] focus:border-transparent"
required
>
<option value="technical">Teknik</option>
<option value="administrative">İdari</option>
<option value="legal">Hukuki</option>
<option value="financial">Mali</option>
<option value="ihale">İhale</option>
<option value="teknik">Teknik</option>
<option value="cevresel">Çevresel</option>
<option value="raporlar">Raporlar</option>
<option value="guvenlik">Güvenlik</option>
</select>
</div>

View File

@@ -1,6 +1,7 @@
'use client';
import { useState } from 'react';
import { dataStore } from '@/lib/dataStore';
interface ComplaintFormProps {
onClose: () => void;
@@ -11,7 +12,7 @@ interface FormData {
email: string;
phone: string;
subject: string;
type: string;
type: 'sikayet' | 'oneri' | 'bilgi';
message: string;
}
@@ -21,15 +22,40 @@ export default function ComplaintForm({ onClose }: ComplaintFormProps) {
email: '',
phone: '',
subject: '',
type: 'dilek',
type: 'oneri',
message: ''
});
const handleSubmit = (e: React.FormEvent) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const [showSuccess, setShowSuccess] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
alert('Form gönderildi! (Demo)');
handleReset();
onClose();
setIsSubmitting(true);
try {
// dataStore'a kaydet
dataStore.addMessage({
name: formData.name,
email: formData.email,
phone: formData.phone,
subject: formData.subject,
type: formData.type,
message: formData.message
});
setShowSuccess(true);
setTimeout(() => {
handleReset();
setShowSuccess(false);
onClose();
}, 2000);
} catch (error) {
console.error('Form gönderilirken hata:', error);
alert('Mesaj gönderilirken bir hata oluştu. Lütfen tekrar deneyin.');
} finally {
setIsSubmitting(false);
}
};
const handleReset = () => {
@@ -38,7 +64,7 @@ export default function ComplaintForm({ onClose }: ComplaintFormProps) {
email: '',
phone: '',
subject: '',
type: 'dilek',
type: 'oneri',
message: ''
});
};
@@ -70,6 +96,20 @@ export default function ComplaintForm({ onClose }: ComplaintFormProps) {
</button>
</div>
{/* Başarı Mesajı */}
{showSuccess && (
<div className="bg-green-50 border-l-4 border-green-500 p-4 mb-6 animate-fade-in">
<div className="flex items-center">
<svg className="w-5 h-5 text-green-500 mr-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
<p className="text-sm font-medium text-green-800">
Mesajınız başarıyla gönderildi! En kısa sürede size dönüş yapılacaktır.
</p>
</div>
</div>
)}
{/* Bilgilendirme */}
<div className="bg-blue-50 border-l-4 border-[#00B4D8] p-4 mb-6">
<div className="flex items-start">
@@ -94,9 +134,9 @@ export default function ComplaintForm({ onClose }: ComplaintFormProps) {
<input
type="radio"
name="type"
value="dilek"
checked={formData.type === 'dilek'}
onChange={(e) => setFormData({...formData, type: e.target.value})}
value="oneri"
checked={formData.type === 'oneri'}
onChange={(e) => setFormData({...formData, type: e.target.value as 'oneri'})}
className="w-4 h-4 text-[#00B4D8] border-gray-300 focus:ring-[#00B4D8]"
/>
<span className="ml-2 text-gray-700">Dilek / Öneri</span>
@@ -107,11 +147,22 @@ export default function ComplaintForm({ onClose }: ComplaintFormProps) {
name="type"
value="sikayet"
checked={formData.type === 'sikayet'}
onChange={(e) => setFormData({...formData, type: e.target.value})}
onChange={(e) => setFormData({...formData, type: e.target.value as 'sikayet'})}
className="w-4 h-4 text-[#00B4D8] border-gray-300 focus:ring-[#00B4D8]"
/>
<span className="ml-2 text-gray-700">Şikayet</span>
</label>
<label className="flex items-center cursor-pointer">
<input
type="radio"
name="type"
value="bilgi"
checked={formData.type === 'bilgi'}
onChange={(e) => setFormData({...formData, type: e.target.value as 'bilgi'})}
className="w-4 h-4 text-[#00B4D8] border-gray-300 focus:ring-[#00B4D8]"
/>
<span className="ml-2 text-gray-700">Bilgi Talebi</span>
</label>
</div>
</div>
@@ -215,17 +266,31 @@ export default function ComplaintForm({ onClose }: ComplaintFormProps) {
<div className="flex flex-col sm:flex-row gap-4 pt-4">
<button
type="submit"
className="flex-1 px-6 py-3 bg-[#00B4D8] text-white rounded-lg hover:bg-[#004B87] transition-colors font-semibold shadow-lg hover:shadow-xl flex items-center justify-center space-x-2"
disabled={isSubmitting}
className="flex-1 px-6 py-3 bg-[#00B4D8] text-white rounded-lg hover:bg-[#004B87] transition-colors font-semibold shadow-lg hover:shadow-xl flex items-center justify-center space-x-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
</svg>
<span>Gönder</span>
{isSubmitting ? (
<>
<svg className="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>Gönderiliyor...</span>
</>
) : (
<>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
</svg>
<span>Gönder</span>
</>
)}
</button>
<button
type="button"
onClick={handleReset}
className="px-6 py-3 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors font-semibold"
disabled={isSubmitting}
className="px-6 py-3 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors font-semibold disabled:opacity-50 disabled:cursor-not-allowed"
>
Temizle
</button>

View File

@@ -1,10 +1,21 @@
'use client';
import { useState, useEffect } from 'react';
import { dataStore, type SiteSettings } from '@/lib/dataStore';
interface ContactSectionProps {
onClose: () => void;
}
export default function ContactSection({ onClose }: ContactSectionProps) {
const [settings, setSettings] = useState<SiteSettings | null>(null);
useEffect(() => {
setSettings(dataStore.getSiteSettings());
}, []);
if (!settings) return null;
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-8 md:pt-32 pb-8">
<div className="bg-white rounded-2xl shadow-2xl p-6 lg:p-8">
@@ -42,8 +53,7 @@ export default function ContactSection({ onClose }: ContactSectionProps) {
<div className="flex-1">
<h3 className="text-lg font-bold text-[#004B87] mb-2">ADRES</h3>
<p className="text-gray-700 leading-relaxed">
Emniyet Mah. Hipodrom Caddesi No: 5<br />
Yenimahalle / Ankara
{settings.contact.address}
</p>
</div>
</div>
@@ -60,10 +70,10 @@ export default function ContactSection({ onClose }: ContactSectionProps) {
<div className="flex-1">
<h3 className="text-lg font-bold text-[#004B87] mb-2">KEP ADRESİ</h3>
<a
href="mailto:ankarabuyuksehirbelediyesi@hs01.kep.tr"
href={`mailto:${settings.contact.kep}`}
className="text-gray-700 hover:text-[#00B4D8] transition-colors break-all"
>
ankarabuyuksehirbelediyesi@hs01.kep.tr
{settings.contact.kep}
</a>
</div>
</div>
@@ -80,10 +90,30 @@ export default function ContactSection({ onClose }: ContactSectionProps) {
<div className="flex-1">
<h3 className="text-lg font-bold text-[#004B87] mb-2">TELEFON</h3>
<a
href="tel:+903125071000"
href={`tel:${settings.contact.phone.replace(/[^0-9+]/g, '')}`}
className="text-xl font-semibold text-gray-700 hover:text-[#00B4D8] transition-colors"
>
+90 (312) 507 10 00
{settings.contact.phone}
</a>
</div>
</div>
</div>
{/* E-posta Kartı */}
<div className="bg-linear-to-br from-purple-50 to-purple-100 rounded-xl p-6 hover:shadow-lg transition-all duration-300">
<div className="flex items-start space-x-4">
<div className="w-16 h-16 bg-purple-600 rounded-xl flex items-center justify-center shrink-0">
<svg className="w-8 h-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207" />
</svg>
</div>
<div className="flex-1">
<h3 className="text-lg font-bold text-[#004B87] mb-2">E-POSTA</h3>
<a
href={`mailto:${settings.contact.email}`}
className="text-gray-700 hover:text-[#00B4D8] transition-colors break-all"
>
{settings.contact.email}
</a>
</div>
</div>

View File

@@ -1,7 +1,19 @@
'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { dataStore, type SiteSettings } from '@/lib/dataStore';
export default function Footer() {
const [settings, setSettings] = useState<SiteSettings | null>(null);
useEffect(() => {
setSettings(dataStore.getSiteSettings());
}, []);
if (!settings) return null;
return (
<footer className="bg-linear-to-r from-[#003366] via-[#004B87] to-[#003366] text-white mt-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
@@ -80,14 +92,14 @@ export default function Footer() {
<svg className="w-5 h-5 text-[#00B4D8] mt-0.5 shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clipRule="evenodd" />
</svg>
<span className="text-gray-300 text-sm">Emniyet, Hipodrom Cd. No:5, 06430 Yenimahalle/Ankara</span>
<span className="text-gray-300 text-sm">{settings.contact.address}</span>
</li>
<li className="flex items-center space-x-3">
<svg className="w-5 h-5 text-[#00B4D8] shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" />
</svg>
<a href="tel:+903124440644" className="text-gray-300 hover:text-white transition-colors text-sm">
(0312) 507 10 00
<a href={`tel:${settings.contact.phone.replace(/[^0-9+]/g, '')}`} className="text-gray-300 hover:text-white transition-colors text-sm">
{settings.contact.phone}
</a>
</li>
<li className="flex items-center space-x-3">
@@ -95,8 +107,8 @@ export default function Footer() {
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
</svg>
<a href="mailto:ankarabuyuksehirbelediyesi@hs01.kep.tr" className="text-gray-300 hover:text-white transition-colors text-sm">
ankarabuyuksehirbelediyesi@hs01.kep.tr
<a href={`mailto:${settings.contact.kep}`} className="text-gray-300 hover:text-white transition-colors text-sm">
{settings.contact.kep}
</a>
</li>
</ul>
@@ -107,7 +119,7 @@ export default function Footer() {
<div className="flex space-x-3">
{/* Facebook */}
<a
href="https://www.facebook.com/ankarabbld/?locale=tr_TR"
href={settings.social.facebook}
target="_blank"
rel="noopener noreferrer"
className="w-9 h-9 rounded-full bg-[#00B4D8] hover:bg-[#48CAE4] flex items-center justify-center transition-colors"
@@ -119,7 +131,7 @@ export default function Footer() {
</a>
{/* Twitter/X */}
<a
href="https://x.com/ankarabbld?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor"
href={settings.social.twitter}
target="_blank"
rel="noopener noreferrer"
className="w-9 h-9 rounded-full bg-[#00B4D8] hover:bg-[#48CAE4] flex items-center justify-center transition-colors"
@@ -131,7 +143,7 @@ export default function Footer() {
</a>
{/* Instagram */}
<a
href="https://www.instagram.com/ankarabbld/"
href={settings.social.instagram}
target="_blank"
rel="noopener noreferrer"
className="w-9 h-9 rounded-full bg-[#00B4D8] hover:bg-[#48CAE4] flex items-center justify-center transition-colors"
@@ -142,17 +154,19 @@ export default function Footer() {
</svg>
</a>
{/* LinkedIn */}
<a
href="https://www.linkedin.com/company/ankara-b%C3%BCy%C3%BCk%C5%9Fehir-belediyesi/?originalSubdomain=tr"
target="_blank"
rel="noopener noreferrer"
className="w-9 h-9 rounded-full bg-[#00B4D8] hover:bg-[#48CAE4] flex items-center justify-center transition-colors"
aria-label="LinkedIn"
>
{settings.social.linkedin && (
<a
href={settings.social.linkedin}
target="_blank"
rel="noopener noreferrer"
className="w-9 h-9 rounded-full bg-[#00B4D8] hover:bg-[#48CAE4] flex items-center justify-center transition-colors"
aria-label="LinkedIn"
>
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/>
</svg>
</a>
</a>
)}
</div>
</div>
</div>
@@ -161,7 +175,7 @@ export default function Footer() {
{/* Alt Bilgi */}
<div className="border-t border-white/10 mt-8 pt-8 text-center">
<p className="text-gray-400 text-sm">
© 2025 T.C. Ankara Büyükşehir Belediyesi - A2 Metro Hattı İnşaat Projesi. Tüm hakları saklıdır.
© {new Date().getFullYear()} {settings.companyInfo.fullName} - Tüm hakları saklıdır.
</p>
</div>
</div>

View File

@@ -1,9 +1,15 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { dataStore, type SiteSettings } from '@/lib/dataStore';
export default function Header() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [settings, setSettings] = useState<SiteSettings | null>(null);
useEffect(() => {
setSettings(dataStore.getSiteSettings());
}, []);
const menuItems = [
{ href: '/', icon: '🏠', title: 'Ana Sayfa', desc: 'Projeye genel bakış' },
@@ -107,26 +113,58 @@ export default function Header() {
<div className="flex items-center space-x-4">
<div className="flex-1">
<p className="text-white/70 text-xs mb-2">Sosyal Medya</p>
<div className="flex space-x-2">
{[
{ icon: 'M23 3a10.9 10.9 0 01-3.14 1.53 4.48 4.48 0 00-7.86 3v1A10.66 10.66 0 013 4s-4 9 5 13a11.64 11.64 0 01-7 2c9 5 20 0 20-11.5a4.5 4.5 0 00-.08-.83A7.72 7.72 0 0023 3z', url: 'https://twitter.com/Ankara_BB' },
{ icon: 'M18 2h-3a5 5 0 00-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 011-1h3z', url: 'https://www.facebook.com/ankarabb' },
{ icon: 'M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z', url: 'https://www.instagram.com/ankarabb' },
{ icon: 'M16 8a6 6 0 016 6v7h-4v-7a2 2 0 00-2-2 2 2 0 00-2 2v7h-4v-7a6 6 0 016-6zM2 9h4v12H2z M4 2a2 2 0 100 4 2 2 0 000-4z', url: 'https://www.linkedin.com/company/ankara-bb' },
].map((social, i) => (
<a
key={i}
href={social.url}
target="_blank"
rel="noopener noreferrer"
className="w-10 h-10 rounded-lg bg-white/10 hover:bg-white/20 flex items-center justify-center transition-all hover:scale-110"
>
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d={social.icon} />
</svg>
</a>
))}
</div>
{settings && (
<div className="flex space-x-2">
{settings.social.twitter && (
<a
href={settings.social.twitter}
target="_blank"
rel="noopener noreferrer"
className="w-10 h-10 rounded-lg bg-white/10 hover:bg-white/20 flex items-center justify-center transition-all hover:scale-110"
>
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M23 3a10.9 10.9 0 01-3.14 1.53 4.48 4.48 0 00-7.86 3v1A10.66 10.66 0 013 4s-4 9 5 13a11.64 11.64 0 01-7 2c9 5 20 0 20-11.5a4.5 4.5 0 00-.08-.83A7.72 7.72 0 0023 3z" />
</svg>
</a>
)}
{settings.social.facebook && (
<a
href={settings.social.facebook}
target="_blank"
rel="noopener noreferrer"
className="w-10 h-10 rounded-lg bg-white/10 hover:bg-white/20 flex items-center justify-center transition-all hover:scale-110"
>
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M18 2h-3a5 5 0 00-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 011-1h3z" />
</svg>
</a>
)}
{settings.social.instagram && (
<a
href={settings.social.instagram}
target="_blank"
rel="noopener noreferrer"
className="w-10 h-10 rounded-lg bg-white/10 hover:bg-white/20 flex items-center justify-center transition-all hover:scale-110"
>
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z" />
</svg>
</a>
)}
{settings.social.linkedin && (
<a
href={settings.social.linkedin}
target="_blank"
rel="noopener noreferrer"
className="w-10 h-10 rounded-lg bg-white/10 hover:bg-white/20 flex items-center justify-center transition-all hover:scale-110"
>
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M16 8a6 6 0 016 6v7h-4v-7a2 2 0 00-2-2 2 2 0 00-2 2v7h-4v-7a6 6 0 016-6zM2 9h4v12H2z M4 2a2 2 0 100 4 2 2 0 000-4z" />
</svg>
</a>
)}
</div>
)}
</div>
</div>
</div>

View File

@@ -39,28 +39,51 @@ export default function MetroLine() {
}, []);
const getStationStyle = (index: number, station: MetroStation) => {
// Seçili istasyonun index'ini bul
const selectedIndex = selectedStationId
? stations.findIndex(s => s.id === selectedStationId)
: -1;
// Animasyonda şu an bulunulan istasyon (sarı - metro treni burada)
if (index === currentStationIndex) {
return 'w-16 h-16 bg-[#F59E0B] border-4 border-white shadow-2xl scale-110';
}
if (station.status === 'completed' || index < currentStationIndex) {
// Seçili istasyondan ÖNCE olanlar - Tamamlandı (yeşil)
if (selectedIndex !== -1 && index < selectedIndex) {
return 'w-14 h-14 bg-green-500 border-4 border-white shadow-lg';
}
if (station.status === 'in-progress') {
// Seçili istasyon - Şu an inşaat aşamasında (mavi)
if (index === selectedIndex) {
return 'w-14 h-14 bg-blue-500 border-4 border-white shadow-lg';
}
// Seçili istasyondan SONRA olanlar - Planlı (gri)
return 'w-12 h-12 bg-gray-300 border-4 border-dashed border-gray-400 shadow-lg opacity-60';
};
const getTextStyle = (index: number, station: MetroStation) => {
const selectedIndex = selectedStationId
? stations.findIndex(s => s.id === selectedStationId)
: -1;
// Animasyonda şu an bulunulan istasyon
if (index === currentStationIndex) {
return 'text-[#F59E0B] font-bold';
}
if (station.status === 'completed' || index < currentStationIndex) {
// Seçili istasyondan ÖNCE olanlar - Tamamlandı
if (selectedIndex !== -1 && index < selectedIndex) {
return 'text-green-600 font-bold';
}
if (station.status === 'in-progress') {
// Seçili istasyon - İnşaat aşamasında
if (index === selectedIndex) {
return 'text-blue-600 font-bold';
}
// Seçili istasyondan SONRA olanlar - Planlı
return 'text-gray-500 font-semibold';
};
@@ -103,28 +126,36 @@ export default function MetroLine() {
{/* Duraklar Grid - Desktop */}
<div className="grid grid-cols-4 lg:grid-cols-8 gap-6 relative">
{stations.map((station, index) => (
<div key={station.id} className="flex flex-col items-center">
<div className={`relative z-10 rounded-full flex items-center justify-center mb-3 transition-all duration-500 ${getStationStyle(index, station)}`}>
{index === currentStationIndex ? (
<>
{/* Metro simgesi - animasyonlu */}
<svg className="w-8 h-8 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h2l2-2h4l2 2h2v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-4-4-8-4zM7.5 17c-.83 0-1.5-.67-1.5-1.5S6.67 14 7.5 14s1.5.67 1.5 1.5S8.33 17 7.5 17zm3.5-6H6V6h5v5zm5.5 6c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm1.5-6h-5V6h5v5z"/>
{stations.map((station, index) => {
const selectedIndex = selectedStationId
? stations.findIndex(s => s.id === selectedStationId)
: -1;
return (
<div key={station.id} className="flex flex-col items-center">
<div className={`relative z-10 rounded-full flex items-center justify-center mb-3 transition-all duration-500 ${getStationStyle(index, station)}`}>
{index === currentStationIndex ? (
<>
{/* Metro simgesi - animasyonlu */}
<svg className="w-8 h-8 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h2l2-2h4l2 2h2v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-4-4-8-4zM7.5 17c-.83 0-1.5-.67-1.5-1.5S6.67 14 7.5 14s1.5.67 1.5 1.5S8.33 17 7.5 17zm3.5-6H6V6h5v5zm5.5 6c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm1.5-6h-5V6h5v5z"/>
</svg>
{/* Pulse efekti */}
<div className="absolute inset-0 rounded-full bg-[#F59E0B] animate-ping opacity-30"></div>
</>
) : (selectedIndex !== -1 && index < selectedIndex) ? (
// Tamamlandı - Yeşil check
<svg className="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
</svg>
{/* Pulse efekti */}
<div className="absolute inset-0 rounded-full bg-[#F59E0B] animate-ping opacity-30"></div>
</>
) : station.status === 'completed' || index < currentStationIndex ? (
<svg className="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
</svg>
) : station.status === 'in-progress' ? (
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h2l2-2h4l2 2h2v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-4-4-8-4z"/>
</svg>
) : (
<svg className="w-5 h-5 text-gray-500" fill="currentColor" viewBox="0 0 24 24">
) : (index === selectedIndex) ? (
// Seçili istasyon - İnşaat aşamasında
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h2l2-2h4l2 2h2v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-4-4-8-4z"/>
</svg>
) : (
// Planlı - Gri saat
<svg className="w-5 h-5 text-gray-500" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>
)}
@@ -134,44 +165,50 @@ export default function MetroLine() {
</span>
<span className={`text-xs transition-all duration-300 ${
index === currentStationIndex ? 'text-[#F59E0B] font-semibold' :
station.status === 'completed' || index < currentStationIndex ? 'text-green-600' :
station.status === 'in-progress' ? 'text-blue-600' : 'text-gray-400'
(selectedIndex !== -1 && index < selectedIndex) ? 'text-green-600' :
(index === selectedIndex) ? 'text-blue-600' : 'text-gray-400'
}`}>
{index === currentStationIndex ? '🚇 Metro Burada' :
station.status === 'completed' || index < currentStationIndex ? '✓ Tamamlandı' :
station.status === 'in-progress' ? '🔄 Devam Ediyor' :
(selectedIndex !== -1 && index < selectedIndex) ? '✓ Tamamlandı' :
(index === selectedIndex) ? '🔄 Devam Ediyor' :
'◯ Planlı'}
</span>
</div>
))}
);
})}
</div>
</div>
{/* Metro Hat - Mobil (Dikey Liste) */}
<div className="md:hidden space-y-3">
{stations.map((station, index) => (
<div key={station.id} className={`flex items-center space-x-3 p-3 rounded-lg transition-all duration-500 ${
{stations.map((station, index) => {
const selectedIndex = selectedStationId
? stations.findIndex(s => s.id === selectedStationId)
: -1;
return (
<div key={station.id} className={`flex items-center space-x-3 p-3 rounded-lg transition-all duration-500 ${
index === currentStationIndex ? 'bg-[#F59E0B]/10 border-2 border-[#F59E0B]' :
station.status === 'completed' || index < currentStationIndex ? 'bg-green-50 border border-green-200' :
station.status === 'in-progress' ? 'bg-blue-50 border border-blue-200' :
(selectedIndex !== -1 && index < selectedIndex) ? 'bg-green-50 border border-green-200' :
(index === selectedIndex) ? 'bg-blue-50 border border-blue-200' :
'bg-gray-50 border border-gray-200'
}`}>
{/* İkon */}
<div className={`rounded-full flex items-center justify-center transition-all duration-500 shrink-0 ${
index === currentStationIndex ? 'w-12 h-12 bg-[#F59E0B]' :
station.status === 'completed' || index < currentStationIndex ? 'w-10 h-10 bg-green-500' :
station.status === 'in-progress' ? 'w-10 h-10 bg-blue-500' :
(selectedIndex !== -1 && index < selectedIndex) ? 'w-10 h-10 bg-green-500' :
(index === selectedIndex) ? 'w-10 h-10 bg-blue-500' :
'w-10 h-10 bg-gray-300'
}`}>
{index === currentStationIndex ? (
<svg className="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h2l2-2h4l2 2h2v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-4-4-8-4zM7.5 17c-.83 0-1.5-.67-1.5-1.5S6.67 14 7.5 14s1.5.67 1.5 1.5S8.33 17 7.5 17zm3.5-6H6V6h5v5zm5.5 6c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm1.5-6h-5V6h5v5z"/>
</svg>
) : station.status === 'completed' || index < currentStationIndex ? (
) : (selectedIndex !== -1 && index < selectedIndex) ? (
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
</svg>
) : station.status === 'in-progress' ? (
) : (index === selectedIndex) ? (
<svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h2l2-2h4l2 2h2v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-4-4-8-4z"/>
</svg>
@@ -186,21 +223,21 @@ export default function MetroLine() {
<div className="flex-1">
<h4 className={`font-bold text-sm ${
index === currentStationIndex ? 'text-[#F59E0B]' :
station.status === 'completed' || index < currentStationIndex ? 'text-green-700' :
station.status === 'in-progress' ? 'text-blue-700' :
(selectedIndex !== -1 && index < selectedIndex) ? 'text-green-700' :
(index === selectedIndex) ? 'text-blue-700' :
'text-gray-600'
}`}>
{station.name}
</h4>
<p className={`text-xs ${
index === currentStationIndex ? 'text-[#F59E0B]' :
station.status === 'completed' || index < currentStationIndex ? 'text-green-600' :
station.status === 'in-progress' ? 'text-blue-600' :
(selectedIndex !== -1 && index < selectedIndex) ? 'text-green-600' :
(index === selectedIndex) ? 'text-blue-600' :
'text-gray-500'
}`}>
{index === currentStationIndex ? '🚇 Metro Burada' :
station.status === 'completed' || index < currentStationIndex ? '✓ Tamamlandı' :
station.status === 'in-progress' ? '🔄 Devam Ediyor' :
(selectedIndex !== -1 && index < selectedIndex) ? '✓ Tamamlandı' :
(index === selectedIndex) ? '🔄 Devam Ediyor' :
'◯ Planlı'}
</p>
</div>
@@ -214,7 +251,8 @@ export default function MetroLine() {
}`} style={{ top: '100%', marginLeft: '-1px' }}></div>
)}
</div>
))}
);
})}
</div>
{/* Alt Bilgi */}

View File

@@ -13,6 +13,62 @@ export interface SliderItem {
active: boolean;
}
export interface LiveStreamConfig {
url: string;
active: boolean;
title?: string;
}
export interface Message {
id: string;
name: string;
email: string;
phone: string;
subject: string;
type: 'sikayet' | 'oneri' | 'bilgi';
message: string;
date: string;
read: boolean;
}
export interface FAQ {
id: number;
question: string;
answer: string;
order: number;
}
export interface Camera {
id: number;
name: string;
location: string;
videoUrl: string;
status: 'online' | 'offline';
viewers?: number;
order: number;
}
export interface SiteSettings {
contact: {
phone: string;
email: string;
address: string;
kep: string;
};
social: {
facebook: string;
twitter: string;
instagram: string;
youtube: string;
linkedin?: string;
};
companyInfo: {
name: string;
fullName: string;
foundedYear: string;
};
}
// Default slider data
export const defaultSliderData: SliderItem[] = [
{
@@ -49,6 +105,98 @@ export const defaultSliderData: SliderItem[] = [
}
];
// Default FAQ data
export const defaultFAQData: FAQ[] = [
{
id: 1,
question: 'A2 Metro Hattı projesi ne zaman tamamlanacak?',
answer: 'A2 Metro Hattı projesinin 2026 yılı sonunda tamamlanması planlanmaktadır. Proje, Dikimevi-Natoyolu güzergâhında 6.5 kilometre uzunluğunda ve 5 istasyonlu bir metro hattı inşasını kapsamaktadır.',
order: 1
},
{
id: 2,
question: 'Metro hattı hangi istasyonları kapsayacak?',
answer: 'A2 Metro Hattı aşağıdaki istasyonları içerecektir: Dikimevi İstasyonu, Tuzluçayır İstasyonu, Natoyolu İstasyonu ve diğer ara istasyonlar. Toplam 5 istasyon bulunacaktır.',
order: 2
},
{
id: 3,
question: 'İnşaat çalışmaları sırasında trafik nasıl etkilenecek?',
answer: 'İnşaat çalışmaları sırasında geçici trafik düzenlemeleri uygulanacaktır. Alternatif güzergâhlar belirlenecek ve yönlendirme levhaları yerleştirilecektir. Vatandaşlarımızın anlayışına sığınıyoruz.',
order: 3
},
{
id: 4,
question: 'Proje maliyeti ne kadar?',
answer: 'A2 Metro Hattı projesinin toplam maliyeti yaklaşık 2.5 milyar TL olarak belirlenmiştir. Bu maliyet, tünel kazısı, istasyon inşaatı, ray döşeme ve elektrik-elektronik sistemleri kapsamaktadır.',
order: 4
},
{
id: 5,
question: 'Çevreye etkisi ne olacak?',
answer: 'Proje, çevre dostu teknolojiler kullanılarak gerçekleştirilmektedir. Çevresel Etki Değerlendirme (ÇED) raporu hazırlanmış ve gerekli izinler alınmıştır. İnşaat sırasında toz kontrolü ve gürültü önleme tedbirleri uygulanacaktır.',
order: 5
},
{
id: 6,
question: 'İstasyonlar hangi özelliklere sahip olacak?',
answer: 'İstasyonlar modern mimari tasarımla, enerji verimliliği, erişilebilirlik ve güvenlik ön planda tutularak inşa edilecektir. Güneş enerjisi panelleri, LED aydınlatma ve akıllı havalandırma sistemleri kullanılacaktır.',
order: 6
},
{
id: 7,
question: 'İnşaatta hangi teknolojiler kullanılacak?',
answer: 'Proje kapsamında TBM (Tunnel Boring Machine) tünel açma makinesi kullanılacaktır. Bu teknoloji sayesinde kazı çalışmaları daha hızlı ve güvenli şekilde gerçekleştirilecektir.',
order: 7
},
{
id: 8,
question: 'Proje ilerleme durumu nasıl takip edilebilir?',
answer: 'Bu web sitesi üzerinden canlı yayın, haberler ve medya galerisi bölümlerinden proje ilerleme durumunu takip edebilirsiniz. Ayrıca aylık ilerleme raporları yayınlanmaktadır.',
order: 8
}
];
// Default Camera data
export const defaultCameraData: Camera[] = [
{
id: 1,
name: 'Dikimevi İstasyonu - Ana Giriş',
location: 'Dikimevi',
videoUrl: 'https://www.youtube.com/embed/b9q88QDEcKg?autoplay=1',
status: 'online',
viewers: 1243,
order: 1
},
{
id: 2,
name: 'Tuzluçayır İstasyonu - İnşaat Sahası',
location: 'Tuzluçayır',
videoUrl: 'https://www.youtube.com/embed/b9q88QDEcKg?autoplay=1',
status: 'online',
viewers: 856,
order: 2
},
{
id: 3,
name: 'A2 Metro Hattı - Tünel Kazı Çalışması',
location: 'Mamak',
videoUrl: 'https://www.youtube.com/embed/b9q88QDEcKg?autoplay=1',
status: 'online',
viewers: 2134,
order: 3
},
{
id: 4,
name: 'İstasyon Binası İç Mekan',
location: 'Dikimevi',
videoUrl: 'https://www.youtube.com/embed/b9q88QDEcKg?autoplay=1',
status: 'online',
viewers: 534,
order: 4
}
];
// LocalStorage keys
const KEYS = {
SLIDER: 'a2metro_slider',
@@ -56,6 +204,11 @@ const KEYS = {
MEDIA: 'a2metro_media',
DOCUMENTS: 'a2metro_documents',
METRO_STATIONS: 'a2metro_stations',
LIVE_STREAM: 'a2metro_live_stream',
SITE_SETTINGS: 'a2metro_site_settings',
MESSAGES: 'a2metro_messages',
FAQS: 'a2metro_faqs',
CAMERAS: 'a2metro_cameras'
};
// Helper functions
@@ -183,6 +336,207 @@ export const dataStore = {
dataStore.setMetroStations(updated);
},
// Live Stream
getLiveStream: (): LiveStreamConfig => {
if (!isBrowser) {
return {
url: 'https://www.youtube.com/embed/jfKfPfyJRdk?si=example',
active: true,
title: 'Canlı Yayın'
};
}
const stored = localStorage.getItem(KEYS.LIVE_STREAM);
return stored ? JSON.parse(stored) : {
url: 'https://www.youtube.com/embed/jfKfPfyJRdk?si=example',
active: true,
title: 'Canlı Yayın'
};
},
setLiveStream: (config: LiveStreamConfig) => {
if (isBrowser) {
localStorage.setItem(KEYS.LIVE_STREAM, JSON.stringify(config));
}
},
// Messages
getMessages: (): Message[] => {
if (!isBrowser) return [];
const stored = localStorage.getItem(KEYS.MESSAGES);
return stored ? JSON.parse(stored) : [];
},
addMessage: (message: Omit<Message, 'id' | 'date' | 'read'>) => {
const messages = dataStore.getMessages();
const newMessage: Message = {
...message,
id: Date.now().toString(),
date: new Date().toISOString(),
read: false
};
messages.unshift(newMessage);
if (isBrowser) {
localStorage.setItem(KEYS.MESSAGES, JSON.stringify(messages));
}
return newMessage;
},
markMessageAsRead: (id: string) => {
const messages = dataStore.getMessages();
const updated = messages.map(msg =>
msg.id === id ? { ...msg, read: true } : msg
);
if (isBrowser) {
localStorage.setItem(KEYS.MESSAGES, JSON.stringify(updated));
}
},
deleteMessage: (id: string) => {
const messages = dataStore.getMessages();
const filtered = messages.filter(msg => msg.id !== id);
if (isBrowser) {
localStorage.setItem(KEYS.MESSAGES, JSON.stringify(filtered));
}
},
// Site Settings
getSiteSettings: (): SiteSettings => {
if (!isBrowser) {
return {
contact: {
phone: '+90 (312) 123 45 67',
email: 'info@a2metro.com.tr',
address: 'Ankara Teknokent, Cyberpark C Blok Kat:3 No:301 Çankaya/ANKARA',
kep: 'a2metro@hs03.kep.tr'
},
social: {
facebook: 'https://facebook.com/a2metro',
twitter: 'https://twitter.com/a2metro',
instagram: 'https://instagram.com/a2metro',
youtube: 'https://youtube.com/@a2metro',
linkedin: 'https://linkedin.com/company/a2metro'
},
companyInfo: {
name: 'A2 Metro',
fullName: 'A2 Metro Yapı ve İnşaat A.Ş.',
foundedYear: '2010'
}
};
}
const stored = localStorage.getItem(KEYS.SITE_SETTINGS);
if (stored) return JSON.parse(stored);
// Default settings
const defaultSettings: SiteSettings = {
contact: {
phone: '+90 (312) 123 45 67',
email: 'info@a2metro.com.tr',
address: 'Ankara Teknokent, Cyberpark C Blok Kat:3 No:301 Çankaya/ANKARA',
kep: 'a2metro@hs03.kep.tr'
},
social: {
facebook: 'https://facebook.com/a2metro',
twitter: 'https://twitter.com/a2metro',
instagram: 'https://instagram.com/a2metro',
youtube: 'https://youtube.com/@a2metro',
linkedin: 'https://linkedin.com/company/a2metro'
},
companyInfo: {
name: 'A2 Metro',
fullName: 'A2 Metro Yapı ve İnşaat A.Ş.',
foundedYear: '2010'
}
};
localStorage.setItem(KEYS.SITE_SETTINGS, JSON.stringify(defaultSettings));
return defaultSettings;
},
updateSiteSettings: (settings: Partial<SiteSettings>) => {
const current = dataStore.getSiteSettings();
const updated = {
...current,
...settings,
contact: { ...current.contact, ...(settings.contact || {}) },
social: { ...current.social, ...(settings.social || {}) },
companyInfo: { ...current.companyInfo, ...(settings.companyInfo || {}) }
};
if (isBrowser) {
localStorage.setItem(KEYS.SITE_SETTINGS, JSON.stringify(updated));
}
},
// FAQs
getFAQs: (): FAQ[] => {
if (!isBrowser) return defaultFAQData;
const stored = localStorage.getItem(KEYS.FAQS);
return stored ? JSON.parse(stored) : defaultFAQData;
},
setFAQs: (faqs: FAQ[]) => {
if (isBrowser) {
localStorage.setItem(KEYS.FAQS, JSON.stringify(faqs));
}
},
addFAQ: (faq: Omit<FAQ, 'id'>) => {
const faqs = dataStore.getFAQs();
const newFAQ: FAQ = {
...faq,
id: faqs.length > 0 ? Math.max(...faqs.map(f => f.id)) + 1 : 1
};
dataStore.setFAQs([...faqs, newFAQ]);
return newFAQ;
},
updateFAQ: (id: number, updates: Partial<FAQ>) => {
const faqs = dataStore.getFAQs();
const updated = faqs.map(faq =>
faq.id === id ? { ...faq, ...updates } : faq
);
dataStore.setFAQs(updated);
},
deleteFAQ: (id: number) => {
const faqs = dataStore.getFAQs();
dataStore.setFAQs(faqs.filter(faq => faq.id !== id));
},
// Cameras
getCameras: (): Camera[] => {
if (!isBrowser) return defaultCameraData;
const stored = localStorage.getItem(KEYS.CAMERAS);
return stored ? JSON.parse(stored) : defaultCameraData;
},
setCameras: (cameras: Camera[]) => {
if (isBrowser) {
localStorage.setItem(KEYS.CAMERAS, JSON.stringify(cameras));
}
},
addCamera: (camera: Omit<Camera, 'id'>) => {
const cameras = dataStore.getCameras();
const newCamera: Camera = {
...camera,
id: cameras.length > 0 ? Math.max(...cameras.map(c => c.id)) + 1 : 1
};
dataStore.setCameras([...cameras, newCamera]);
return newCamera;
},
updateCamera: (id: number, updates: Partial<Camera>) => {
const cameras = dataStore.getCameras();
const updated = cameras.map(camera =>
camera.id === id ? { ...camera, ...updates } : camera
);
dataStore.setCameras(updated);
},
deleteCamera: (id: number) => {
const cameras = dataStore.getCameras();
dataStore.setCameras(cameras.filter(camera => camera.id !== id));
},
// Reset all data
resetAll: () => {
if (isBrowser) {
@@ -191,6 +545,11 @@ export const dataStore = {
localStorage.removeItem(KEYS.MEDIA);
localStorage.removeItem(KEYS.DOCUMENTS);
localStorage.removeItem(KEYS.METRO_STATIONS);
localStorage.removeItem(KEYS.LIVE_STREAM);
localStorage.removeItem(KEYS.SITE_SETTINGS);
localStorage.removeItem(KEYS.MESSAGES);
localStorage.removeItem(KEYS.FAQS);
localStorage.removeItem(KEYS.CAMERAS);
}
}
};