erp-platform/ui/src/views/crm/components/CustomerEdit.tsx
2025-11-03 23:31:28 +03:00

691 lines
29 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 } from 'react'
import { useParams, useNavigate, Link } from 'react-router-dom'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import {
FaArrowLeft,
FaSave,
FaTimes,
FaBuilding,
FaUser,
FaMapMarkerAlt,
FaIdCard,
FaCreditCard,
FaExclamationTriangle,
} from 'react-icons/fa'
import classNames from 'classnames'
import { CustomerSegmentEnum, CustomerTypeEnum } from '../../../types/crm'
import { mockBusinessParties, mockBusinessPartyNew } from '../../../mocks/mockBusinessParties'
import { BusinessParty, BusinessPartyStatusEnum, PaymentTerms } from '../../../types/common'
import { Container } from '@/components/shared'
import { ROUTES_ENUM } from '@/routes/route.constant'
import {
getBusinessPartyStatusText,
getCustomerSegmentText,
getCustomerTypeText,
getPaymentTermsText,
} from '@/utils/erp'
import { mockCurrencies } from '@/mocks/mockCurrencies'
const CustomerEdit: React.FC = () => {
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const queryClient = useQueryClient()
const [activeTab, setActiveTab] = useState('basic')
const [isDirty, setIsDirty] = useState(false)
const {
data: customer,
isLoading,
error,
} = useQuery({
queryKey: ['customer', id],
queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 300))
const found = mockBusinessParties.find((c) => c.id === id)
if (!found) throw new Error('Müşteri bulunamadı')
return found
},
})
const [formData, setFormData] = useState<BusinessParty>(mockBusinessPartyNew)
// Initialize form data when customer is loaded
React.useEffect(() => {
if (customer) {
setFormData(mockBusinessParties.find((c) => c.id === id) || mockBusinessPartyNew)
}
}, [customer, id])
const updateMutation = useMutation({
mutationFn: async (data: BusinessParty) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
// Simulate API call
return { ...customer, ...data }
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['customers'] })
queryClient.invalidateQueries({ queryKey: ['customer', id] })
setIsDirty(false)
navigate(ROUTES_ENUM.protected.crm.customersDetail.replace(':id', id || 'new'))
},
})
const handleInputChange = (field: string, value: string | number) => {
const keys = field.split('.')
if (keys.length === 2) {
const [section, subField] = keys
setFormData((prev: any) => {
if (section === 'primaryContact') {
return {
...prev,
primaryContact: {
...prev.primaryContact,
[subField]: value,
},
}
} else if (section === 'address') {
return {
...prev,
address: {
...prev.address,
[subField]: value,
},
}
}
return prev
})
} else {
setFormData((prev) => ({
...prev,
[field]: value,
}))
}
setIsDirty(true)
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
updateMutation.mutate(formData)
}
const handleCancel = () => {
if (isDirty) {
if (window.confirm('Değişiklikleriniz kaydedilmedi. Çıkmak istediğinizden emin misiniz?')) {
navigate(ROUTES_ENUM.protected.crm.customersDetail.replace(':id', id!))
}
} else {
navigate(ROUTES_ENUM.protected.crm.customersDetail.replace(':id', id!))
}
}
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 p-6">
<div className="mx-auto">
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-1/4 mb-6"></div>
<div className="bg-white rounded-lg shadow p-6">
<div className="space-y-4">
{[...Array(8)].map((_, i) => (
<div key={i} className="h-10 bg-gray-200 rounded"></div>
))}
</div>
</div>
</div>
</div>
</div>
)
}
if (error || !customer) {
return (
<div className="min-h-screen bg-gray-50 p-6">
<div className="mx-auto">
<div className="bg-red-50 border border-red-200 rounded-lg p-6">
<div className="flex items-center">
<FaExclamationTriangle className="h-8 w-8 text-red-600 mr-3" />
<div>
<h3 className="text-lg font-medium text-red-800">Müşteri Bulunamadı</h3>
<p className="text-red-600">Düzenlemek istediğiniz müşteri mevcut değil.</p>
</div>
</div>
<div className="mt-4">
<button
onClick={() => navigate(ROUTES_ENUM.protected.crm.customers)}
className="inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
>
<FaArrowLeft className="w-4 h-4 mr-2" />
Müşteri Listesine Dön
</button>
</div>
</div>
</div>
</div>
)
}
const tabs = [
{ id: 'basic', label: 'Temel Bilgiler', icon: FaBuilding },
{ id: 'contact', label: 'İletişim', icon: FaUser },
{ id: 'business', label: 'İş Bilgileri', icon: FaIdCard },
{ id: 'financial', label: 'Finansal', icon: FaCreditCard },
]
return (
<Container>
<div className="space-y-2">
<form onSubmit={handleSubmit}>
{/* Header */}
<div className="bg-white border-b border-gray-200">
<div className="mx-auto px-4 py-3">
{/* Breadcrumb */}
<div className="flex items-center space-x-2 text-xs text-gray-500 mb-3">
<Link to={ROUTES_ENUM.protected.crm.customers} className="hover:text-blue-600">
Müşteriler
</Link>
<span>/</span>
<Link
to={ROUTES_ENUM.protected.crm.customersDetail.replace(':id', id || 'new')}
className="hover:text-blue-600"
>
{customer.name}
</Link>
<span>/</span>
<span className="text-gray-900">Düzenle</span>
</div>
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<button
type="button"
onClick={handleCancel}
className="p-1.5 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
<FaArrowLeft className="w-5 h-5" />
</button>
<div className="flex items-center space-x-4">
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center text-white shadow-lg">
<FaBuilding className="w-8 h-8" />
</div>
<div>
<h2 className="text-2xl font-bold text-gray-900">Müşteri Düzenle</h2>
<p className="text-sm text-gray-600">{customer.name}</p>
</div>
</div>
</div>
<div className="flex items-center space-x-2">
<button
type="button"
onClick={handleCancel}
className="inline-flex items-center px-3 py-1.5 text-sm border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
<FaTimes className="w-4 h-4 mr-2" />
İptal
</button>
<button
type="submit"
disabled={!isDirty || updateMutation.isPending}
className={classNames(
'inline-flex items-center px-3 py-1.5 text-sm rounded-lg transition-colors',
isDirty && !updateMutation.isPending
? 'bg-blue-600 text-white hover:bg-blue-700'
: 'bg-gray-300 text-gray-500 cursor-not-allowed',
)}
>
{updateMutation.isPending ? (
<>
<div className="animate-spin w-4 h-4 mr-2 border-2 border-white border-t-transparent rounded-full"></div>
Kaydediliyor...
</>
) : (
<>
<FaSave className="w-4 h-4 mr-2" />
Kaydet
</>
)}
</button>
</div>
</div>
</div>
</div>
{/* Tabs */}
<div className="bg-white border-b border-gray-200">
<div className="mx-auto px-4">
<nav className="flex space-x-8">
{tabs.map((tab) => (
<button
key={tab.id}
type="button"
onClick={() => setActiveTab(tab.id)}
className={classNames(
'flex items-center space-x-2 py-3 px-1 border-b-2 font-medium text-sm transition-colors',
activeTab === tab.id
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
)}
>
<tab.icon className="w-4 h-4" />
<span>{tab.label}</span>
</button>
))}
</nav>
</div>
</div>
{/* Form Content */}
<div className="mx-auto py-4">
{activeTab === 'basic' && (
<div className="bg-white rounded-lg shadow p-4">
<h3 className="text-base font-semibold text-gray-900 mb-4 flex items-center">
<FaBuilding className="w-5 h-5 mr-2" />
Temel Bilgiler
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Müşteri Kodu *
</label>
<input
type="text"
value={formData.code}
onChange={(e) => handleInputChange('code', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Şirket Adı *
</label>
<input
type="text"
value={formData.name}
onChange={(e) => handleInputChange('name', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Müşteri Tipi
</label>
<select
value={formData.customerType}
onChange={(e) => handleInputChange('customerType', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
{Object.values(CustomerTypeEnum).map((type) => (
<option key={type} value={type}>
{getCustomerTypeText(type)}
</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Sektör</label>
<input
type="text"
value={formData.industry}
onChange={(e) => handleInputChange('industry', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Teknoloji, İmalat, Hizmet vb."
/>
</div>
<div className="md:col-span-2">
<label className="block text-xs font-medium text-gray-700 mb-1">Website</label>
<input
type="url"
value={formData.website}
onChange={(e) => handleInputChange('website', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="https://www.ornek.com"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Müşteri Durumu
</label>
<select
value={formData.status}
onChange={(e) => handleInputChange('status', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
{Object.values(BusinessPartyStatusEnum).map((status) => (
<option key={status} value={status}>
{getBusinessPartyStatusText(status)}
</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Müşteri Segmenti
</label>
<select
value={formData.customerSegment}
onChange={(e) => handleInputChange('customerSegment', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
{Object.values(CustomerSegmentEnum).map((segment) => (
<option key={segment} value={segment}>
{getCustomerSegmentText(segment)}
</option>
))}
</select>
</div>
</div>
</div>
)}
{activeTab === 'contact' && (
<div className="space-y-4">
{/* Primary Contact */}
<div className="bg-white rounded-lg shadow p-4">
<h3 className="text-base font-semibold text-gray-900 mb-4 flex items-center">
<FaUser className="w-5 h-5 mr-2" />
Ana İletişim Kişisi
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Ad *</label>
<input
type="text"
value={formData.primaryContact?.firstName}
onChange={(e) =>
handleInputChange('primaryContact.firstName', e.target.value)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Soyad *
</label>
<input
type="text"
value={formData.primaryContact?.lastName}
onChange={(e) =>
handleInputChange('primaryContact.lastName', e.target.value)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Ünvan</label>
<input
type="text"
value={formData.primaryContact?.title}
onChange={(e) => handleInputChange('primaryContact.title', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Genel Müdür, Satış Direktörü vb."
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Departman
</label>
<input
type="text"
value={formData.primaryContact?.department}
onChange={(e) =>
handleInputChange('primaryContact.department', e.target.value)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Satış, Pazarlama, İnsan Kaynakları vb."
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
E-posta *
</label>
<input
type="email"
value={formData.primaryContact?.email}
onChange={(e) => handleInputChange('primaryContact.email', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Telefon
</label>
<input
type="tel"
value={formData.primaryContact?.phoneNumber}
onChange={(e) => handleInputChange('primaryContact.phoneNumber', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="+90 (212) 555 0123"
/>
</div>
<div className="md:col-span-2">
<label className="block text-xs font-medium text-gray-700 mb-1">
Mobil Telefon
</label>
<input
type="tel"
value={formData.primaryContact?.mobileNumber}
onChange={(e) => handleInputChange('primaryContact.mobile', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="+90 (555) 123 4567"
/>
</div>
</div>
</div>
{/* Address */}
<div className="bg-white rounded-lg shadow p-4">
<h3 className="text-base font-semibold text-gray-900 mb-4 flex items-center">
<FaMapMarkerAlt className="w-5 h-5 mr-2" />
Adres Bilgileri
</h3>
<div className="grid grid-cols-1 gap-4">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Adres *
</label>
<textarea
value={formData.address?.street}
onChange={(e) => handleInputChange('address.street', e.target.value)}
rows={3}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Sokak, cadde, mahalle, bina no vb."
required
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Şehir *
</label>
<input
type="text"
value={formData.address?.city}
onChange={(e) => handleInputChange('address.city', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
İl/Eyalet
</label>
<input
type="text"
value={formData.address?.state}
onChange={(e) => handleInputChange('address.state', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Posta Kodu
</label>
<input
type="text"
value={formData.address?.postalCode}
onChange={(e) => handleInputChange('address.postalCode', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Ülke *
</label>
<input
type="text"
value={formData.address?.country}
onChange={(e) => handleInputChange('address.country', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
</div>
</div>
</div>
</div>
)}
{activeTab === 'business' && (
<div className="bg-white rounded-lg shadow p-4">
<h3 className="text-base font-semibold text-gray-900 mb-4 flex items-center">
<FaIdCard className="w-5 h-5 mr-2" />
İş Bilgileri
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Vergi Numarası
</label>
<input
type="text"
value={formData.taxNumber}
onChange={(e) => handleInputChange('taxNumber', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="1234567890"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Sicil Numarası
</label>
<input
type="text"
value={formData.registrationNumber}
onChange={(e) => handleInputChange('registrationNumber', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="123456789"
/>
</div>
<div className="md:col-span-2">
<label className="block text-xs font-medium text-gray-700 mb-1">
Atanmış Satış Temsilcisi
</label>
<input
type="text"
value={formData.assignedSalesRep}
onChange={(e) => handleInputChange('assignedSalesRep', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Satış temsilcisi adı"
/>
</div>
</div>
</div>
)}
{activeTab === 'financial' && (
<div className="bg-white rounded-lg shadow p-4">
<h3 className="text-base font-semibold text-gray-900 mb-4 flex items-center">
<FaCreditCard className="w-5 h-5 mr-2" />
Finansal Bilgiler
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Kredi Limiti ()
</label>
<input
type="number"
value={formData.creditLimit}
onChange={(e) => handleInputChange('creditLimit', Number(e.target.value))}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
min="0"
step="1000"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Para Birimi
</label>
<select
value={formData.currency}
onChange={(e) => handleInputChange('currency', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
{mockCurrencies.map((currency) => (
<option key={currency.value} value={currency.value}>
{currency.value} - {currency.label}
</option>
))}
</select>
</div>
<div className="md:col-span-2">
<label className="block text-xs font-medium text-gray-700 mb-1">
Ödeme Koşulları
</label>
<select
value={formData.paymentTerms}
onChange={(e) => handleInputChange('paymentTerms', e.target.value)}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
{Object.values(PaymentTerms).map((term) => (
<option key={term} value={term}>
{getPaymentTermsText(term)}
</option>
))}
</select>
</div>
</div>
</div>
)}
</div>
</form>
</div>
</Container>
)
}
export default CustomerEdit