import { getTenantByNameDetail } from '@/services/tenant.service' import { CustomTenantDto } from '@/proxy/config/models' import { FaArrowLeft, FaArrowRight, FaDollarSign, FaMoneyBillWave, FaBuilding, FaGlobe, FaEnvelope, FaMap, FaMapPin, FaPhone, FaSearch, FaUser, FaUserPlus, FaCheckCircle, FaSitemap, } from 'react-icons/fa' import React, { useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useLocalization } from '@/utils/hooks/useLocalization' import { ROUTES_ENUM } from '@/routes/route.constant' import { Button, Select } from '@/components/ui' import { CityDto, CountryDto, DistrictDto, getCities, getCountries, getDistricts, } from '@/services/location.service' import { useStoreActions, useStoreState } from '@/store/store' import type { TenantFormDraft } from '@/store/client.model' interface LocationOption { label: string value: string data: T } const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ const WEBSITE_PATTERN = /^(https?:\/\/)?(www\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(\/[^\s]*)?$/ const DEFAULT_PHONE_FORMAT = '000 000 000 000 000' const TURKEY_PHONE_FORMAT = '(000) 000-0000' const REQUIRED_NEW_CUSTOMER_FIELDS: Array = [ 'organizationName', 'founder', 'country', 'city', 'district', 'township', 'postalCode', 'address1', 'phoneNumber', 'email', 'taxOffice', 'vknTckn', ] const REQUIRED_EXISTING_CUSTOMER_FIELDS: Array = [ 'id', 'name', 'organizationName', 'founder', 'country', 'city', 'district', 'township', 'address1', 'email', ] const getPhoneFormat = (country?: CountryDto) => country?.phoneNumberFormat || (country?.phoneCode === 90 ? TURKEY_PHONE_FORMAT : DEFAULT_PHONE_FORMAT) const getPhoneMaxLength = (country?: CountryDto) => country?.phoneNumberMaxLength ?? getPhoneFormat(country).replace(/[^0]/g, '').length const getPhoneMinLength = (country?: CountryDto) => country?.phoneNumberMinLength ?? getPhoneMaxLength(country) const getNationalPhoneDigits = (value: string, country?: CountryDto) => { let digits = value.replace(/\D/g, '') if (!country) return digits const code = String(country.phoneCode) if (value.trimStart().startsWith('+') && digits.startsWith(code)) { digits = digits.slice(code.length) } return digits } const getPhoneValidationError = (value: string, country?: CountryDto) => { if (!country) return 'Lütfen önce ülke seçin.' const length = getNationalPhoneDigits(value, country).length const minLength = getPhoneMinLength(country) const maxLength = getPhoneMaxLength(country) if (length >= minLength && length <= maxLength) return '' return minLength === maxLength ? `Telefon numarası ${minLength} haneli olmalıdır.` : `Telefon numarası ${minLength}-${maxLength} hane arasında olmalıdır.` } const formatPhoneNumber = (value: string, country?: CountryDto) => { if (!country) return value.replace(/\D/g, '').slice(0, 15) const maxLength = getPhoneMaxLength(country) const digits = getNationalPhoneDigits(value, country).slice(0, maxLength) const format = getPhoneFormat(country) let digitIndex = 0 let nationalNumber = '' for (const character of format) { if (character === '0') { if (digitIndex >= digits.length) break nationalNumber += digits[digitIndex] digitIndex += 1 } else if (digitIndex === 0) { if (digits.length > 0) nationalNumber += character } else if (digitIndex < digits.length || character === ')') { nationalNumber += character } else { break } } return `+${country.phoneCode}${nationalNumber ? ` ${nationalNumber}` : ''}` } const normalizePhoneForStorage = (value?: string, country?: CountryDto) => value ? getNationalPhoneDigits(value, country) : value const isBlankValue = (value: unknown) => value === undefined || value === null || String(value).trim() === '' const hasRequiredFields = ( tenant: Partial, fields: Array, ) => fields.every((field) => !isBlankValue(tenant[field])) const normalizeCode = (value?: string) => value?.trim().toLowerCase() || '' const getDistrictPostalCode = (district?: DistrictDto) => district?.postalCode || (district as (DistrictDto & { PostalCode?: string }) | undefined)?.PostalCode || '' const getUniqueLocationOptions = ( items: T[], getKey: (item: T) => string, getLabel: (item: T) => string, ): LocationOption[] => { const seen = new Set() return items.reduce[]>((options, item) => { const key = getKey(item) if (!key || seen.has(key)) return options seen.add(key) options.push({ label: getLabel(item), value: key, data: item, }) return options }, []) } interface TenantFormProps { onSubmit: (tenant: CustomTenantDto) => void onBack: () => void } export const TenantForm: React.FC = ({ onSubmit }) => { const [isExisting, setIsExisting] = useState(true) const [formData, setFormData] = useState>({}) const [countries, setCountries] = useState([]) const [cities, setCities] = useState([]) const [districts, setDistricts] = useState([]) const [isLoadingCountries, setIsLoadingCountries] = useState(false) const [isLoadingCities, setIsLoadingCities] = useState(false) const [isLoadingDistricts, setIsLoadingDistricts] = useState(false) const [isLoadingTenant, setIsLoadingTenant] = useState(false) const [emailError, setEmailError] = useState('') const [websiteError, setWebsiteError] = useState('') const [phoneError, setPhoneError] = useState('') const [existingTenantError, setExistingTenantError] = useState('') const [formError, setFormError] = useState('') const [foundTenantName, setFoundTenantName] = useState('') const navigate = useNavigate() const { translate } = useLocalization() const { tenantData, tenantFormDraft } = useStoreState((state) => state.client.order) const { setTenantFormDraft, clearTenant } = useStoreActions((actions) => actions.client.order) const hasRestoredTenantData = useRef(false) useEffect(() => { if (hasRestoredTenantData.current) return hasRestoredTenantData.current = true const savedData: TenantFormDraft | null = tenantFormDraft || tenantData if (!savedData) return setIsExisting(savedData.isExisting ?? true) setFoundTenantName(savedData.foundTenantName || savedData.name || '') setFormData(savedData) }, [tenantData, tenantFormDraft]) useEffect(() => { const loadCountries = async () => { setIsLoadingCountries(true) try { const response = await getCountries() setCountries(response.data) const defaultCountry = response.data.find((country) => country.name === 'Türkiye') if (defaultCountry) { setFormData((prev) => prev.country ? prev : { ...prev, country: defaultCountry.name, city: '', district: '', township: '', postalCode: '', phoneNumber: `+${defaultCountry.phoneCode}`, }, ) setIsLoadingCities(true) try { const cityResponse = await getCities(defaultCountry.name) setCities(cityResponse.data) } catch (error) { console.error('Şehirler alınırken hata:', error) } finally { setIsLoadingCities(false) } } } catch (error) { console.error('Ülkeler alınırken hata:', error) } finally { setIsLoadingCountries(false) } } void loadCountries() }, []) useEffect(() => { if (Object.keys(formData).length === 0) return setTenantFormDraft({ ...formData, isExisting, foundTenantName, }) }, [formData, foundTenantName, isExisting, setTenantFormDraft]) useEffect(() => { const loadLocationOptions = async () => { if (!formData.country) return setIsLoadingCities(true) try { const cityResponse = await getCities(formData.country) setCities(cityResponse.data) } catch (error) { console.error('Şehirler alınırken hata:', error) } finally { setIsLoadingCities(false) } if (!formData.city) return setIsLoadingDistricts(true) try { const districtResponse = await getDistricts(formData.country, formData.city) setDistricts(districtResponse.data) } catch (error) { console.error('İlçeler alınırken hata:', error) } finally { setIsLoadingDistricts(false) } } void loadLocationOptions() }, [formData.country, formData.city]) const selectedCountry = countries.find((country) => country.name === formData.country) const phoneMask = selectedCountry ? `+${selectedCountry.phoneCode} ${getPhoneFormat(selectedCountry)}` : DEFAULT_PHONE_FORMAT const canSubmitExistingTenant = Boolean( formData.id && formData.organizationName && normalizeCode(foundTenantName) === normalizeCode(formData.name), ) && !isLoadingTenant const handleSubmit = (e: React.FormEvent) => { e.preventDefault() let hasValidationError = false setFormError('') if (isExisting) { if (!formData.name?.trim()) { setExistingTenantError('Lütfen önce organization code girin.') return } if ( !formData.id || normalizeCode(foundTenantName) !== normalizeCode(formData.name) || !formData.organizationName ) { setExistingTenantError('Devam etmek için önce organization code ile kurum bilgisini bulun.') return } if (!hasRequiredFields(formData, REQUIRED_EXISTING_CUSTOMER_FIELDS)) { setExistingTenantError('Kurum bilgileri eksik geldi. Lütfen kurum kodunu tekrar aratın.') return } onSubmit({ ...formData, phoneNumber: normalizePhoneForStorage(formData.phoneNumber, selectedCountry), mobileNumber: normalizePhoneForStorage(formData.mobileNumber, selectedCountry), faxNumber: normalizePhoneForStorage(formData.faxNumber, selectedCountry), isExisting: true, } as CustomTenantDto) return } if (!hasRequiredFields(formData, REQUIRED_NEW_CUSTOMER_FIELDS)) { setFormError('Lütfen zorunlu alanları eksiksiz doldurun.') hasValidationError = true } const nextPhoneError = getPhoneValidationError(formData.phoneNumber || '', selectedCountry) setPhoneError(nextPhoneError) if (nextPhoneError) { hasValidationError = true } if (!isExisting && !EMAIL_PATTERN.test(formData.email || '')) { setEmailError('Lütfen geçerli bir e-posta adresi girin.') hasValidationError = true } if (!isExisting && !WEBSITE_PATTERN.test(formData.website || '')) { setWebsiteError('Lütfen geçerli bir web sitesi adresi girin.') hasValidationError = true } if (hasValidationError) return onSubmit({ ...formData, phoneNumber: normalizePhoneForStorage(formData.phoneNumber, selectedCountry), mobileNumber: normalizePhoneForStorage(formData.mobileNumber, selectedCountry), faxNumber: normalizePhoneForStorage(formData.faxNumber, selectedCountry), isExisting: false, } as CustomTenantDto) } const handleInputChange = (field: keyof CustomTenantDto, value: string) => { setFormData((prev) => ({ ...prev, [field]: value })) } const getDefaultNewCustomerData = (): Partial => { const defaultCountry = countries.find((country) => country.name === 'Türkiye') return defaultCountry ? { country: defaultCountry.name, city: '', district: '', township: '', postalCode: '', phoneNumber: `+${defaultCountry.phoneCode}`, } : {} } const handleCustomerTypeChange = (nextIsExisting: boolean) => { if (nextIsExisting === isExisting) return setIsExisting(nextIsExisting) setFoundTenantName('') setExistingTenantError('') setFormError('') setEmailError('') setWebsiteError('') setPhoneError('') setDistricts([]) setFormData(nextIsExisting ? {} : getDefaultNewCustomerData()) clearTenant() } const resetExistingTenantDetails = (name: string) => { setFoundTenantName('') setExistingTenantError('') setFormError('') setFormData({ name }) } const handleCountryChange = async (option: LocationOption | null) => { const country = option?.data setFormData((prev) => ({ ...prev, country: country?.name || '', city: '', district: '', township: '', postalCode: '', phoneNumber: country ? `+${country.phoneCode}` : '', })) setCities([]) setDistricts([]) setPhoneError('') if (!country) return setIsLoadingCities(true) try { const response = await getCities(country.name) setCities(response.data) } catch (error) { console.error('Şehirler alınırken hata:', error) } finally { setIsLoadingCities(false) } } const handleCityChange = async (option: LocationOption | null) => { const city = option?.data setFormData((prev) => ({ ...prev, city: city?.name || '', district: '', township: '', postalCode: '', })) setDistricts([]) if (!city || !formData.country) return setIsLoadingDistricts(true) try { const response = await getDistricts(formData.country, city.name) setDistricts(response.data) } catch (error) { console.error('İlçeler alınırken hata:', error) } finally { setIsLoadingDistricts(false) } } const handleDistrictChange = (option: LocationOption | null) => { setFormData((prev) => ({ ...prev, district: option?.data.name || '', township: '', postalCode: '', })) } const handleTownshipChange = (option: LocationOption | null) => { setFormData((prev) => ({ ...prev, township: option?.data.township || '', postalCode: getDistrictPostalCode(option?.data), })) } const countryOptions: LocationOption[] = countries.map((country) => ({ label: country.name, value: country.name, data: country, })) const cityOptions: LocationOption[] = cities.map((city) => ({ label: city.name, value: city.name, data: city, })) const districtOptions = getUniqueLocationOptions( districts, (district) => district.name, (district) => district.name, ) const townshipOptions = getUniqueLocationOptions( districts.filter((district) => district.name === formData.district && district.township), (district) => `${district.township || ''}:${getDistrictPostalCode(district)}`, (district) => district.township || '', ) const getTenantInfo = async () => { const tenantName = formData.name?.trim() if (!tenantName) { setExistingTenantError('Lütfen önce organization code girin.') return } setIsLoadingTenant(true) setExistingTenantError('') setFormError('') try { const tenant = await getTenantByNameDetail(tenantName) setFormData((prev) => ({ ...prev, id: tenant.data.id, name: tenant.data.name, organizationName: tenant.data.organizationName, founder: tenant.data.founder, vknTckn: tenant.data.vknTckn, taxOffice: tenant.data.taxOffice, country: tenant.data.country, city: tenant.data.city, district: tenant.data.district, township: tenant.data.township, postalCode: tenant.data.postalCode, phoneNumber: tenant.data.phoneNumber, mobileNumber: tenant.data.mobileNumber, faxNumber: tenant.data.faxNumber, address1: tenant.data.address1, address2: tenant.data.address2, email: tenant.data.email, website: tenant.data.website, menuGroup: tenant.data.menuGroup, })) if (tenant.data.name) setFoundTenantName(tenant.data.name) } catch (error) { setFoundTenantName('') setExistingTenantError('Kurum bulunamadı. Lütfen organization code bilgisini kontrol edin.') console.error('Kurum bilgisi alınırken hata:', error) } finally { setIsLoadingTenant(false) } } return (

{' '} {translate('::Public.payment.customerInfo')}

{isExisting !== null && (
{isExisting ? (
resetExistingTenantDetails(e.target.value)} onKeyDown={async (e) => { if (e.key === 'Enter') { e.preventDefault() await getTenantInfo() } }} className="h-[40px] w-full pl-10 pr-4 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 sm:rounded-l-lg sm:rounded-r-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" />
{existingTenantError && (

{existingTenantError}

)} {!existingTenantError && formData.name && !canSubmitExistingTenant && !isLoadingTenant && (

Devam etmek için önce Find Organization ile kurum bilgilerini getirin.

)} {formData.organizationName && (
{translate('::Public.payment.customer.company')} {formData.organizationName}
{translate('::LoginPanel.Profil')} {formData.founder}
{translate('::Abp.Account.EmailAddress')}: {formData.email}
{translate('::Abp.Identity.User.UserInformation.PhoneNumber')}: {formData.phoneNumber}
{translate('::App.Address')}:
{formData.address1}
{translate('::Public.payment.customer.country')}: {formData.country}
{translate('::Public.common.city')}: {formData.city}
{translate('::Public.payment.customer.district')}: {formData.district}
{translate('::App.Listform.ListformField.Township')}: {formData.township}
{translate('::Public.payment.customer.postalCode')}: {formData.postalCode}
{translate('::Public.contact.taxOffice')}: {formData.taxOffice}
{translate('::Public.contact.taxNumber')}: {formData.vknTckn}
{formData.reference && (
{translate('::Public.payment.customer.reference')}: {formData.reference}
)}
)}
) : (
handleInputChange('organizationName', e.target.value)} className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" />
handleInputChange('founder', e.target.value)} className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
> required isClearable isLoading={isLoadingCountries} options={countryOptions} value={ countryOptions.find((option) => option.value === formData.country) || null } onChange={handleCountryChange} placeholder="Ülke seçin" />
> required isClearable isDisabled={!formData.country} isLoading={isLoadingCities} options={cityOptions} value={cityOptions.find((option) => option.value === formData.city) || null} onChange={handleCityChange} placeholder={formData.country ? 'Şehir seçin' : 'Önce ülke seçin'} />
> required isClearable isDisabled={!formData.city} isLoading={isLoadingDistricts} options={districtOptions} value={ districtOptions.find((option) => option.value === formData.district) || null } onChange={handleDistrictChange} placeholder={formData.city ? 'İlçe seçin' : 'Önce şehir seçin'} />
> required isClearable isDisabled={!formData.district} options={townshipOptions} value={ townshipOptions.find( (option) => option.data.township === formData.township, ) || null } onChange={handleTownshipChange} placeholder={formData.district ? 'Township seçin' : 'Önce ilçe seçin'} />
handleInputChange('postalCode', e.target.value)} className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent" />