Build
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
10
app/page.tsx
10
app/page.tsx
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 Açı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'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'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'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'ı 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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
359
lib/dataStore.ts
359
lib/dataStore.ts
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user