erp-platform/ui/src/views/crm/components/SalesOrderView.tsx
2025-09-16 15:33:57 +03:00

416 lines
18 KiB
TypeScript
Raw 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.

import React, { useState, useEffect } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import {
FaEdit,
FaArrowLeft,
FaPrint,
FaDownload,
FaShoppingCart,
FaCalendar,
FaBuilding,
FaUser,
FaPhone,
FaEnvelope,
FaMapMarkerAlt,
FaClipboardList,
FaCalculator,
FaTruck,
FaFileInvoiceDollar,
FaExclamationCircle,
} from 'react-icons/fa'
import { CrmSalesOrder, CrmSalesOrderItem, SaleOrderItemStatusEnum } from '../../../types/crm'
import { mockSalesOrders } from '../../../mocks/mockSalesOrders'
import {
getSaleOrderItemStatusnfo,
getSaleOrderStatusColor,
getSaleOrderStatusText,
} from '../../../utils/erp'
import { Container } from '@/components/shared'
import { ROUTES_ENUM } from '@/routes/route.constant'
const SalesOrderView: React.FC = () => {
const navigate = useNavigate()
const { id } = useParams()
const [order, setOrder] = useState<CrmSalesOrder | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
if (id) {
// Find the order from mock data
setTimeout(() => {
const foundOrder = mockSalesOrders.find((order) => order.id === id)
if (foundOrder) {
setOrder(foundOrder)
}
setLoading(false)
}, 1000)
}
}, [id])
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
)
}
if (!order) {
return (
<div className="flex flex-col items-center justify-center h-64">
<FaExclamationCircle className="text-4xl text-red-500 mb-4" />
<h2 className="text-xl font-semibold text-gray-900 mb-2">Sipariş Bulunamadı</h2>
<p className="text-gray-600 mb-4">Belirtilen sipariş mevcut değil.</p>
<button
onClick={() => navigate(ROUTES_ENUM.protected.crm.salesOrders)}
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
>
Sipariş Listesine Dön
</button>
</div>
)
}
return (
<Container>
<div className="space-y-2">
{/* Header */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-4">
<button
onClick={() => navigate(ROUTES_ENUM.protected.crm.salesOrders)}
className="flex items-center text-gray-600 hover:text-gray-800 transition-colors"
>
<FaArrowLeft className="mr-2" />
Geri
</button>
<div className="h-6 border-l border-gray-300"></div>
<div>
<h1 className="text-xl font-bold text-gray-900 flex items-center">
<FaShoppingCart className="mr-3 text-blue-600" />
{order.orderNumber}
</h1>
<p className="text-xs text-gray-500 mt-1">Satış Siparişi Detayları</p>
</div>
</div>
<div className="flex items-center space-x-2">
<span
className={`inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium ${getSaleOrderStatusColor(
order.status,
)}`}
>
{getSaleOrderStatusText(order.status)}
</span>
<button
onClick={() =>
navigate(ROUTES_ENUM.protected.crm.salesOrdersEdit.replace(':id', order.id))
}
className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 transition-colors flex items-center"
>
<FaEdit className="mr-2" />
Düzenle
</button>
<button className="bg-gray-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-gray-700 transition-colors flex items-center">
<FaPrint className="mr-2" />
Yazdır
</button>
<button className="bg-green-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-green-700 transition-colors flex items-center">
<FaDownload className="mr-2" />
İndir
</button>
</div>
</div>
{/* Order Info Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3 mb-4">
<div className="bg-gradient-to-r from-blue-50 to-blue-100 p-3 rounded-lg border border-blue-200">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-blue-800">Sipariş Tarihi</p>
<p className="text-base font-bold text-blue-900">
{new Date(order.orderDate).toLocaleDateString('tr-TR')}
</p>
</div>
<FaCalendar className="text-2xl text-blue-600" />
</div>
</div>
<div className="bg-gradient-to-r from-green-50 to-green-100 p-3 rounded-lg border border-green-200">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-green-800">Talep Edilen Tarih</p>
<p className="text-base font-bold text-green-900">
{new Date(order.requestedDeliveryDate).toLocaleDateString('tr-TR')}
</p>
</div>
<FaCalendar className="text-2xl text-green-600" />
</div>
</div>
<div className="bg-gradient-to-r from-purple-50 to-purple-100 p-3 rounded-lg border border-purple-200">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-purple-800">Onaylanan Tarih</p>
<p className="text-base font-bold text-purple-900">
{order.confirmedDeliveryDate
? new Date(order.confirmedDeliveryDate).toLocaleDateString('tr-TR')
: 'Belirtilmemiş'}
</p>
</div>
<FaCalendar className="text-2xl text-purple-600" />
</div>
</div>
<div className="bg-gradient-to-r from-amber-50 to-amber-100 p-3 rounded-lg border border-amber-200">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-amber-800">Toplam Tutar</p>
<p className="text-base font-bold text-amber-900">
{order.totalAmount.toLocaleString('tr-TR')} {order.currency}
</p>
</div>
<FaCalculator className="text-2xl text-amber-600" />
</div>
</div>
</div>
{/* Additional Info */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 text-sm">
<div>
<span className="font-medium text-gray-700">Ödeme Koşulları:</span>
<p className="text-gray-900">{order.paymentTerms}</p>
</div>
<div>
<span className="font-medium text-gray-700">Para Birimi:</span>
<p className="text-gray-900">{order.currency}</p>
</div>
</div>
<div className="mt-3 pt-3 border-t border-gray-200">
<div className="text-sm text-gray-600">
<span className="font-medium">Son Güncelleme:</span>
<div className="mt-1">
{order.lastModificationTime
? new Date(order.lastModificationTime).toLocaleString('tr-TR')
: 'Güncelleme yok'}
</div>
</div>
</div>
</div>
{/* Customer Information */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
<h2 className="text-lg font-semibold text-gray-900 mb-3 flex items-center">
<FaBuilding className="mr-3 text-blue-600" />
Müşteri Bilgileri
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h3 className="text-base font-medium text-gray-800 mb-2 flex items-center">
<FaUser className="mr-2 text-gray-600" />
Müşteri Detayları
</h3>
<div className="space-y-2">
<div>
<span className="text-sm font-medium text-gray-600">Firma Adı:</span>
<p className="text-sm font-medium text-gray-900">
{order.customer?.name || 'Müşteri bilgisi yok'}
</p>
</div>
<div>
<span className="text-sm font-medium text-gray-600">İletişim Kişisi:</span>
<p className="text-sm text-gray-900">
{order.customer?.primaryContact?.fullName || 'Belirtilmemiş'}
</p>
</div>
<div className="flex items-center text-sm text-gray-600">
<FaPhone className="mr-2" />
<span>{order.customer?.primaryContact?.phone || 'Belirtilmemiş'}</span>
</div>
<div className="flex items-center text-sm text-gray-600">
<FaEnvelope className="mr-2" />
<span>{order.customer?.primaryContact?.email || 'Belirtilmemiş'}</span>
</div>
</div>
</div>
<div>
<h3 className="text-base font-medium text-gray-800 mb-2 flex items-center">
<FaMapMarkerAlt className="mr-2 text-gray-600" />
Teslimat Adresi
</h3>
<div className="text-sm text-gray-900 space-y-1">
<p>{order.deliveryAddress.street}</p>
<p>
{order.deliveryAddress.city}, {order.deliveryAddress.state}
</p>
<p>{order.deliveryAddress.postalCode}</p>
<p>{order.deliveryAddress.country}</p>
</div>
</div>
</div>
</div>
{/* Order Items */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
<h2 className="text-lg font-semibold text-gray-900 mb-3 flex items-center">
<FaClipboardList className="mr-3 text-blue-600" />
Sipariş Kalemleri
</h2>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Malzeme
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Miktar
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Teslim Edilen
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Birim Fiyat
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Toplam
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{order.items.map((item: CrmSalesOrderItem) => {
const itemStatus = getSaleOrderItemStatusnfo(item.status)
return (
<tr key={item.id} className="hover:bg-gray-50">
<td className="px-4 py-2 whitespace-nowrap">
<div>
<div className="text-sm font-medium text-gray-900">
{item.material?.code || item.materialId}
</div>
<div className="text-xs text-gray-500">
{item.material?.name || item.description}
</div>
</div>
</td>
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900">
{item.quantity} {item.material?.baseUnit?.name || 'Adet'}
</td>
<td className="px-4 py-2 whitespace-nowrap">
<div className="font-medium">
{item.deliveredQuantity} {item.material?.baseUnit?.name || 'Adet'}
</div>
<div className="text-xs text-gray-500">
{((item.deliveredQuantity / item.quantity) * 100).toFixed(1)}% tamamlandı
</div>
</td>
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900">
{item.unitPrice.toLocaleString('tr-TR')} {order.currency}
</td>
<td className="px-4 py-2 whitespace-nowrap text-sm font-medium text-gray-900">
{item.totalAmount.toLocaleString('tr-TR')} {order.currency}
</td>
<td className="px-4 py-2 whitespace-nowrap">
<span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${itemStatus.color}`}
>
<itemStatus.icon className={`mr-1 ${itemStatus.iconColor}`} />
{itemStatus.label}
</span>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
{/* Financial Summary */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
<h2 className="text-lg font-semibold text-gray-900 mb-3 flex items-center">
<FaCalculator className="mr-3 text-blue-600" />
Mali Özet
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<div className="flex justify-between py-1.5 border-b border-gray-100">
<span className="text-sm font-medium text-gray-600">Ara Toplam:</span>
<span className="text-sm text-gray-900">
{order.subtotal.toLocaleString('tr-TR')} {order.currency}
</span>
</div>
<div className="flex justify-between py-1.5 border-b border-gray-100">
<span className="text-sm font-medium text-gray-600">İndirim:</span>
<span className="text-sm text-gray-900">
-{order.discountAmount.toLocaleString('tr-TR')} {order.currency}
</span>
</div>
<div className="flex justify-between py-1.5 border-b border-gray-100">
<span className="text-sm font-medium text-gray-600">KDV:</span>
<span className="text-sm text-gray-900">
{order.taxAmount.toLocaleString('tr-TR')} {order.currency}
</span>
</div>
<div className="flex justify-between py-2 border-t-2 border-gray-200">
<span className="text-base font-bold text-gray-900">Genel Toplam:</span>
<span className="text-base font-bold text-gray-900">
{order.totalAmount.toLocaleString('tr-TR')} {order.currency}
</span>
</div>
</div>
<div className="space-y-4">
<div className="bg-blue-50 p-4 rounded-lg border border-blue-200">
<h3 className="text-sm font-medium text-blue-800 mb-2 flex items-center">
<FaTruck className="mr-2" />
Teslimat Durumu
</h3>
<div className="text-sm text-blue-900">
<p>Toplam Kalem: {order.items.length}</p>
<p>
Teslim Edilenler:{' '}
{
order.items.filter(
(item) => item.status === SaleOrderItemStatusEnum.Delivered,
).length
}
</p>
<p>
Bekleyenler:{' '}
{
order.items.filter(
(item) => item.status !== SaleOrderItemStatusEnum.Delivered,
).length
}
</p>
</div>
</div>
<div className="bg-green-50 p-4 rounded-lg border border-green-200">
<h3 className="text-sm font-medium text-green-800 mb-2 flex items-center">
<FaFileInvoiceDollar className="mr-2" />
Faturalama Durumu
</h3>
<div className="text-sm text-green-900">
<p>Teslimat Sayısı: {order.deliveries?.length || 0}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</Container>
)
}
export default SalesOrderView