308 lines
10 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import React, { useEffect, useState } from 'react'
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/navigation'
import Link from 'next/link'
import Cookies from 'js-cookie'
interface Address {
id: string
street: string
city: string
postcode: string
latitude: number
longitude: number
order: number
}
interface Route {
id: string
createdAt: string
status: string
addresses: Address[]
}
interface RouteClientProps {
routeId: string
}
// İki nokta arası mesafeyi hesapla (Haversine formülü)
function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
const R = 6371; // Dünya'nın yarıçapı (km)
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return Math.round(R * c * 10) / 10; // 1 ondalık basamak
}
function toRad(value: number): number {
return value * Math.PI / 180;
}
// Toplam mesafeyi hesapla
function calculateTotalDistance(addresses: any[]): number {
let total = 0;
const sortedAddresses = addresses.sort((a, b) => a.order - b.order);
for (let i = 0; i < sortedAddresses.length - 1; i++) {
const current = sortedAddresses[i];
const next = sortedAddresses[i + 1];
total += calculateDistance(
current.latitude,
current.longitude,
next.latitude,
next.longitude
);
}
return Math.round(total * 10) / 10; // 1 ondalık basamak
}
export default function RouteClient({ routeId }: RouteClientProps) {
const router = useRouter()
const { data: session, status: sessionStatus } = useSession()
const [route, setRoute] = useState<Route | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [messages, setMessages] = useState<any>(null)
const [visitedAddresses, setVisitedAddresses] = useState<Set<string>>(new Set())
useEffect(() => {
const loadMessages = async () => {
const lang = Cookies.get('NEXT_LOCALE') || 'tr'
const messages = await import(`@/messages/${lang}.json`)
setMessages(messages.default)
}
loadMessages()
}, [])
useEffect(() => {
fetchRoute()
}, [routeId])
const fetchRoute = async () => {
try {
setLoading(true)
const response = await fetch(`/api/routes/${routeId}`, {
credentials: 'include'
})
if (!response.ok) {
throw new Error(messages?.routeDetails?.error || 'Rota yüklenirken bir hata oluştu')
}
const data = await response.json()
setRoute(data)
setError(null)
} catch (err) {
console.error('Rota getirme hatası:', err)
setError(err instanceof Error ? err.message : messages?.routeDetails?.error || 'Rota yüklenirken bir hata oluştu')
} finally {
setLoading(false)
}
}
const startRoute = async () => {
try {
const response = await fetch(`/api/routes/${routeId}/start`, {
method: 'POST',
credentials: 'include'
})
if (!response.ok) {
throw new Error(messages?.routeDetails?.startError || 'Rota başlatılırken bir hata oluştu')
}
await fetchRoute()
} catch (err) {
console.error('Rota başlatma hatası:', err)
setError(err instanceof Error ? err.message : messages?.routeDetails?.startError || 'Rota başlatılırken bir hata oluştu')
}
}
const handleAddressClick = async (addressId: string) => {
try {
const address = route?.addresses.find(a => a.id === addressId);
if (!address) return;
// Google Maps URL'sini oluştur
const googleMapsUrl = `https://www.google.com/maps/place/${encodeURIComponent(
`${address.street}, ${address.postcode} ${address.city}`
)}`;
// Yeni sekmede Google Maps'i aç
window.open(googleMapsUrl, '_blank', 'noopener,noreferrer');
// Ziyaret edilen adresleri güncelle
setVisitedAddresses(prev => new Set(Array.from(prev).concat(addressId)));
// Geçmişe kaydet
await fetch('/api/history', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
routeId,
addressId,
action: 'ADDRESS_VIEWED'
})
});
} catch (error) {
console.error('Adres görüntüleme hatası:', error);
}
};
if (!messages || sessionStatus === 'loading' || loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-indigo-500"></div>
</div>
)
}
if (sessionStatus === 'unauthenticated') {
router.push('/auth/login')
return null
}
if (error || !route) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h2 className="text-2xl font-bold mb-4">{messages.common.error}</h2>
<p className="text-red-600">{error || messages.routeDetails.notFound}</p>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-100 py-6">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center">
<div className="flex items-center space-x-4">
<Link
href="/dashboard/routes"
className="bg-gray-500 text-white px-4 py-2 rounded-md hover:bg-gray-600"
>
{messages.common.back}
</Link>
<h1 className="text-3xl font-bold text-gray-900">{messages.routeDetails.title}</h1>
</div>
{route?.status === 'CREATED' && (
<button
onClick={startRoute}
className="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700"
>
{messages.routeDetails.startDrive}
</button>
)}
</div>
<div className="mt-8 bg-white shadow overflow-hidden sm:rounded-lg">
<div className="px-4 py-5 sm:px-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">
{messages.routeDetails.routeInfo}
</h3>
<p className="mt-1 max-w-2xl text-sm text-gray-500">
{messages.routeDetails.createdAt}: {new Date(route.createdAt).toLocaleDateString()}
</p>
</div>
<div className="border-t border-gray-200">
<dl className="grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2">
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">
{String(messages?.routeDetails?.status || 'Durum')}
</dt>
<dd className="mt-1 text-sm text-gray-900">{route?.status}</dd>
</div>
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">
{String(messages?.routeDetails?.addressCount || 'Adres Sayısı')}
</dt>
<dd className="mt-1 text-sm text-gray-900">
{route?.addresses.length}
</dd>
</div>
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">
{String(messages?.routeDetails?.optimizationStatus || 'Optimizasyon Durumu')}
</dt>
<dd className="mt-1 text-sm text-gray-900">
{route?.status === 'OPTIMIZED'
? String(messages?.routeDetails?.status?.optimized || 'Optimize Edilmiş')
: String(messages?.routeDetails?.status?.notOptimized || 'Optimize Edilmemiş')}
</dd>
</div>
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">
{String(messages?.routeDetails?.totalDistance || 'Toplam Mesafe')}
</dt>
<dd className="mt-1 text-sm text-gray-900">
{calculateTotalDistance(route.addresses)} km
</dd>
</div>
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">
{String(messages?.routeDetails?.estimatedDuration)}
</dt>
<dd className="mt-1 text-sm text-gray-900">
{Math.round(calculateTotalDistance(route.addresses) / 30 * 60)} dakika
</dd>
</div>
</dl>
</div>
</div>
<div className="mt-8">
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
<div className="px-4 py-5 sm:px-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">
{messages.routeDetails.addresses}
</h3>
</div>
<div className="border-t border-gray-200">
<div className="space-y-4">
{route?.addresses.map((address, index) => (
<div
key={address.id}
onClick={() => handleAddressClick(address.id)}
className="flex items-center justify-between p-4 bg-gray-50 rounded-lg cursor-pointer hover:bg-gray-100"
>
<div className="flex items-center">
<span className="text-gray-500 mr-4">{index + 1}.</span>
<span className="text-gray-900">
{address.street}, {address.postcode} {address.city}
</span>
</div>
{visitedAddresses.has(address.id) && (
<svg
className="h-5 w-5 text-green-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
)}
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
)
}