You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

2360 lines
111 KiB

import React, { useState, useEffect, useMemo } from 'react'
import { useParams, useNavigate, useSearchParams, Link } from 'react-router-dom'
import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog'
import DeleteConfirmDialog from '@/components/shared/DeleteConfirmDialog'
import { Switch } from '@/components/ui/switch'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Ic } from '@/icons'
import { toFa, cn } from '@/lib/utils'
import { useTranslation } from '@/i18n'
import { Shimmer } from '@/components/shared/shimmer_frontend'
import {
CustomTripDestinationItem,
CustomTripHotelItem,
CustomTripTransportRelationItem,
CustomTripOriginItem,
} from './types'
import {
ServiceTranslation,
SERVICE_LANGUAGES,
type ServiceLanguageCode,
} from '@/components/shared/ServiceTranslation'
import { OFFICIAL_PRESET_CITIES } from './components/CustomTripDestinationsTab'
import {
fetchCustomTripDestinations,
fetchCustomTripOrigins,
fetchCustomTripHotels,
fetchCustomTripTransportRelations,
saveCustomTripDestination,
deleteCustomTripDestination,
saveCustomTripHotel,
deleteCustomTripHotel,
saveCustomTripTransportRelation,
deleteCustomTripTransportRelation,
} from './services/custom-trip-api'
const PRESET_DEST_COUNTRIES = [
{ name: 'عراق', flag: '🇮🇶' },
{ name: 'ایران', flag: '🇮🇷' },
{ name: 'عربستان سعودی', flag: '🇸🇦' },
{ name: 'سوریه', flag: '🇸🇾' },
]
const COMMON_AMENITIES = [
{ id: 'wifi', name: 'اینترنت وای‌فای رایگان' },
{ id: 'restaurant', name: 'رستوران ایرانی و بوفه باز' },
{ id: 'shrine_transfer', name: 'ترانسفر حرم مطهر رایگان' },
{ id: 'airport_transfer', name: 'ترانسفر فرودگاهی اختصاصی' },
{ id: 'laundry', name: 'لاندری و خدمات خشکشویی' },
{ id: 'wheelchair', name: 'خدمات ویژه ویلچر و توان‌یابان' },
{ id: 'doctor', name: 'پزشک و خدمات درمانی مقیم' },
{ id: 'safety_box', name: 'صندوق امانات دیجیتال در اتاق' },
]
export default function CustomTripDestinationDetailPage() {
const { t } = useTranslation()
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const [searchParams, setSearchParams] = useSearchParams()
// Default active tab is 'info' (مشخصات و اطلاعات شهر)
const currentTab = searchParams.get('tab') || 'info'
const [isLoading, setIsLoading] = useState(true)
const [destination, setDestination] = useState<CustomTripDestinationItem | null>(null)
const [allOrigins, setAllOrigins] = useState<CustomTripOriginItem[]>([])
const [allDestinations, setAllDestinations] = useState<CustomTripDestinationItem[]>([])
const [allHotels, setAllHotels] = useState<CustomTripHotelItem[]>([])
const [allRelations, setAllRelations] = useState<CustomTripTransportRelationItem[]>([])
// --- TAB 1: DESTINATION INFO FORM STATE ---
const [destName, setDestName] = useState('')
const [destNameEn, setDestNameEn] = useState('')
const [destCountry, setDestCountry] = useState('عراق')
const [destFlag, setDestFlag] = useState('🇮🇶')
const [destImage, setDestImage] = useState('')
const [destBlogSlug, setDestBlogSlug] = useState('')
const [destJourneysCount, setDestJourneysCount] = useState<number>(10)
const [destDescription, setDestDescription] = useState('')
const [destHolyPlaces, setDestHolyPlaces] = useState('')
const [destIsActive, setDestIsActive] = useState(true)
const [isDestSavedSuccess, setIsDestSavedSuccess] = useState(false)
const [isDestDeleteOpen, setIsDestDeleteOpen] = useState(false)
const [isEditDestModalOpen, setIsEditDestModalOpen] = useState(false)
const [isTogglingStatus, setIsTogglingStatus] = useState(false)
const [isSavingDestination, setIsSavingDestination] = useState(false)
const handleToggleStatus = async (newVal: boolean) => {
if (!destination) return
setIsTogglingStatus(true)
try {
const updated: CustomTripDestinationItem = {
...destination,
isActive: newVal,
}
await saveCustomTripDestination(updated)
setDestIsActive(newVal)
setDestination(updated)
setIsDestSavedSuccess(true)
setTimeout(() => setIsDestSavedSuccess(false), 2500)
} catch (err) {
console.error('Failed to toggle destination active status:', err)
} finally {
setIsTogglingStatus(false)
}
}
// Multi-language Tab 1 states (Arabic is default primary)
const [activeLangTab, setActiveLangTab] = useState<ServiceLanguageCode>('ar')
const [names, setNames] = useState<Record<ServiceLanguageCode, string>>({
ar: '',
fa: '',
en: '',
ru: '',
id: '',
})
const [holyPlacesMap, setHolyPlacesMap] = useState<Record<ServiceLanguageCode, string>>({
ar: '',
fa: '',
en: '',
ru: '',
id: '',
})
const [descriptionsMap, setDescriptionsMap] = useState<Record<ServiceLanguageCode, string>>({
ar: '',
fa: '',
en: '',
ru: '',
id: '',
})
const currentLang =
SERVICE_LANGUAGES.find((l) => l.code === activeLangTab) || SERVICE_LANGUAGES[0]
const initDestinationState = (target: CustomTripDestinationItem) => {
setDestination(target)
setDestName(target.name)
setDestNameEn(target.nameEn || '')
setDestCountry(target.country)
setDestFlag(target.flag || '📍')
setDestImage(target.image || '')
setDestBlogSlug(target.blogSlug || target.blog_slug || '')
setDestJourneysCount(target.journeysCount ?? target.journeys_count ?? 10)
setDestDescription(target.description || '')
setDestHolyPlaces((target.holyPlaces || []).join('، '))
setDestIsActive(target.isActive)
const preset = OFFICIAL_PRESET_CITIES.find(
(c) =>
String(c.id).toLowerCase() === String(target.id).toLowerCase() ||
c.names.fa === target.name ||
c.names.en.toLowerCase() === (target.nameEn || '').toLowerCase()
)
setNames({
ar: target.translations?.ar?.name || preset?.names.ar || '',
fa: target.translations?.fa?.name || preset?.names.fa || target.name || '',
en: target.translations?.en?.name || preset?.names.en || target.nameEn || '',
ru: target.translations?.ru?.name || preset?.names.ru || '',
id: target.translations?.id?.name || preset?.names.id || '',
})
setHolyPlacesMap({
ar: target.translations?.ar?.holyPlaces?.join('، ') || preset?.holyPlaces.ar || '',
fa: target.translations?.fa?.holyPlaces?.join('، ') || preset?.holyPlaces.fa || (target.holyPlaces || []).join('، '),
en: target.translations?.en?.holyPlaces?.join(', ') || preset?.holyPlaces.en || '',
ru: target.translations?.ru?.holyPlaces?.join(', ') || preset?.holyPlaces.ru || '',
id: target.translations?.id?.holyPlaces?.join(', ') || preset?.holyPlaces.id || '',
})
setDescriptionsMap({
ar: target.translations?.ar?.description || preset?.description.ar || '',
fa: target.translations?.fa?.description || preset?.description.fa || target.description || '',
en: target.translations?.en?.description || preset?.description.en || '',
ru: target.translations?.ru?.description || preset?.description.ru || '',
id: target.translations?.id?.description || preset?.description.id || '',
})
}
// --- TAB 2: HOTELS STATE ---
const [hotelSearch, setHotelSearch] = useState('')
const [hotelStarsFilter, setHotelStarsFilter] = useState<'all' | '5' | '4' | '3'>('all')
const [hotelViewMode, setHotelViewMode] = useState<'cards' | 'table'>('cards')
const [isHotelFormOpen, setIsHotelFormOpen] = useState(false)
const [editingHotel, setEditingHotel] = useState<CustomTripHotelItem | null>(null)
const [hotelToDelete, setHotelToDelete] = useState<CustomTripHotelItem | null>(null)
// Hotel form fields
const [hotelLangTab, setHotelLangTab] = useState<ServiceLanguageCode>('ar')
const [hotelNames, setHotelNames] = useState<Record<ServiceLanguageCode, string>>({
ar: '',
fa: '',
en: '',
ru: '',
id: '',
})
const [hotelDistances, setHotelDistances] = useState<Record<ServiceLanguageCode, string>>({
ar: '',
fa: '',
en: '',
ru: '',
id: '',
})
const [hotelDescriptions, setHotelDescriptions] = useState<Record<ServiceLanguageCode, string>>({
ar: '',
fa: '',
en: '',
ru: '',
id: '',
})
const [hotelGuideNotes, setHotelGuideNotes] = useState<Record<ServiceLanguageCode, string>>({
ar: '',
fa: '',
en: '',
ru: '',
id: '',
})
const [hotelFormStars, setHotelFormStars] = useState<number>(5)
const [hotelFormScore, setHotelFormScore] = useState<number>(4.8)
const [hotelFormAmenities, setHotelFormAmenities] = useState<string[]>([])
const [hotelFormCustomAmenity, setHotelFormCustomAmenity] = useState('')
const [hotelFormPriceAdult, setHotelFormPriceAdult] = useState<number>(100)
const [hotelFormPriceChild, setHotelFormPriceChild] = useState<number>(50)
const [hotelFormIsActive, setHotelFormIsActive] = useState(true)
// --- TAB 3: TRANSPORTS STATE ---
const [transportSearch, setTransportSearch] = useState('')
const [transportMethodFilter, setTransportMethodFilter] = useState<'all' | 'flight' | 'train' | 'road'>('all')
const [transportViewMode, setTransportViewMode] = useState<'cards' | 'table'>('cards')
const [isTransportFormOpen, setIsTransportFormOpen] = useState(false)
const [editingTransport, setEditingTransport] = useState<CustomTripTransportRelationItem | null>(null)
const [transportToDelete, setTransportToDelete] = useState<CustomTripTransportRelationItem | null>(null)
// Transport form fields (Arabic is default primary)
const [transportLangTab, setTransportLangTab] = useState<ServiceLanguageCode>('ar')
const [transportGuideNotes, setTransportGuideNotes] = useState<Record<ServiceLanguageCode, string>>({
ar: '',
fa: '',
en: '',
ru: '',
id: '',
})
const [transportAirplaneDetailsMap, setTransportAirplaneDetailsMap] = useState<Record<ServiceLanguageCode, string>>({
ar: '',
fa: '',
en: '',
ru: '',
id: '',
})
const [transportTrainDetailsMap, setTransportTrainDetailsMap] = useState<Record<ServiceLanguageCode, string>>({
ar: '',
fa: '',
en: '',
ru: '',
id: '',
})
const [transportRoadDetailsMap, setTransportRoadDetailsMap] = useState<Record<ServiceLanguageCode, string>>({
ar: '',
fa: '',
en: '',
ru: '',
id: '',
})
const [transportFormFromCity, setTransportFormFromCity] = useState('')
const [transportFormFromCountry, setTransportFormFromCountry] = useState('')
const [transportFormToCity, setTransportFormToCity] = useState('')
const [transportFormToCountry, setTransportFormToCountry] = useState('')
const [transportFormHasAirplane, setTransportFormHasAirplane] = useState(true)
const [transportFormHasTrain, setTransportFormHasTrain] = useState(false)
const [transportFormHasRoad, setTransportFormHasRoad] = useState(false)
const [transportFormIsActive, setTransportFormIsActive] = useState(true)
// Load all initial data
useEffect(() => {
let isMounted = true
async function load() {
setIsLoading(true)
try {
const [dests, origs, hotels, rels] = await Promise.all([
fetchCustomTripDestinations(),
fetchCustomTripOrigins(),
fetchCustomTripHotels(),
fetchCustomTripTransportRelations(),
])
if (isMounted) {
setAllDestinations(dests)
setAllOrigins(origs)
setAllHotels(hotels)
setAllRelations(rels)
const target = dests.find(
(d) =>
String(d.id) === String(id) ||
d.name === String(id) ||
(d.nameEn && d.nameEn.toLowerCase() === String(id).toLowerCase())
)
if (target) {
initDestinationState(target)
} else if (dests.length > 0) {
initDestinationState(dests[0])
}
}
} finally {
if (isMounted) setIsLoading(false)
}
}
load()
return () => {
isMounted = false
}
}, [id])
// Destination-specific hotels
const destHotels = useMemo(() => {
if (!destination) return []
return allHotels.filter(
(h) =>
h.cityName === destination.name ||
destination.name.includes(h.cityName) ||
h.cityName.includes(destination.name)
)
}, [allHotels, destination])
// Filtered hotels
const filteredHotels = useMemo(() => {
return destHotels.filter((h) => {
const matchStars = hotelStarsFilter === 'all' || String(h.stars) === hotelStarsFilter
const term = hotelSearch.trim().toLowerCase()
const matchSearch =
!term ||
h.name.toLowerCase().includes(term) ||
(h.distanceToShrine && h.distanceToShrine.toLowerCase().includes(term)) ||
(h.description && h.description.toLowerCase().includes(term)) ||
(h.guideNote && h.guideNote.toLowerCase().includes(term)) ||
h.amenities.some((a) => a.toLowerCase().includes(term))
return matchStars && matchSearch
})
}, [destHotels, hotelStarsFilter, hotelSearch])
// Destination-specific transport relations
const destRelations = useMemo(() => {
if (!destination) return []
return allRelations.filter(
(r) =>
r.toCity === destination.name ||
destination.name.includes(r.toCity) ||
r.toCity.includes(destination.name)
)
}, [allRelations, destination])
// Filtered relations
const filteredRelations = useMemo(() => {
return destRelations.filter((rel) => {
const matchMethod =
transportMethodFilter === 'all' ||
(transportMethodFilter === 'flight' && rel.hasAirplane) ||
(transportMethodFilter === 'train' && rel.hasTrain) ||
(transportMethodFilter === 'road' && rel.hasRoad)
const term = transportSearch.trim().toLowerCase()
const matchSearch =
!term ||
rel.fromCity.toLowerCase().includes(term) ||
rel.fromCountry.toLowerCase().includes(term) ||
(rel.airplaneDetails && rel.airplaneDetails.toLowerCase().includes(term)) ||
(rel.trainDetails && rel.trainDetails.toLowerCase().includes(term)) ||
(rel.roadDetails && rel.roadDetails.toLowerCase().includes(term)) ||
(rel.guideNote && rel.guideNote.toLowerCase().includes(term))
return matchMethod && matchSearch
})
}, [destRelations, transportMethodFilter, transportSearch])
const handleTabChange = (tabKey: string) => {
setSearchParams({ tab: tabKey })
}
// --- TAB 1: SAVE DESTINATION HANDLER ---
const handleSaveDestinationInfo = async (e: React.FormEvent) => {
e.preventDefault()
if (!destination || isSavingDestination) return
const primaryName =
names[activeLangTab]?.trim() ||
names.fa?.trim() ||
names.ar?.trim() ||
names.en?.trim() ||
destName.trim()
if (!primaryName) return
setIsSavingDestination(true)
try {
const translations: Record<string, { name?: string; description?: string; holyPlaces?: string[] }> = {}
SERVICE_LANGUAGES.forEach((lang) => {
const langName = names[lang.code]?.trim()
const langDesc = descriptionsMap[lang.code]?.trim()
const langPlaces = (holyPlacesMap[lang.code] || '')
.split(/[،,]/)
.map((s) => s.trim())
.filter(Boolean)
if (langName || langDesc || langPlaces.length > 0) {
translations[lang.code] = {
name: langName || undefined,
description: langDesc || undefined,
holyPlaces: langPlaces.length > 0 ? langPlaces : undefined,
}
}
})
const primaryNameEn = names.en?.trim() || destNameEn.trim() || undefined
const primaryDesc = descriptionsMap.fa?.trim() || descriptionsMap.ar?.trim() || descriptionsMap.en?.trim() || destDescription.trim() || undefined
const primaryPlaces = (holyPlacesMap.fa || holyPlacesMap.ar || destHolyPlaces)
.split(/[،,]/)
.map((s) => s.trim())
.filter(Boolean)
const updated: CustomTripDestinationItem = {
...destination,
name: primaryName,
nameEn: primaryNameEn,
country: destCountry.trim(),
flag: destFlag,
image: destImage.trim() || undefined,
blogSlug: destBlogSlug.trim() || undefined,
journeysCount: Number(destJourneysCount) || 10,
description: primaryDesc,
holyPlaces: primaryPlaces.length ? primaryPlaces : undefined,
isActive: destIsActive,
translations,
}
await saveCustomTripDestination(updated)
setDestination(updated)
setDestName(primaryName)
if (primaryNameEn) setDestNameEn(primaryNameEn)
if (primaryDesc) setDestDescription(primaryDesc)
setIsDestSavedSuccess(true)
setIsEditDestModalOpen(false)
setIsSavingDestination(false)
setTimeout(() => setIsDestSavedSuccess(false), 3000)
} catch (err) {
console.error('Failed to save destination:', err)
setIsSavingDestination(false)
}
}
const handleDeleteDestinationConfirm = async () => {
if (!destination) return
await deleteCustomTripDestination(destination.id)
navigate('/custom-trip/destinations')
}
// --- TAB 2: HOTEL HANDLERS ---
const currentHotelLang =
SERVICE_LANGUAGES.find((l) => l.code === hotelLangTab) || SERVICE_LANGUAGES[0]
const handleOpenAddHotel = () => {
setEditingHotel(null)
setHotelLangTab('ar')
const emptyMap: Record<ServiceLanguageCode, string> = { ar: '', fa: '', en: '', ru: '', id: '' }
setHotelNames(emptyMap)
setHotelDistances(emptyMap)
setHotelDescriptions(emptyMap)
setHotelGuideNotes(emptyMap)
setHotelFormStars(5)
setHotelFormScore(4.8)
setHotelFormAmenities(['اینترنت وای‌فای رایگان', 'رستوران ایرانی و بوفه باز'])
setHotelFormCustomAmenity('')
setHotelFormPriceAdult(110)
setHotelFormPriceChild(55)
setHotelFormIsActive(true)
setIsHotelFormOpen(true)
}
const handleOpenEditHotel = (hotel: CustomTripHotelItem) => {
setEditingHotel(hotel)
setHotelLangTab('ar')
const newNames: Record<ServiceLanguageCode, string> = { ar: '', fa: '', en: '', ru: '', id: '' }
const newDistances: Record<ServiceLanguageCode, string> = { ar: '', fa: '', en: '', ru: '', id: '' }
const newDescs: Record<ServiceLanguageCode, string> = { ar: '', fa: '', en: '', ru: '', id: '' }
const newGuides: Record<ServiceLanguageCode, string> = { ar: '', fa: '', en: '', ru: '', id: '' }
if (hotel.translations) {
for (const lang of ['ar', 'fa', 'en', 'ru', 'id'] as ServiceLanguageCode[]) {
if (hotel.translations[lang]?.name) newNames[lang] = hotel.translations[lang].name!
if (hotel.translations[lang]?.distanceToShrine) newDistances[lang] = hotel.translations[lang].distanceToShrine!
if (hotel.translations[lang]?.description) newDescs[lang] = hotel.translations[lang].description!
if (hotel.translations[lang]?.guideNote) newGuides[lang] = hotel.translations[lang].guideNote!
}
}
// Fallbacks
if (!newNames.ar) newNames.ar = hotel.name || ''
if (!newNames.fa) newNames.fa = hotel.name || ''
if (!newDistances.ar) newDistances.ar = hotel.distanceToShrine || ''
if (!newDistances.fa) newDistances.fa = hotel.distanceToShrine || ''
if (!newDescs.ar) newDescs.ar = hotel.description || ''
if (!newDescs.fa) newDescs.fa = hotel.description || ''
if (!newGuides.ar && hotel.guideNote) newGuides.ar = hotel.guideNote
if (!newGuides.fa && hotel.guideNote) newGuides.fa = hotel.guideNote
setHotelNames(newNames)
setHotelDistances(newDistances)
setHotelDescriptions(newDescs)
setHotelGuideNotes(newGuides)
setHotelFormStars(hotel.stars)
setHotelFormScore(hotel.score || 4.8)
setHotelFormAmenities(hotel.amenities || [])
setHotelFormCustomAmenity('')
setHotelFormPriceAdult(hotel.pricePerDayAdult || 100)
setHotelFormPriceChild(hotel.pricePerDayChild || 50)
setHotelFormIsActive(hotel.isActive)
setIsHotelFormOpen(true)
}
const handleToggleHotelAmenity = (name: string) => {
setHotelFormAmenities((prev) =>
prev.includes(name) ? prev.filter((a) => a !== name) : [...prev, name]
)
}
const handleAddCustomHotelAmenity = () => {
const val = hotelFormCustomAmenity.trim()
if (!val) return
if (!hotelFormAmenities.includes(val)) {
setHotelFormAmenities((prev) => [...prev, val])
}
setHotelFormCustomAmenity('')
}
const handleSaveHotel = async (e: React.FormEvent) => {
e.preventDefault()
if (!destination) return
const primaryName =
hotelNames[hotelLangTab]?.trim() ||
hotelNames.ar?.trim() ||
hotelNames.fa?.trim() ||
hotelNames.en?.trim() ||
'هتل جدید'
const primaryDistance =
hotelDistances[hotelLangTab]?.trim() ||
hotelDistances.ar?.trim() ||
hotelDistances.fa?.trim() ||
'فاصله استاندارد تا حرم مطهر'
const primaryDesc =
hotelDescriptions[hotelLangTab]?.trim() ||
hotelDescriptions.ar?.trim() ||
hotelDescriptions.fa?.trim() ||
'هتل درجه یک و مجهز جهت اقامت زائرین گرامی.'
const primaryGuide =
hotelGuideNotes[hotelLangTab]?.trim() ||
hotelGuideNotes.ar?.trim() ||
hotelGuideNotes.fa?.trim() ||
undefined
const translations: Record<
string,
{ name?: string; description?: string; distanceToShrine?: string; guideNote?: string }
> = {}
for (const lang of ['ar', 'fa', 'en', 'ru', 'id'] as ServiceLanguageCode[]) {
translations[lang] = {
name: hotelNames[lang]?.trim() || primaryName,
distanceToShrine: hotelDistances[lang]?.trim() || primaryDistance,
description: hotelDescriptions[lang]?.trim() || primaryDesc,
guideNote: hotelGuideNotes[lang]?.trim() || primaryGuide,
}
}
const item: CustomTripHotelItem = {
id: editingHotel ? editingHotel.id : `hotel_${Date.now()}`,
name: primaryName,
cityName: destination.name,
stars: hotelFormStars,
score: hotelFormScore,
distanceToShrine: primaryDistance,
amenities: hotelFormAmenities,
description: primaryDesc,
guideNote: primaryGuide,
pricePerDayAdult: hotelFormPriceAdult,
pricePerDayChild: hotelFormPriceChild,
isActive: hotelFormIsActive,
translations,
}
await saveCustomTripHotel(item)
setAllHotels((prev) => {
const exists = prev.some((h) => h.id === item.id)
return exists ? prev.map((h) => (h.id === item.id ? item : h)) : [item, ...prev]
})
setIsHotelFormOpen(false)
}
const handleConfirmDeleteHotel = async () => {
if (!hotelToDelete) return
await deleteCustomTripHotel(hotelToDelete.id)
setAllHotels((prev) => prev.filter((h) => h.id !== hotelToDelete.id))
setHotelToDelete(null)
}
// --- TAB 3: TRANSPORT HANDLERS ---
const currentTransportLang =
SERVICE_LANGUAGES.find((l) => l.code === transportLangTab) || SERVICE_LANGUAGES[0]
const handleOpenAddTransport = () => {
setEditingTransport(null)
setTransportLangTab('ar')
const emptyMap: Record<ServiceLanguageCode, string> = { ar: '', fa: '', en: '', ru: '', id: '' }
setTransportGuideNotes(emptyMap)
setTransportAirplaneDetailsMap(emptyMap)
setTransportTrainDetailsMap(emptyMap)
setTransportRoadDetailsMap(emptyMap)
setTransportFormFromCity(allOrigins.length > 0 ? allOrigins[0].name : 'مشهد')
setTransportFormFromCountry(allOrigins.length > 0 ? allOrigins[0].country : 'ایران')
setTransportFormToCity(destination?.name || (allDestinations.length > 0 ? allDestinations[0].name : 'کربلای معلی'))
setTransportFormToCountry(destination?.country || (allDestinations.length > 0 ? allDestinations[0].country : 'عراق'))
setTransportFormHasAirplane(true)
setTransportFormHasTrain(false)
setTransportFormHasRoad(false)
setTransportFormIsActive(true)
setIsTransportFormOpen(true)
}
const handleOpenEditTransport = (rel: CustomTripTransportRelationItem) => {
setEditingTransport(rel)
setTransportLangTab('ar')
const newGuides: Record<ServiceLanguageCode, string> = { ar: '', fa: '', en: '', ru: '', id: '' }
const newAirplanes: Record<ServiceLanguageCode, string> = { ar: '', fa: '', en: '', ru: '', id: '' }
const newTrains: Record<ServiceLanguageCode, string> = { ar: '', fa: '', en: '', ru: '', id: '' }
const newRoads: Record<ServiceLanguageCode, string> = { ar: '', fa: '', en: '', ru: '', id: '' }
if (rel.translations) {
for (const lang of ['ar', 'fa', 'en', 'ru', 'id'] as ServiceLanguageCode[]) {
if (rel.translations[lang]?.guideNote) newGuides[lang] = rel.translations[lang].guideNote!
if (rel.translations[lang]?.airplaneDetails) newAirplanes[lang] = rel.translations[lang].airplaneDetails!
if (rel.translations[lang]?.trainDetails) newTrains[lang] = rel.translations[lang].trainDetails!
if (rel.translations[lang]?.roadDetails) newRoads[lang] = rel.translations[lang].roadDetails!
}
}
// Fallbacks: prioritize Arabic first
if (!newGuides.ar && rel.guideNote) newGuides.ar = rel.guideNote
if (!newGuides.fa && rel.guideNote) newGuides.fa = rel.guideNote
if (!newAirplanes.ar && rel.airplaneDetails) newAirplanes.ar = rel.airplaneDetails
if (!newAirplanes.fa && rel.airplaneDetails) newAirplanes.fa = rel.airplaneDetails
if (!newTrains.ar && rel.trainDetails) newTrains.ar = rel.trainDetails
if (!newTrains.fa && rel.trainDetails) newTrains.fa = rel.trainDetails
if (!newRoads.ar && rel.roadDetails) newRoads.ar = rel.roadDetails
if (!newRoads.fa && rel.roadDetails) newRoads.fa = rel.roadDetails
setTransportGuideNotes(newGuides)
setTransportAirplaneDetailsMap(newAirplanes)
setTransportTrainDetailsMap(newTrains)
setTransportRoadDetailsMap(newRoads)
setTransportFormFromCity(rel.fromCity)
setTransportFormFromCountry(rel.fromCountry)
setTransportFormToCity(rel.toCity || destination?.name || '')
setTransportFormToCountry(rel.toCountry || destination?.country || '')
setTransportFormHasAirplane(rel.hasAirplane)
setTransportFormHasTrain(rel.hasTrain)
setTransportFormHasRoad(rel.hasRoad)
setTransportFormIsActive(rel.isActive !== false)
setIsTransportFormOpen(true)
}
const handleSaveTransport = async (e: React.FormEvent) => {
e.preventDefault()
if (!destination || !transportFormFromCity.trim()) return
const primaryAirplane =
transportAirplaneDetailsMap[transportLangTab]?.trim() ||
transportAirplaneDetailsMap.ar?.trim() ||
transportAirplaneDetailsMap.fa?.trim() ||
undefined
const primaryTrain =
transportTrainDetailsMap[transportLangTab]?.trim() ||
transportTrainDetailsMap.ar?.trim() ||
transportTrainDetailsMap.fa?.trim() ||
undefined
const primaryRoad =
transportRoadDetailsMap[transportLangTab]?.trim() ||
transportRoadDetailsMap.ar?.trim() ||
transportRoadDetailsMap.fa?.trim() ||
undefined
const primaryGuide =
transportGuideNotes[transportLangTab]?.trim() ||
transportGuideNotes.ar?.trim() ||
transportGuideNotes.fa?.trim() ||
undefined
const translations: Record<
string,
{ guideNote?: string; airplaneDetails?: string; trainDetails?: string; roadDetails?: string }
> = {}
for (const lang of ['ar', 'fa', 'en', 'ru', 'id'] as ServiceLanguageCode[]) {
translations[lang] = {
guideNote: transportGuideNotes[lang]?.trim() || primaryGuide,
airplaneDetails: transportAirplaneDetailsMap[lang]?.trim() || primaryAirplane,
trainDetails: transportTrainDetailsMap[lang]?.trim() || primaryTrain,
roadDetails: transportRoadDetailsMap[lang]?.trim() || primaryRoad,
}
}
const targetToCity = transportFormToCity.trim() || destination.name
const targetToCountry = transportFormToCountry.trim() || destination.country
const item: CustomTripTransportRelationItem = {
id: editingTransport ? editingTransport.id : `rel_${Date.now()}`,
fromCity: transportFormFromCity.trim(),
fromCountry: transportFormFromCountry.trim() || 'نامشخص',
toCity: targetToCity,
toCountry: targetToCountry,
hasAirplane: transportFormHasAirplane,
airplaneDetails: primaryAirplane,
hasTrain: transportFormHasTrain,
trainDetails: primaryTrain,
hasRoad: transportFormHasRoad,
roadDetails: primaryRoad,
guideNote: primaryGuide,
isActive: transportFormIsActive,
translations,
}
await saveCustomTripTransportRelation(item)
setAllRelations((prev) => {
const exists = prev.some((r) => r.id === item.id)
return exists ? prev.map((r) => (r.id === item.id ? item : r)) : [item, ...prev]
})
setIsTransportFormOpen(false)
}
const handleConfirmDeleteTransport = async () => {
if (!transportToDelete) return
await deleteCustomTripTransportRelation(transportToDelete.id)
setAllRelations((prev) => prev.filter((r) => r.id !== transportToDelete.id))
setTransportToDelete(null)
}
if (isLoading) {
return (
<div className="space-y-4">
<Shimmer className="h-10 w-48 rounded-xl" />
<Shimmer className="h-44 w-full rounded-2xl" />
<Shimmer className="h-72 w-full rounded-2xl" />
</div>
)
}
if (!destination) {
return (
<div className="p-8 text-center rounded-2xl bg-card border border-border/70 space-y-4">
<Ic name="alert" className="size-10 text-muted-foreground mx-auto" />
<h2 className="text-base font-bold text-foreground">شهر مقصد مورد نظر یافت نشد!</h2>
<Button onClick={() => navigate('/custom-trip/destinations')} variant="outline" className="rounded-xl">
بازگشت به فهرست شهرهای مقصد
</Button>
</div>
)
}
return (
<div className="space-y-6">
{/* Top Breadcrumb Navigation */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 pb-2 border-b border-border/50">
<div className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground">
<Link
to="/custom-trip/destinations"
className="hover:text-foreground font-medium transition-colors"
>
شهرهای مقصد
</Link>
<span className="text-border">/</span>
<span className="font-bold text-foreground flex items-center gap-1.5">
<span>{destFlag || destination.flag || '📍'}</span>
<span>{destName || destination.name}</span>
</span>
</div>
<div className="flex items-center gap-2">
<span
className={cn(
'inline-flex items-center gap-1.5 text-xs font-semibold px-2.5 py-1 rounded-full border transition-colors',
destIsActive
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20'
: 'bg-muted text-muted-foreground border-border-soft'
)}
>
<span
className={cn(
'size-2 rounded-full',
destIsActive ? 'bg-emerald-500 animate-pulse' : 'bg-muted-foreground'
)}
/>
<span>{destIsActive ? 'مقصد فعال' : 'مقصد غیرفعال'}</span>
</span>
</div>
</div>
{/* Tabs Navigation Header (Segmented Control) */}
<div className="inline-flex w-fit max-w-full items-center gap-1.5 p-1.5 rounded-2xl bg-muted/60 border border-border/70 overflow-x-auto shadow-2xs">
{/* Tab 1: City Info */}
<button
type="button"
onClick={() => handleTabChange('info')}
className={`flex items-center gap-2 px-4 py-2 rounded-xl text-xs sm:text-sm font-semibold transition-all cursor-pointer whitespace-nowrap ${
currentTab === 'info'
? 'bg-card text-foreground shadow-xs border border-border/70 font-bold'
: 'text-muted-foreground hover:text-foreground hover:bg-card/50'
}`}
>
<Ic name="mapPin" className={`size-4 ${currentTab === 'info' ? 'text-primary' : ''}`} />
<span>مشخصات و توضیحات</span>
</button>
{/* Tab 2: Hotels */}
<button
type="button"
onClick={() => handleTabChange('hotels')}
className={`flex items-center gap-2 px-4 py-2 rounded-xl text-xs sm:text-sm font-semibold transition-all cursor-pointer whitespace-nowrap ${
currentTab === 'hotels'
? 'bg-card text-foreground shadow-xs border border-border/70 font-bold'
: 'text-muted-foreground hover:text-foreground hover:bg-card/50'
}`}
>
<Ic name="bed" className={`size-4 ${currentTab === 'hotels' ? 'text-primary' : ''}`} />
<span>هتل‌ها و اقامتگاه‌ها</span>
<span
className={cn(
'px-2 py-0.5 rounded-full text-[11px] font-mono font-bold',
currentTab === 'hotels'
? 'bg-primary/10 text-primary'
: 'bg-muted text-muted-foreground'
)}
>
{toFa(destHotels.length)}
</span>
</button>
{/* Tab 3: Transportation */}
<button
type="button"
onClick={() => handleTabChange('transports')}
className={`flex items-center gap-2 px-4 py-2 rounded-xl text-xs sm:text-sm font-semibold transition-all cursor-pointer whitespace-nowrap ${
currentTab === 'transports'
? 'bg-card text-foreground shadow-xs border border-border/70 font-bold'
: 'text-muted-foreground hover:text-foreground hover:bg-card/50'
}`}
>
<Ic name="route" className={`size-4 ${currentTab === 'transports' ? 'text-primary' : ''}`} />
<span>روش‌های حمل‌ونقل</span>
<span
className={cn(
'px-2 py-0.5 rounded-full text-[11px] font-mono font-bold',
currentTab === 'transports'
? 'bg-primary/10 text-primary'
: 'bg-muted text-muted-foreground'
)}
>
{toFa(destRelations.length)}
</span>
</button>
</div>
{/* ========================================================================= */}
{/* TAB 1: DESTINATION INFO (شناسنامه و مشخصات مقصد) */}
{/* ========================================================================= */}
{currentTab === 'info' && (
<div className="space-y-6 pt-1 sm:pt-2">
{/* کارت اصلی شناسنامه شهر */}
<div className="rounded-2xl bg-card border border-border/70 p-5 sm:p-6 shadow-xs space-y-6">
{/* هدر: نام شهر، وضعیت و دکمه‌های اکشن */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b border-border/60 pb-5">
<div className="flex items-center gap-4">
{destination.image || destImage ? (
<div className="size-16 sm:size-20 rounded-2xl overflow-hidden shrink-0 border border-border/70 shadow-sm bg-muted/40">
<img
src={destination.image || destImage}
alt={destination.name}
className="size-full object-cover"
onError={(e) => {
(e.currentTarget as HTMLElement).style.display = 'none'
}}
/>
</div>
) : (
<span className="text-3xl select-none">{destFlag || destination.flag || '📍'}</span>
)}
<div>
<div className="flex items-center gap-2.5 flex-wrap">
<h2 className="text-lg sm:text-xl font-black text-foreground">
{destination.name}
</h2>
{destination.nameEn && (
<span className="text-xs font-mono text-muted-foreground font-normal">
({destination.nameEn})
</span>
)}
<span
className={cn(
'inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold',
destIsActive
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border border-emerald-500/20'
: 'bg-muted text-muted-foreground border border-border-soft'
)}
>
<span
className={cn(
'size-1.5 rounded-full',
destIsActive ? 'bg-emerald-500 animate-pulse' : 'bg-muted-foreground'
)}
/>
<span>{destIsActive ? 'شهر فعال در سامانه' : 'شهر غیرفعال'}</span>
</span>
</div>
<p className="text-xs text-muted-foreground mt-1">
شناسنامه شهر، اماکن متبرکه، هتل‌های مرتبط و ترجمه‌های بین‌المللی مقصد
</p>
</div>
</div>
{/* دکمه‌های عملیات */}
<div className="flex items-center gap-2 shrink-0">
<Button
onClick={() => setIsEditDestModalOpen(true)}
className="rounded-xl h-10 px-4 text-xs sm:text-sm font-bold gap-2 bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs cursor-pointer"
>
<Ic name="pencil" className="size-4" />
<span>ویرایش در پاپ‌آپ</span>
</Button>
<Button
variant="outline"
onClick={() => setIsDestDeleteOpen(true)}
className="rounded-xl h-10 px-3 text-xs font-medium text-rose-600 hover:bg-rose-500/10 hover:text-rose-700 border-border/70 cursor-pointer"
title="حذف مقصد"
>
<Ic name="trash" className="size-4" />
</Button>
</div>
</div>
{/* اعلان موفقیت ذخیره در صورت وجود */}
{isDestSavedSuccess && (
<div className="flex items-center gap-2 p-3 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-600 text-xs font-bold animate-fadeIn">
<Ic name="checkCircle" className="size-4 shrink-0" />
<span>مشخصات شهر مقصد با موفقیت در سامانه ذخیره و به‌روزرسانی شد.</span>
</div>
)}
{/* کارت‌های آماری سریع */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div className="p-3.5 rounded-xl bg-muted/30 border border-border/60">
<span className="text-[11px] text-muted-foreground block">کشور مقصد</span>
<span className="text-sm font-bold text-foreground mt-1 flex items-center gap-1.5">
<span className="text-base select-none">{destFlag || destination.flag || '📍'}</span>
<span>{destCountry || destination.country}</span>
</span>
</div>
<div
onClick={() => handleTabChange('hotels')}
className="p-3.5 rounded-xl bg-muted/30 border border-border/60 cursor-pointer hover:border-primary/50 transition-colors"
title="مشاهده هتل‌ها"
>
<span className="text-[11px] text-muted-foreground block">هتل‌های فعال</span>
<span className="text-sm font-mono font-bold text-foreground mt-1 flex items-center gap-1.5">
<Ic name="bed" className="size-4 text-primary" />
<span>{toFa(destHotels.length)} هتل</span>
</span>
</div>
<div
onClick={() => handleTabChange('transports')}
className="p-3.5 rounded-xl bg-muted/30 border border-border/60 cursor-pointer hover:border-primary/50 transition-colors"
title="مشاهده مسیرهای حمل‌ونقل"
>
<span className="text-[11px] text-muted-foreground block">مسیرهای متصل</span>
<span className="text-sm font-mono font-bold text-foreground mt-1 flex items-center gap-1.5">
<Ic name="route" className="size-4 text-primary" />
<span>{toFa(destRelations.length)} مسیر</span>
</span>
</div>
<div className="p-3.5 rounded-xl bg-muted/30 border border-border/60">
<span className="text-[11px] text-muted-foreground block">وضعیت در سامانه</span>
<div className="mt-1 flex items-center justify-between">
<span className="text-xs font-bold text-foreground">
{destIsActive ? 'فعال' : 'غیرفعال'}
</span>
<Switch
checked={destIsActive}
onCheckedChange={handleToggleStatus}
isLoading={isTogglingStatus}
disabled={isTogglingStatus}
aria-label="تغییر وضعیت فعال یا غیرفعال بودن شهر مقصد"
/>
</div>
</div>
</div>
{/* ماژول نمایش مشخصات چندزبانه با ServiceTranslation در حالت Readonly */}
<div className="space-y-4 rounded-2xl border border-border/70 bg-card p-4 sm:p-5">
<div className="border-b border-border/60 pb-3">
<ServiceTranslation
activeLang={activeLangTab}
onLangChange={(lang) => setActiveLangTab(lang)}
completedLangs={{
ar: Boolean(names.ar?.trim()),
fa: Boolean(names.fa?.trim()),
en: Boolean(names.en?.trim()),
ru: Boolean(names.ru?.trim()),
id: Boolean(names.id?.trim()),
}}
primaryLang="ar"
title="شناسنامه و متون چندزبانه مقصد"
variant="pills"
/>
</div>
{/* محتوای زبان انتخاب‌شده */}
<div className="space-y-4 pt-1" dir={currentLang.dir}>
<div className="space-y-1">
<span className="text-[11px] text-muted-foreground font-semibold block">
نام شهر مقصد ({currentLang.name}):
</span>
<div className="p-3 rounded-xl bg-muted/40 border border-border/60 text-sm font-bold text-foreground">
{names[activeLangTab] || destination.name || '---'}
</div>
</div>
{holyPlacesMap[activeLangTab] && (
<div className="space-y-1.5">
<span className="text-[11px] text-muted-foreground font-semibold block">
اماکن مقدسه و زیارتگاه‌ها ({currentLang.name}):
</span>
<div className="flex flex-wrap gap-1.5 p-3 rounded-xl bg-muted/40 border border-border/60">
{holyPlacesMap[activeLangTab]
.split(/[،,]/)
.map((p) => p.trim())
.filter(Boolean)
.map((place, idx) => (
<span
key={idx}
className="inline-flex items-center gap-1 px-2.5 py-1 rounded-lg bg-card border border-border/70 text-xs font-semibold text-foreground shadow-2xs"
>
<span>🕌</span>
<span>{place}</span>
</span>
))}
</div>
</div>
)}
<div className="space-y-1">
<span className="text-[11px] text-muted-foreground font-semibold block">
توضیحات و راهنمای شهر ({currentLang.name}):
</span>
<div className="p-3.5 rounded-xl bg-muted/40 border border-border/60 text-xs text-foreground leading-relaxed whitespace-pre-wrap">
{descriptionsMap[activeLangTab] || destination.description || 'توضیحاتی برای این زبان ثبت نشده است.'}
</div>
</div>
</div>
</div>
</div>
{/* ========================================================================= */}
{/* MODAL: EDIT DESTINATION INFO (طبق مدل popup.tsx / dialog.tsx) */}
{/* ========================================================================= */}
<Dialog open={isEditDestModalOpen} onOpenChange={setIsEditDestModalOpen}>
<DialogContent className="sm:max-w-xl md:max-w-2xl w-full rounded-2xl bg-card border border-border-soft p-0 max-h-[88vh] flex flex-col overflow-hidden shadow-2xl">
{/* هدر ثابت مدال */}
<DialogHeader className="p-5 sm:p-6 pb-4 pe-12 border-b border-border/60 shrink-0 bg-card text-start">
<div className="flex items-center gap-3">
<div className="size-10 rounded-xl bg-primary/10 text-primary flex items-center justify-center shrink-0">
<Ic name="map" className="size-5" />
</div>
<div>
<DialogTitle className="text-base sm:text-lg font-bold text-foreground">
ویرایش اطلاعات و شناسنامه شهر {destination.name}
</DialogTitle>
<DialogDescription className="text-xs text-muted-foreground mt-0.5">
ویرایش مشخصات اصلی، موقعیت کشور و توضیحات چندزبانه مقصد
</DialogDescription>
</div>
</div>
</DialogHeader>
{/* فرم: بدنه اسکرول‌شونده و فوتر فیکس‌شده */}
<form onSubmit={handleSaveDestinationInfo} className="flex flex-col flex-1 overflow-hidden min-h-0">
<div className="flex-1 overflow-y-auto p-5 sm:p-6 space-y-4 text-xs sm:text-sm">
{/* تنظیمات رسانه‌ای، تصویر و پیوند به وبلاگ */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 p-3.5 rounded-xl bg-card border border-border/70 shadow-2xs">
<div className="space-y-1.5 sm:col-span-2">
<label className="text-xs font-bold text-foreground flex items-center justify-between">
<span className="flex items-center gap-1.5">
<Ic name="image" className="size-3.5 text-muted-foreground" />
<span>تصویر شهر مقصد (URL یا مسیر فایل)</span>
</span>
{destImage && (
<span className="text-[10px] text-emerald-600 font-medium">دارای تصویر</span>
)}
</label>
<Input
value={destImage}
onChange={(e) => setDestImage(e.target.value)}
dir="ltr"
placeholder="مثال: /media/destinations/karbala.jpg یا https://..."
className="h-10 rounded-xl text-xs bg-card border-border/70 font-mono shadow-2xs"
/>
</div>
<div className="space-y-1.5">
<label className="text-xs font-bold text-foreground block">
تعداد سفرهای فعال:
</label>
<Input
type="number"
min={0}
value={destJourneysCount}
onChange={(e) => setDestJourneysCount(Number(e.target.value) || 0)}
dir="ltr"
className="h-10 rounded-xl text-xs bg-card border-border/70 font-mono shadow-2xs"
/>
</div>
<div className="space-y-1.5 sm:col-span-3">
<label className="text-xs font-bold text-foreground block">
اسلاگ مقاله راهنمای زیارت در بلاگ (اختیاری):
</label>
<Input
value={destBlogSlug}
onChange={(e) => setDestBlogSlug(e.target.value)}
dir="ltr"
placeholder="مثال: ziyarat-rituals-and-prayers-of-karbala"
className="h-10 rounded-xl text-xs bg-card border-border/70 font-mono shadow-2xs"
/>
</div>
</div>
{/* تنظیمات کلی شهر مقصد (کشور و وضعیت) */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 p-3.5 rounded-xl bg-card border border-border/70 shadow-2xs">
{/* انتخاب کشور مقصد */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-foreground block">
کشور مقصد:
</label>
<Select
value={destCountry}
onValueChange={(val) => {
setDestCountry(val)
const preset = PRESET_DEST_COUNTRIES.find((p) => p.name === val)
if (preset) setDestFlag(preset.flag)
}}
>
<SelectTrigger className="h-10 text-xs rounded-xl bg-card border-border/70 hover:border-border">
<SelectValue placeholder="انتخاب کشور مقصد..." />
</SelectTrigger>
<SelectContent>
{PRESET_DEST_COUNTRIES.map((c) => (
<SelectItem key={c.name} value={c.name}>
<span className="flex items-center gap-2">
<span className="text-base select-none">{c.flag}</span>
<span className="font-medium">{c.name}</span>
</span>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* وضعیت شهر در سامانه */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-foreground block">
وضعیت شهر در سامانه:
</label>
<div className="flex h-10 items-center justify-between px-3.5 rounded-xl border border-border/70 bg-card">
<div className="flex items-center gap-2">
<span
className={cn(
'size-2 rounded-full',
destIsActive ? 'bg-emerald-500 animate-pulse' : 'bg-muted-foreground'
)}
/>
<span
className={cn(
'text-xs font-bold',
destIsActive
? 'text-emerald-600 dark:text-emerald-400'
: 'text-muted-foreground'
)}
>
{destIsActive ? 'شهر فعال' : 'شهر غیرفعال'}
</span>
</div>
<Switch
checked={destIsActive}
onCheckedChange={setDestIsActive}
aria-label="تغییر وضعیت فعال بودن شهر مقصد"
/>
</div>
</div>
</div>
{/* ماژول چندزبانه ترجمه */}
<div className="space-y-4 rounded-2xl border border-border/70 bg-card p-4">
<div className="border-b border-border/60 pb-3">
<ServiceTranslation
activeLang={activeLangTab}
onLangChange={(lang) => setActiveLangTab(lang)}
completedLangs={{
ar: Boolean(names.ar?.trim()),
fa: Boolean(names.fa?.trim()),
en: Boolean(names.en?.trim()),
ru: Boolean(names.ru?.trim()),
id: Boolean(names.id?.trim()),
}}
primaryLang="ar"
title="اطلاعات متنی و چندزبانه مقصد"
required
variant="pills"
/>
</div>
{/* فیلدهای ترجمه‌پذیر متناسب با زبان فعال */}
<div className="space-y-3.5 pt-1" dir={currentLang.dir}>
<div className="space-y-1.5">
<div className="flex items-center justify-between">
<label className="text-xs font-bold text-foreground flex items-center gap-1.5">
<Ic name="pencil" className="size-3.5 text-muted-foreground" />
<span>نام شهر مقصد ({currentLang.name})</span>
<span className="text-[10px] px-1.5 py-0.5 rounded font-mono font-bold bg-primary/10 text-primary border border-primary/20">
{currentLang.badge}
</span>
<span className="text-rose-500">*</span>
</label>
<span className="text-[11px] text-muted-foreground font-mono">
{currentLang.dir.toUpperCase()}
</span>
</div>
<Input
value={names[activeLangTab] || ''}
onChange={(e) =>
setNames((prev) => ({ ...prev, [activeLangTab]: e.target.value }))
}
dir={currentLang.dir}
placeholder={`نام شهر به زبان ${currentLang.name}...`}
className="h-10 rounded-xl text-xs bg-card border-border/70 focus-visible:border-primary focus-visible:ring-1 focus-visible:ring-primary shadow-2xs"
required={activeLangTab === 'fa' || activeLangTab === 'ar'}
/>
</div>
<div className="space-y-1.5">
<label className="text-xs font-bold text-foreground flex items-center gap-1.5">
<span>اماکن مقدسه و زیارتگاه‌ها ({currentLang.name})</span>
<span className="text-[11px] text-muted-foreground font-normal">(با ویرگول جدا کنید)</span>
</label>
<Input
value={holyPlacesMap[activeLangTab] || ''}
onChange={(e) =>
setHolyPlacesMap((prev) => ({ ...prev, [activeLangTab]: e.target.value }))
}
dir={currentLang.dir}
placeholder={`اماکن متبرکه به زبان ${currentLang.name}...`}
className="h-10 rounded-xl text-xs bg-card border-border/70 focus-visible:border-primary focus-visible:ring-1 focus-visible:ring-primary shadow-2xs"
/>
</div>
<div className="space-y-1.5">
<label className="text-xs font-bold text-foreground block">
توضیحات و راهنمای شهر ({currentLang.name}):
</label>
<Textarea
value={descriptionsMap[activeLangTab] || ''}
onChange={(e) =>
setDescriptionsMap((prev) => ({ ...prev, [activeLangTab]: e.target.value }))
}
dir={currentLang.dir}
rows={3}
placeholder={`شرح کوتاه اماکن مقدسه، شرایط سفر و ویژگی‌های شهر به زبان ${currentLang.name}...`}
className="text-xs rounded-xl bg-card border-border/70 text-foreground placeholder:text-muted-foreground leading-relaxed focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none resize-y shadow-2xs"
/>
</div>
</div>
</div>
</div>
{/* فوتر فیکس‌شده در پایین مدال */}
<DialogFooter className="p-4 sm:p-5 border-t border-border-soft bg-card/95 backdrop-blur-xs flex items-center justify-end gap-2.5 shrink-0 mt-0">
<Button
type="button"
variant="outline"
onClick={() => setIsEditDestModalOpen(false)}
className="rounded-xl text-xs h-9 px-4 border-border-soft hover:bg-muted"
>
انصراف
</Button>
<Button
type="submit"
isLoading={isSavingDestination}
loadingText={t('common.loading')}
className="rounded-xl text-xs font-bold h-9 px-5 bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs"
>
<Ic name="check" className="size-4" />
<span>ذخیره و ثبت تغییرات شهر مقصد</span>
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
{/* Delete Destination Dialog */}
<DeleteConfirmDialog
open={isDestDeleteOpen}
onOpenChange={setIsDestDeleteOpen}
title={`حذف شهر مقصد ${destination.name}`}
description={`آیا از حذف کامل شهر مقصد «${destination.name}» اطمینان دارید؟ تمام هتل‌ها و مسیرهای مرتبط با آن نیز حذف خواهند شد.`}
onConfirm={handleDeleteDestinationConfirm}
/>
</div>
)}
{/* ========================================================================= */}
{/* TAB 2: HOTELS (هتل‌ها و اقامتگاه‌های مقصد) */}
{/* ========================================================================= */}
{currentTab === 'hotels' && (
<div className="space-y-6 pt-1 sm:pt-2">
{/* Action & Filter Bar */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-3 p-3.5 rounded-2xl bg-card border border-border/70 shadow-xs">
<div className="flex items-center gap-2 flex-1 flex-wrap">
<div className="relative flex-1 min-w-[220px]">
<Ic
name="search"
className="absolute start-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground"
/>
<Input
value={hotelSearch}
onChange={(e) => setHotelSearch(e.target.value)}
placeholder="جستجوی نام هتل یا فاصله تا حرم..."
className="ps-9 h-10 rounded-xl bg-background text-xs sm:text-sm"
/>
</div>
{/* Stars Filter */}
<div className="flex items-center gap-1 p-1 bg-muted/60 rounded-xl border border-border/50 text-xs overflow-x-auto no-scrollbar max-w-full">
{(['all', '5', '4', '3'] as const).map((s) => (
<button
key={s}
type="button"
onClick={() => setHotelStarsFilter(s)}
className={`px-3 py-1.5 rounded-lg font-medium transition-all cursor-pointer whitespace-nowrap shrink-0 ${
hotelStarsFilter === s
? 'bg-card text-foreground shadow-2xs font-bold'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{s === 'all' ? 'همه ستاره‌ها' : `${toFa(s)} ستاره`}
</button>
))}
</div>
</div>
<div className="flex items-center gap-2 justify-end">
{/* View Mode Toggle */}
<div className="flex items-center p-1 bg-muted/60 rounded-xl border border-border/50">
<button
type="button"
onClick={() => setHotelViewMode('cards')}
className={`p-1.5 rounded-lg transition-all cursor-pointer ${
hotelViewMode === 'cards'
? 'bg-card text-foreground shadow-2xs'
: 'text-muted-foreground hover:text-foreground'
}`}
title="نمایش کارتی"
>
<Ic name="grid" className="size-4" />
</button>
<button
type="button"
onClick={() => setHotelViewMode('table')}
className={`p-1.5 rounded-lg transition-all cursor-pointer ${
hotelViewMode === 'table'
? 'bg-card text-foreground shadow-2xs'
: 'text-muted-foreground hover:text-foreground'
}`}
title="نمایش جدولی"
>
<Ic name="list" className="size-4" />
</button>
</div>
<Button
onClick={handleOpenAddHotel}
className="rounded-xl h-10 text-xs sm:text-sm font-bold gap-1.5 bg-primary text-white hover:bg-primary/90 shadow-xs"
>
<Ic name="plus" className="size-4" />
<span>{t('customTrip.addHotel')}</span>
</Button>
</div>
</div>
{/* Hotels Content */}
{filteredHotels.length === 0 ? (
<div className="p-8 text-center rounded-2xl bg-card border border-border/70 space-y-3">
<Ic name="bed" className="size-8 text-muted-foreground mx-auto" />
<p className="text-sm font-medium text-foreground">
هیچ هتلی برای شهر {destination.name} یافت نشد.
</p>
<Button onClick={handleOpenAddHotel} variant="outline" className="rounded-xl text-xs">
+ افزودن اولین هتل برای این شهر
</Button>
</div>
) : hotelViewMode === 'cards' ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3.5">
{filteredHotels.map((hotel) => {
const cleanName = hotel.name.replace(/\s*\([A-Za-z0-9\s\-']+\)/g, '').trim()
return (
<div
key={hotel.id}
className="rounded-2xl bg-card border border-border/70 p-4 hover:border-primary/40 transition-all shadow-xs flex flex-col justify-between gap-3 group"
>
<div className="space-y-2">
{/* Header: Name + Stars + Actions */}
<div className="flex items-start justify-between gap-2">
<div className="space-y-1 min-w-0">
<div className="flex items-center gap-1.5 flex-wrap">
<h3 className="text-sm font-bold text-foreground truncate group-hover:text-primary transition-colors">
{cleanName}
</h3>
<div className="flex items-center text-amber-500 shrink-0">
{Array.from({ length: hotel.stars }).map((_, i) => (
<span key={i} className="text-xs">★</span>
))}
</div>
</div>
{/* Distance & Score row */}
<div className="flex items-center gap-2 text-xs text-muted-foreground flex-wrap">
<span className="flex items-center gap-1 text-[11px]">
<Ic name="mapPin" className="size-3 text-primary shrink-0" />
<span className="truncate">{hotel.distanceToShrine}</span>
</span>
{hotel.score && (
<>
<span className="text-border">•</span>
<span className="font-mono text-emerald-600 dark:text-emerald-400 font-bold text-[11px]">
{toFa(hotel.score)} / ۵
</span>
</>
)}
</div>
</div>
{/* Actions */}
<div className="flex items-center gap-0.5 shrink-0">
<Button
variant="ghost"
size="sm"
onClick={() => handleOpenEditHotel(hotel)}
className="size-7 p-0 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground"
title="ویرایش هتل"
>
<Ic name="pencil" className="size-3.5" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => setHotelToDelete(hotel)}
className="size-7 p-0 rounded-lg hover:bg-rose-500/10 text-muted-foreground hover:text-rose-600"
title="حذف هتل"
>
<Ic name="trash" className="size-3.5" />
</Button>
</div>
</div>
</div>
{/* Bottom Pricing & Status */}
<div className="pt-2.5 border-t border-border/50 flex items-center justify-between text-xs">
<span
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold ${
hotel.isActive
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
: 'bg-muted text-muted-foreground'
}`}
>
<span
className={`size-1.5 rounded-full ${
hotel.isActive ? 'bg-emerald-500' : 'bg-muted-foreground'
}`}
/>
<span>{hotel.isActive ? 'فعال' : 'غیرفعال'}</span>
</span>
<div className="flex items-center gap-2.5 font-mono">
<div className="flex items-baseline gap-1">
<span className="text-muted-foreground text-[10px]">بزرگسال:</span>
<span className="font-bold text-foreground">
${toFa(hotel.pricePerDayAdult)}
</span>
</div>
<span className="text-border text-xs">|</span>
<div className="flex items-baseline gap-1">
<span className="text-muted-foreground text-[10px]">کودک:</span>
<span className="font-bold text-foreground">
${toFa(hotel.pricePerDayChild)}
</span>
</div>
</div>
</div>
</div>
)
})}
</div>
) : (
/* Table View */
<div className="overflow-hidden rounded-2xl border border-border/70 bg-card shadow-xs">
<div className="overflow-x-auto">
<table className="w-full text-xs sm:text-sm text-start">
<thead className="bg-muted/40 text-muted-foreground border-b border-border/60 font-medium">
<tr>
<th className="px-4 py-3.5 text-start">ردیف</th>
<th className="px-4 py-3.5 text-start">نام هتل</th>
<th className="px-4 py-3.5 text-center">ستاره</th>
<th className="px-4 py-3.5 text-start">فاصله تا حرم مطهر</th>
<th className="px-4 py-3.5 text-center font-mono">نرخ بزرگسال</th>
<th className="px-4 py-3.5 text-center font-mono">نرخ کودک</th>
<th className="px-4 py-3.5 text-center">وضعیت</th>
<th className="px-4 py-3.5 text-center">عملیات</th>
</tr>
</thead>
<tbody className="divide-y divide-border/50">
{filteredHotels.map((hotel, idx) => (
<tr key={hotel.id} className="hover:bg-muted/20 transition-colors">
<td className="px-4 py-3.5 font-mono text-muted-foreground">
{toFa(idx + 1)}
</td>
<td className="px-4 py-3.5 font-bold text-foreground">
<div>{hotel.name}</div>
{hotel.score && (
<span className="text-[11px] font-mono text-muted-foreground">
امتیاز {toFa(hotel.score)} / ۵
</span>
)}
</td>
<td className="px-4 py-3.5 text-center">
<span className="text-amber-500 font-bold">
{toFa(hotel.stars)} ★
</span>
</td>
<td className="px-4 py-3.5 text-xs text-muted-foreground max-w-xs">
{hotel.distanceToShrine}
</td>
<td className="px-4 py-3.5 text-center font-mono font-bold text-foreground">
${toFa(hotel.pricePerDayAdult)}
</td>
<td className="px-4 py-3.5 text-center font-mono text-muted-foreground">
${toFa(hotel.pricePerDayChild)}
</td>
<td className="px-4 py-3.5 text-center">
<span
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-semibold ${
hotel.isActive
? 'bg-emerald-500/10 text-emerald-600'
: 'bg-muted text-muted-foreground'
}`}
>
<span
className={`size-1.5 rounded-full ${
hotel.isActive ? 'bg-emerald-500' : 'bg-muted-foreground'
}`}
/>
{hotel.isActive ? 'فعال' : 'غیرفعال'}
</span>
</td>
<td className="px-4 py-3.5 text-center">
<div className="flex items-center justify-center gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => handleOpenEditHotel(hotel)}
className="size-8 p-0 rounded-lg hover:bg-muted"
>
<Ic name="pencil" className="size-3.5" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => setHotelToDelete(hotel)}
className="size-8 p-0 rounded-lg hover:bg-rose-500/10 text-rose-600"
>
<Ic name="trash" className="size-3.5" />
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Delete Hotel Dialog */}
<DeleteConfirmDialog
open={!!hotelToDelete}
onOpenChange={(open) => !open && setHotelToDelete(null)}
title="حذف هتل"
description={`آیا از حذف هتل «${hotelToDelete?.name || ''}» اطمینان دارید؟`}
onConfirm={handleConfirmDeleteHotel}
/>
</div>
)}
{/* ========================================================================= */}
{/* TAB 3: TRANSPORTATION (روش‌ها و مسیرهای حمل‌ونقل) */}
{/* ========================================================================= */}
{currentTab === 'transports' && (
<div className="space-y-6 pt-1 sm:pt-2">
{/* Action & Filter Bar */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-3 p-3.5 rounded-2xl bg-card border border-border/70 shadow-xs">
<div className="flex items-center gap-2 flex-1 flex-wrap">
<div className="relative flex-1 min-w-[220px]">
<Ic
name="search"
className="absolute start-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground"
/>
<Input
value={transportSearch}
onChange={(e) => setTransportSearch(e.target.value)}
placeholder="جستجوی شهر مبدأ، جزئیات خطوط یا راهنمای مسیر..."
className="ps-9 h-10 rounded-xl bg-background text-xs sm:text-sm"
/>
</div>
{/* Method Filter */}
<div className="flex items-center gap-1 p-1 bg-muted/60 rounded-xl border border-border/50 text-xs overflow-x-auto no-scrollbar max-w-full">
{(
[
{ id: 'all', label: 'همه روش‌ها' },
{ id: 'flight', label: '✈️ هوایی' },
{ id: 'train', label: '🚆 ریلی' },
{ id: 'road', label: '🚌 جاده‌ای' },
] as const
).map((m) => (
<button
key={m.id}
type="button"
onClick={() => setTransportMethodFilter(m.id)}
className={`px-3 py-1.5 rounded-lg font-medium transition-all cursor-pointer whitespace-nowrap shrink-0 ${
transportMethodFilter === m.id
? 'bg-card text-foreground shadow-2xs font-bold'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{m.label}
</button>
))}
</div>
</div>
<div className="flex items-center gap-2 justify-end">
<Button
onClick={handleOpenAddTransport}
className="rounded-xl h-10 text-xs sm:text-sm font-bold gap-1.5 bg-primary text-white hover:bg-primary/90 shadow-xs"
>
<Ic name="plus" className="size-4" />
<span>تعریف مسیر مبدأ به {destination.name}</span>
</Button>
</div>
</div>
{/* Transports Cards */}
{filteredRelations.length === 0 ? (
<div className="p-8 text-center rounded-2xl bg-card border border-border/70 space-y-3">
<Ic name="route" className="size-8 text-muted-foreground mx-auto" />
<p className="text-sm font-medium text-foreground">
هیچ مسیر حمل‌ونقلی به مقصد {destination.name} ثبت نشده است.
</p>
<Button onClick={handleOpenAddTransport} variant="outline" className="rounded-xl text-xs">
+ افزودن اولین مسیر ارتباطی به این مقصد
</Button>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3.5">
{filteredRelations.map((rel) => (
<div
key={rel.id}
className="rounded-2xl bg-card border border-border/70 p-4 hover:border-primary/40 transition-all shadow-xs flex flex-col justify-between gap-3 group"
>
<div className="space-y-2.5">
{/* Route Header */}
<div className="flex items-start justify-between gap-2">
<div>
<div className="flex items-center gap-1.5 font-bold text-foreground text-sm group-hover:text-primary transition-colors">
<span>{rel.fromCity}</span>
<Ic name="arrowLeft" className="size-3.5 text-primary" />
<span className="text-primary">{destination.name}</span>
</div>
<span className="text-[11px] text-muted-foreground mt-0.5 block">
{rel.fromCountry}
</span>
</div>
<div className="flex items-center gap-0.5 shrink-0">
<Button
variant="ghost"
size="sm"
onClick={() => handleOpenEditTransport(rel)}
className="size-7 p-0 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground"
title="ویرایش مسیر"
>
<Ic name="pencil" className="size-3.5" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => setTransportToDelete(rel)}
className="size-7 p-0 rounded-lg hover:bg-rose-500/10 text-muted-foreground hover:text-rose-600"
title="حذف مسیر"
>
<Ic name="trash" className="size-3.5" />
</Button>
</div>
</div>
{/* Active Transport Methods */}
<div className="flex items-center gap-1.5 flex-wrap pt-0.5">
{rel.hasAirplane && (
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-lg bg-sky-500/10 text-sky-700 dark:text-sky-300 text-xs font-semibold border border-sky-500/20">
<span>✈️</span>
<span>پرواز هوایی</span>
</span>
)}
{rel.hasTrain && (
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-lg bg-emerald-500/10 text-emerald-700 dark:text-emerald-300 text-xs font-semibold border border-emerald-500/20">
<span>🚆</span>
<span>قطار ریلی</span>
</span>
)}
{rel.hasRoad && (
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-lg bg-amber-500/10 text-amber-700 dark:text-amber-300 text-xs font-semibold border border-amber-500/20">
<span>🚌</span>
<span>جاده‌ای</span>
</span>
)}
</div>
</div>
{/* Route Status Footer */}
<div className="pt-2.5 border-t border-border/50 flex items-center justify-between text-xs">
<span
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold ${
rel.isActive !== false
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
: 'bg-muted text-muted-foreground'
}`}
>
<span
className={`size-1.5 rounded-full ${
rel.isActive !== false ? 'bg-emerald-500' : 'bg-muted-foreground'
}`}
/>
<span>{rel.isActive !== false ? 'مسیر فعال' : 'مسیر غیرفعال'}</span>
</span>
</div>
</div>
))}
</div>
)}
{/* Delete Transport Dialog */}
<DeleteConfirmDialog
open={!!transportToDelete}
onOpenChange={(open) => !open && setTransportToDelete(null)}
title="حذف مسیر حمل‌ونقل"
description={`آیا از حذف مسیر «${transportToDelete?.fromCity} به ${destination.name}» مطمئن هستید؟`}
onConfirm={handleConfirmDeleteTransport}
/>
</div>
)}
{/* ========================================================================= */}
{/* MODAL: ADD / EDIT HOTEL */}
{/* ========================================================================= */}
<Dialog open={isHotelFormOpen} onOpenChange={setIsHotelFormOpen}>
<DialogContent className="max-w-2xl w-full max-h-[88vh] flex flex-col p-0 overflow-hidden rounded-2xl bg-card border border-border/70 shadow-2xl">
<DialogHeader className="p-5 sm:p-6 pb-4 pe-12 border-b border-border/60 shrink-0 bg-card text-start">
<DialogTitle className="text-base font-bold text-foreground flex items-center gap-2">
<Ic name="bed" className="size-5 text-primary" />
{editingHotel ? 'ویرایش مشخصات هتل' : `افزودن هتل جدید به ${destination.name}`}
</DialogTitle>
<DialogDescription className="text-xs text-muted-foreground mt-0.5">
اطلاعات، درجه ستاره، فاصله تا حرم، نرخ‌ها و ترجمه چندزبانه هتل را ثبت کنید.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSaveHotel} className="flex flex-col flex-1 overflow-hidden min-h-0">
<div className="flex-1 overflow-y-auto p-5 sm:p-6 space-y-4 text-xs sm:text-sm">
{/* Locked City Name */}
<div>
<label className="text-xs font-semibold text-foreground block mb-1">شهر مقصد:</label>
<div className="h-10 px-3 flex items-center rounded-xl bg-muted/70 border border-border/70 text-foreground font-bold">
{destination.flag} {destination.name} ({destination.country})
</div>
</div>
{/* ServiceTranslation Module for Hotel */}
<div className="rounded-2xl border border-border/70 bg-card p-4 space-y-4 shadow-2xs">
<div className="border-b border-border/60 pb-3">
<ServiceTranslation
activeLang={hotelLangTab}
onLangChange={(lang) => setHotelLangTab(lang)}
completedLangs={{
ar: Boolean(hotelNames.ar?.trim()),
fa: Boolean(hotelNames.fa?.trim()),
en: Boolean(hotelNames.en?.trim()),
ru: Boolean(hotelNames.ru?.trim()),
id: Boolean(hotelNames.id?.trim()),
}}
primaryLang="ar"
title="اطلاعات متنی و چندزبانه هتل"
required
variant="pills"
/>
</div>
{/* Language Specific Fields */}
<div className="space-y-3.5 pt-1" dir={currentHotelLang.dir}>
{/* Hotel Name */}
<div className="space-y-1">
<div className="flex items-center justify-between">
<label className="text-xs font-bold text-foreground flex items-center gap-1.5">
<Ic name="pencil" className="size-3.5 text-muted-foreground" />
<span>نام هتل ({currentHotelLang.name})</span>
<span className="text-[10px] px-1.5 py-0.5 rounded font-mono font-bold bg-primary/10 text-primary border border-primary/20">
{currentHotelLang.badge}
</span>
<span className="text-rose-500">*</span>
</label>
<span className="text-[11px] text-muted-foreground font-mono">
{currentHotelLang.dir.toUpperCase()}
</span>
</div>
<Input
value={hotelNames[hotelLangTab] || ''}
onChange={(e) =>
setHotelNames((prev) => ({ ...prev, [hotelLangTab]: e.target.value }))
}
dir={currentHotelLang.dir}
placeholder={`نام هتل به زبان ${currentHotelLang.name}...`}
className="h-10 rounded-xl text-xs bg-background"
required={hotelLangTab === 'fa' || hotelLangTab === 'ar'}
/>
</div>
{/* Distance to Shrine */}
<div className="space-y-1">
<label className="text-xs font-bold text-foreground block">
فاصله و نزدیکی به حرم مطهر ({currentHotelLang.name}):
</label>
<Input
value={hotelDistances[hotelLangTab] || ''}
onChange={(e) =>
setHotelDistances((prev) => ({ ...prev, [hotelLangTab]: e.target.value }))
}
dir={currentHotelLang.dir}
placeholder={`مثال: ۴۰۰ متر تا حرم مطهر به زبان ${currentHotelLang.name}...`}
className="h-10 rounded-xl text-xs bg-background"
/>
</div>
{/* Description */}
<div className="space-y-1">
<label className="text-xs font-bold text-foreground block">
توضیحات و مشخصات هتل ({currentHotelLang.name}):
</label>
<Textarea
value={hotelDescriptions[hotelLangTab] || ''}
onChange={(e) =>
setHotelDescriptions((prev) => ({ ...prev, [hotelLangTab]: e.target.value }))
}
dir={currentHotelLang.dir}
rows={2}
placeholder={`شرح امکانات و شرایط هتل به زبان ${currentHotelLang.name}...`}
className="text-xs rounded-xl bg-background border-border/70 text-foreground leading-relaxed focus:ring-1 focus:ring-primary focus:outline-none"
/>
</div>
{/* Guide Note */}
<div className="space-y-1">
<label className="text-xs font-bold text-foreground block">
راهنما و نکات تجربی اقامت ({currentHotelLang.name}):
</label>
<Textarea
value={hotelGuideNotes[hotelLangTab] || ''}
onChange={(e) =>
setHotelGuideNotes((prev) => ({ ...prev, [hotelLangTab]: e.target.value }))
}
dir={currentHotelLang.dir}
rows={2}
placeholder={`نکات راهنما برای مسافران به زبان ${currentHotelLang.name}...`}
className="text-xs rounded-xl bg-background border-border/70 text-foreground leading-relaxed focus:ring-1 focus:ring-primary focus:outline-none"
/>
</div>
</div>
</div>
{/* Stars & Score */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label className="text-xs font-semibold text-foreground block mb-1">
درجه هتل (تعداد ستاره):
</label>
<Select
value={String(hotelFormStars)}
onValueChange={(val) => setHotelFormStars(Number(val))}
>
<SelectTrigger className="h-10 text-xs rounded-xl bg-card border-border/70 hover:border-border">
<SelectValue placeholder="انتخاب درجه ستاره..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="5">۵ ستاره VIP (لوکس)</SelectItem>
<SelectItem value="4">۴ ستاره عالی</SelectItem>
<SelectItem value="3">۳ ستاره اقتصادی</SelectItem>
<SelectItem value="2">۲ ستاره</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="text-xs font-semibold text-foreground block mb-1">
امتیاز رضایت (از ۵):
</label>
<Input
type="number"
step="0.1"
min="1"
max="5"
value={hotelFormScore}
onChange={(e) => setHotelFormScore(Number(e.target.value))}
className="h-10 rounded-xl font-mono"
/>
</div>
</div>
{/* Daily Pricing */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label className="text-xs font-semibold text-foreground block mb-1">
نرخ روزانه بزرگسال (دلار $):
</label>
<Input
type="number"
min="0"
value={hotelFormPriceAdult}
onChange={(e) => setHotelFormPriceAdult(Number(e.target.value))}
className="h-10 rounded-xl font-mono"
/>
</div>
<div>
<label className="text-xs font-semibold text-foreground block mb-1">
نرخ روزانه کودک (دلار $):
</label>
<Input
type="number"
min="0"
value={hotelFormPriceChild}
onChange={(e) => setHotelFormPriceChild(Number(e.target.value))}
className="h-10 rounded-xl font-mono"
/>
</div>
</div>
{/* Active Status */}
<div className="flex items-center justify-between p-3.5 rounded-xl bg-card border border-border/70">
<span className="text-xs font-semibold text-foreground">وضعیت هتل (فعال در سامانه):</span>
<div className="flex items-center gap-2">
<span
className={cn(
'text-xs font-bold',
hotelFormIsActive
? 'text-emerald-600 dark:text-emerald-400'
: 'text-muted-foreground'
)}
>
{hotelFormIsActive ? 'فعال' : 'غیرفعال'}
</span>
<Switch
checked={hotelFormIsActive}
onCheckedChange={setHotelFormIsActive}
aria-label="تغییر وضعیت فعال بودن هتل"
/>
</div>
</div>
{/* Amenities Checkboxes */}
<div>
<label className="text-xs font-semibold text-foreground block mb-1.5">
امکانات و خدمات هتل:
</label>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 p-2.5 rounded-xl bg-muted/30 border border-border/60 max-h-36 overflow-y-auto">
{COMMON_AMENITIES.map((amenity) => {
const isChecked = hotelFormAmenities.includes(amenity.name)
return (
<label
key={amenity.id}
className="flex items-center gap-2 cursor-pointer text-xs text-foreground p-1 hover:bg-card rounded-lg"
>
<input
type="checkbox"
checked={isChecked}
onChange={() => handleToggleHotelAmenity(amenity.name)}
className="rounded border-border text-primary focus:ring-primary"
/>
<span>{amenity.name}</span>
</label>
)
})}
</div>
{/* Add Custom Amenity */}
<div className="flex items-center gap-2 mt-2">
<Input
value={hotelFormCustomAmenity}
onChange={(e) => setHotelFormCustomAmenity(e.target.value)}
placeholder="افزودن امکانات دیگر..."
className="h-9 rounded-xl text-xs"
/>
<Button
type="button"
variant="outline"
size="sm"
onClick={handleAddCustomHotelAmenity}
className="rounded-xl h-9 text-xs"
>
افزودن
</Button>
</div>
</div>
</div>
<DialogFooter className="p-4 sm:p-5 border-t border-border-soft shrink-0 bg-card/95 backdrop-blur-xs flex items-center justify-end gap-2.5 mt-0">
<Button
type="button"
variant="outline"
onClick={() => setIsHotelFormOpen(false)}
className="rounded-xl text-xs h-9 px-4 border-border-soft hover:bg-muted"
>
انصراف
</Button>
<Button
type="submit"
className="rounded-xl text-xs font-bold h-9 px-5 bg-primary text-white hover:bg-primary/90 shadow-xs"
>
{editingHotel ? 'ذخیره تغییرات هتل' : 'ثبت نهایی هتل'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
{/* ========================================================================= */}
{/* MODAL: ADD / EDIT TRANSPORT ROUTE */}
{/* ========================================================================= */}
<Dialog open={isTransportFormOpen} onOpenChange={setIsTransportFormOpen}>
<DialogContent className="max-w-2xl w-full max-h-[88vh] flex flex-col p-0 overflow-hidden rounded-2xl bg-card border border-border/70 shadow-2xl">
<DialogHeader className="p-5 sm:p-6 pb-4 pe-12 border-b border-border/60 shrink-0 bg-card text-start">
<DialogTitle className="text-base font-bold text-foreground flex items-center gap-2">
<Ic name="route" className="size-5 text-primary" />
{editingTransport ? 'ویرایش مسیر حمل‌ونقل' : `تعریف مسیر حمل‌ونقل جدید به ${destination.name}`}
</DialogTitle>
<DialogDescription className="text-xs text-muted-foreground mt-0.5">
مشخصات وسایل نقلیه، خطوط و اطلاعات چندزبانه مسیر را ثبت کنید.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSaveTransport} className="flex flex-col flex-1 overflow-hidden min-h-0">
<div className="flex-1 overflow-y-auto p-5 sm:p-6 space-y-4 text-xs sm:text-sm">
{/* Origin & Destination City Selectors (unlocked) */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label className="text-xs font-semibold text-foreground block mb-1">شهر مبدأ:</label>
<select
value={transportFormFromCity}
onChange={(e) => {
const cityName = e.target.value
setTransportFormFromCity(cityName)
const orig = allOrigins.find((o) => o.name === cityName)
if (orig) setTransportFormFromCountry(orig.country)
}}
className="w-full h-10 px-3 text-xs sm:text-sm rounded-xl bg-background border border-border/70 text-foreground font-medium focus:ring-1 focus:ring-primary focus:outline-none"
>
{allOrigins.map((orig) => (
<option key={orig.id} value={orig.name}>
{orig.flag} {orig.name} ({orig.country})
</option>
))}
</select>
</div>
<div>
<label className="text-xs font-semibold text-foreground block mb-1">شهر مقصد:</label>
<select
value={transportFormToCity}
onChange={(e) => {
const cityName = e.target.value
setTransportFormToCity(cityName)
const dst = allDestinations.find((d) => d.name === cityName)
if (dst) setTransportFormToCountry(dst.country)
}}
className="w-full h-10 px-3 text-xs sm:text-sm rounded-xl bg-background border border-border/70 text-foreground font-medium focus:ring-1 focus:ring-primary focus:outline-none"
>
{allDestinations.map((dst) => (
<option key={dst.id} value={dst.name}>
{dst.flag} {dst.name} ({dst.country})
</option>
))}
</select>
</div>
</div>
{/* Transport Methods Selection */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2.5 p-3 rounded-xl bg-card border border-border/70">
<label className="flex items-center gap-2 cursor-pointer font-bold text-foreground text-xs">
<input
type="checkbox"
checked={transportFormHasAirplane}
onChange={(e) => setTransportFormHasAirplane(e.target.checked)}
className="rounded border-border text-primary focus:ring-primary"
/>
<span>✈️ پرواز هوایی</span>
</label>
<label className="flex items-center gap-2 cursor-pointer font-bold text-foreground text-xs">
<input
type="checkbox"
checked={transportFormHasTrain}
onChange={(e) => setTransportFormHasTrain(e.target.checked)}
className="rounded border-border text-primary focus:ring-primary"
/>
<span>🚆 قطار ریلی</span>
</label>
<label className="flex items-center gap-2 cursor-pointer font-bold text-foreground text-xs">
<input
type="checkbox"
checked={transportFormHasRoad}
onChange={(e) => setTransportFormHasRoad(e.target.checked)}
className="rounded border-border text-primary focus:ring-primary"
/>
<span>🚌 مسیر جاده‌ای</span>
</label>
</div>
{/* Active Switch */}
<div className="flex items-center justify-between p-3.5 rounded-xl bg-card border border-border/70">
<span className="text-xs font-semibold text-foreground">وضعیت مسیر (فعال در سامانه):</span>
<div className="flex items-center gap-2">
<span
className={cn(
'text-xs font-bold',
transportFormIsActive
? 'text-emerald-600 dark:text-emerald-400'
: 'text-muted-foreground'
)}
>
{transportFormIsActive ? 'فعال' : 'غیرفعال'}
</span>
<Switch
checked={transportFormIsActive}
onCheckedChange={setTransportFormIsActive}
aria-label="تغییر وضعیت فعال بودن مسیر"
/>
</div>
</div>
{/* ServiceTranslation Module for Transport Route */}
<div className="rounded-2xl border border-border/70 bg-card p-4 space-y-4 shadow-2xs">
<div className="border-b border-border/60 pb-3">
<ServiceTranslation
activeLang={transportLangTab}
onLangChange={(lang) => setTransportLangTab(lang)}
completedLangs={{
ar: Boolean(
transportAirplaneDetailsMap.ar?.trim() ||
transportTrainDetailsMap.ar?.trim() ||
transportRoadDetailsMap.ar?.trim()
),
fa: Boolean(
transportAirplaneDetailsMap.fa?.trim() ||
transportTrainDetailsMap.fa?.trim() ||
transportRoadDetailsMap.fa?.trim()
),
en: Boolean(
transportAirplaneDetailsMap.en?.trim() ||
transportTrainDetailsMap.en?.trim() ||
transportRoadDetailsMap.en?.trim()
),
ru: Boolean(
transportAirplaneDetailsMap.ru?.trim() ||
transportTrainDetailsMap.ru?.trim() ||
transportRoadDetailsMap.ru?.trim()
),
id: Boolean(
transportAirplaneDetailsMap.id?.trim() ||
transportTrainDetailsMap.id?.trim() ||
transportRoadDetailsMap.id?.trim()
),
}}
primaryLang="ar"
title="توضیحات و جزئیات روش‌های حمل‌ونقل"
variant="pills"
/>
</div>
{/* Language Specific Transport Descriptions: exactly 3 optional descriptions */}
<div className="space-y-3.5 pt-1" dir={currentTransportLang.dir}>
{/* 1. Airplane Details */}
<div className="space-y-1">
<div className="flex items-center justify-between">
<label className="text-xs font-bold text-foreground flex items-center gap-1.5">
<span>✈️</span>
<span>توضیحات پرواز هوایی ({currentTransportLang.name})</span>
<span className="text-[10px] text-muted-foreground font-normal">(اختیاری)</span>
{!transportFormHasAirplane && (
<span className="text-[10px] text-muted-foreground bg-muted px-1.5 py-0.5 rounded">
غیرفعال در این مسیر
</span>
)}
</label>
<span className="text-[11px] text-muted-foreground font-mono">
{currentTransportLang.dir.toUpperCase()}
</span>
</div>
<Textarea
value={transportAirplaneDetailsMap[transportLangTab] || ''}
onChange={(e) =>
setTransportAirplaneDetailsMap((prev) => ({
...prev,
[transportLangTab]: e.target.value,
}))
}
dir={currentTransportLang.dir}
rows={2}
placeholder={`توضیحات اختیاری خطوط هوایی و پروازها به زبان ${currentTransportLang.name}...`}
className="text-xs rounded-xl bg-background border-border/70 text-foreground leading-relaxed focus:ring-1 focus:ring-primary focus:outline-none"
/>
</div>
{/* 2. Train Details */}
<div className="space-y-1">
<div className="flex items-center justify-between">
<label className="text-xs font-bold text-foreground flex items-center gap-1.5">
<span>🚆</span>
<span>توضیحات قطار ریلی ({currentTransportLang.name})</span>
<span className="text-[10px] text-muted-foreground font-normal">(اختیاری)</span>
{!transportFormHasTrain && (
<span className="text-[10px] text-muted-foreground bg-muted px-1.5 py-0.5 rounded">
غیرفعال در این مسیر
</span>
)}
</label>
<span className="text-[11px] text-muted-foreground font-mono">
{currentTransportLang.dir.toUpperCase()}
</span>
</div>
<Textarea
value={transportTrainDetailsMap[transportLangTab] || ''}
onChange={(e) =>
setTransportTrainDetailsMap((prev) => ({
...prev,
[transportLangTab]: e.target.value,
}))
}
dir={currentTransportLang.dir}
rows={2}
placeholder={`توضیحات اختیاری قطار ریلی به زبان ${currentTransportLang.name}...`}
className="text-xs rounded-xl bg-background border-border/70 text-foreground leading-relaxed focus:ring-1 focus:ring-primary focus:outline-none"
/>
</div>
{/* 3. Road Details */}
<div className="space-y-1">
<div className="flex items-center justify-between">
<label className="text-xs font-bold text-foreground flex items-center gap-1.5">
<span>🚌</span>
<span>توضیحات مسیر جاده‌ای ({currentTransportLang.name})</span>
<span className="text-[10px] text-muted-foreground font-normal">(اختیاری)</span>
{!transportFormHasRoad && (
<span className="text-[10px] text-muted-foreground bg-muted px-1.5 py-0.5 rounded">
غیرفعال در این مسیر
</span>
)}
</label>
<span className="text-[11px] text-muted-foreground font-mono">
{currentTransportLang.dir.toUpperCase()}
</span>
</div>
<Textarea
value={transportRoadDetailsMap[transportLangTab] || ''}
onChange={(e) =>
setTransportRoadDetailsMap((prev) => ({
...prev,
[transportLangTab]: e.target.value,
}))
}
dir={currentTransportLang.dir}
rows={2}
placeholder={`توضیحات اختیاری ترابری اتوبوس و ون به زبان ${currentTransportLang.name}...`}
className="text-xs rounded-xl bg-background border-border/70 text-foreground leading-relaxed focus:ring-1 focus:ring-primary focus:outline-none"
/>
</div>
</div>
</div>
</div>
<DialogFooter className="p-4 sm:p-5 border-t border-border-soft shrink-0 bg-card/95 backdrop-blur-xs flex items-center justify-end gap-2.5 mt-0">
<Button
type="button"
variant="outline"
onClick={() => setIsTransportFormOpen(false)}
className="rounded-xl text-xs h-9 px-4 border-border-soft hover:bg-muted"
>
انصراف
</Button>
<Button
type="submit"
className="rounded-xl text-xs font-bold h-9 px-5 bg-primary text-white hover:bg-primary/90 shadow-xs"
>
{editingTransport ? 'ذخیره تغییرات مسیر' : 'ثبت مسیر حمل‌ونقل'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</div>
)
}