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.
 
 
 

1113 lines
49 KiB

import * as React from 'react'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
DialogClose,
} from '@/components/ui/dialog'
import { Button, ButtonSpinner } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Ic } from '@/icons'
import { createTour, uploadTourImage, type CreateTourPayload } from '../services/tour-api'
import type { TourTranslationData } from '../types'
interface CreateTourDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
onSuccess?: () => void
}
const SUPPORTED_LANGUAGES = [
{ code: 'ar', label: 'العربية', flag: '🇸🇦', dir: 'rtl' },
{ code: 'fa', label: 'فارسی', flag: '🇮🇷', dir: 'rtl' },
{ code: 'en', label: 'English', flag: '🇬🇧', dir: 'ltr' },
{ code: 'ru', label: 'Русский', flag: '🇷🇺', dir: 'ltr' },
{ code: 'id', label: 'Indonesia', flag: '🇮🇩', dir: 'ltr' },
] as const
type LangCode = (typeof SUPPORTED_LANGUAGES)[number]['code']
export function CreateTourDialog({
open,
onOpenChange,
onSuccess,
}: CreateTourDialogProps) {
const [activeTab, setActiveTab] = React.useState<'basic' | 'features_tips' | 'translations'>('basic')
const [selectedTransLang, setSelectedTransLang] = React.useState<LangCode>('en')
const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false)
const [errorMessage, setErrorMessage] = React.useState<string | null>(null)
const [successMessage, setSuccessMessage] = React.useState<string | null>(null)
// Basic info states
const [title, setTitle] = React.useState('')
const [description, setDescription] = React.useState('')
const [startedAt, setStartedAt] = React.useState('')
const [endedAt, setEndedAt] = React.useState('')
const [capacity, setCapacity] = React.useState('40')
const [primaryLang, setPrimaryLang] = React.useState<LangCode>('ar')
const [status, setStatus] = React.useState('AVAILABLE')
const [visibility, setVisibility] = React.useState<boolean>(true)
// Image states (mandatory cover photo)
const [imageId, setImageId] = React.useState<number | null>(null)
const [imageUrl, setImageUrl] = React.useState<string | null>(null)
const [imageName, setImageName] = React.useState<string | null>(null)
const [isUploadingImage, setIsUploadingImage] = React.useState<boolean>(false)
const [imageUploadError, setImageUploadError] = React.useState<string | null>(null)
const fileInputRef = React.useRef<HTMLInputElement | null>(null)
// Pricing states
const [price, setPrice] = React.useState('')
const [priceChild, setPriceChild] = React.useState('')
const [priceInfant, setPriceInfant] = React.useState('')
const [percentOff, setPercentOff] = React.useState('0')
// Base features & tips
const [features, setFeatures] = React.useState<string[]>([])
const [newFeature, setNewFeature] = React.useState('')
const [tips, setTips] = React.useState<{ title: string; desc: string }[]>([])
const [newTipTitle, setNewTipTitle] = React.useState('')
const [newTipDesc, setNewTipDesc] = React.useState('')
// Multi-language translation state
const [translations, setTranslations] = React.useState<Record<string, TourTranslationData>>({
fa: { title: '', description: '', tour_feature: [], travel_tips: {} },
ar: { title: '', description: '', tour_feature: [], travel_tips: {} },
en: { title: '', description: '', tour_feature: [], travel_tips: {} },
ru: { title: '', description: '', tour_feature: [], travel_tips: {} },
id: { title: '', description: '', tour_feature: [], travel_tips: {} },
})
// Reset form when opened
React.useEffect(() => {
if (open) {
setErrorMessage(null)
setSuccessMessage(null)
setActiveTab('basic')
setImageId(null)
setImageUrl(null)
setImageName(null)
setImageUploadError(null)
}
}, [open])
const processImageUpload = async (file: File) => {
if (!file.type.startsWith('image/')) {
setImageUploadError('لطفاً فقط فایل تصویری (JPG، PNG، WEBP) انتخاب نمایید.')
return
}
if (file.size > 15 * 1024 * 1024) {
setImageUploadError('حجم تصویر نباید بیشتر از 15 مگابایت باشد.')
return
}
setImageUploadError(null)
setIsUploadingImage(true)
try {
const res = await uploadTourImage(file)
setImageId(res.id)
setImageUrl(res.url || URL.createObjectURL(file))
setImageName(file.name)
} catch (err: any) {
setImageUploadError(err?.message || 'خطا در آپلود تصویر در سرور.')
} finally {
setIsUploadingImage(false)
}
}
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file) return
await processImageUpload(file)
}
const handleRemoveImage = () => {
setImageId(null)
setImageUrl(null)
setImageName(null)
setImageUploadError(null)
if (fileInputRef.current) {
fileInputRef.current.value = ''
}
}
const handleAddFeature = () => {
if (!newFeature.trim()) return
setFeatures((prev) => [...prev, newFeature.trim()])
setNewFeature('')
}
const handleRemoveFeature = (idx: number) => {
setFeatures((prev) => prev.filter((_, i) => i !== idx))
}
const handleAddTip = () => {
if (!newTipTitle.trim() || !newTipDesc.trim()) return
setTips((prev) => [...prev, { title: newTipTitle.trim(), desc: newTipDesc.trim() }])
setNewTipTitle('')
setNewTipDesc('')
}
const handleRemoveTip = (idx: number) => {
setTips((prev) => prev.filter((_, i) => i !== idx))
}
// Helper for updating translation fields for selectedTransLang
const handleTransFieldChange = (field: 'title' | 'description', val: string) => {
setTranslations((prev) => ({
...prev,
[selectedTransLang]: {
...prev[selectedTransLang],
[field]: val,
},
}))
}
const handleAddTransFeature = (text: string) => {
if (!text.trim()) return
setTranslations((prev) => {
const current = prev[selectedTransLang] || { tour_feature: [] }
const currFeats = current.tour_feature || []
return {
...prev,
[selectedTransLang]: {
...current,
tour_feature: [...currFeats, { title: text.trim() }],
},
}
})
}
const handleRemoveTransFeature = (idx: number) => {
setTranslations((prev) => {
const current = prev[selectedTransLang] || { tour_feature: [] }
const currFeats = current.tour_feature || []
return {
...prev,
[selectedTransLang]: {
...current,
tour_feature: currFeats.filter((_, i) => i !== idx),
},
}
})
}
const handleAddTransTip = (tipTitle: string, tipDesc: string) => {
if (!tipTitle.trim() || !tipDesc.trim()) return
setTranslations((prev) => {
const current = prev[selectedTransLang] || { travel_tips: {} }
const currTips = current.travel_tips || {}
return {
...prev,
[selectedTransLang]: {
...current,
travel_tips: {
...currTips,
[tipTitle.trim()]: tipDesc.trim(),
},
},
}
})
}
const handleRemoveTransTip = (tipTitle: string) => {
setTranslations((prev) => {
const current = prev[selectedTransLang] || { travel_tips: {} }
const currTips = { ...(current.travel_tips || {}) }
delete currTips[tipTitle]
return {
...prev,
[selectedTransLang]: {
...current,
travel_tips: currTips,
},
}
})
}
const [newTransFeatText, setNewTransFeatText] = React.useState('')
const [newTransTipTitle, setNewTransTipTitle] = React.useState('')
const [newTransTipDesc, setNewTransTipDesc] = React.useState('')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setErrorMessage(null)
setSuccessMessage(null)
// Validation
if (!title.trim()) {
setErrorMessage('لطفاً عنوان اصلی تور را وارد نمایید.')
setActiveTab('basic')
return
}
if (!imageId) {
setErrorMessage('بارگذاری تصویر شاخص برای ایجاد تور الزامی است.')
setActiveTab('basic')
return
}
if (!startedAt) {
setErrorMessage('لطفاً تاریخ شروع تور را مشخص نمایید.')
setActiveTab('basic')
return
}
if (!endedAt) {
setErrorMessage('لطفاً تاریخ پایان تور را مشخص نمایید.')
setActiveTab('basic')
return
}
if (new Date(endedAt) < new Date(startedAt)) {
setErrorMessage('تاریخ پایان تور نمی‌تواند قبل از تاریخ شروع باشد.')
setActiveTab('basic')
return
}
if (!price || isNaN(Number(price)) || Number(price) <= 0) {
setErrorMessage('لطفاً قیمت معتبر برای بزرگسال وارد نمایید.')
setActiveTab('basic')
return
}
if (!capacity || isNaN(Number(capacity)) || Number(capacity) <= 0) {
setErrorMessage('لطفاً ظرفیت معتبر برای تور وارد نمایید.')
setActiveTab('basic')
return
}
setIsSubmitting(true)
try {
const tour_feature = features.map((f) => ({ title: f }))
const travel_tips: Record<string, string> = {}
tips.forEach((t) => {
travel_tips[t.title] = t.desc
})
// Clean translations payload: include any language that has data
const cleanedTranslations: Record<string, TourTranslationData> = {}
Object.entries(translations).forEach(([code, data]) => {
const hasTitle = Boolean(data.title?.trim())
const hasDesc = Boolean(data.description?.trim())
const hasFeats = Boolean(data.tour_feature && data.tour_feature.length > 0)
const hasTips = Boolean(data.travel_tips && Object.keys(data.travel_tips).length > 0)
if (hasTitle || hasDesc || hasFeats || hasTips) {
cleanedTranslations[code] = {
title: data.title?.trim() || (code === primaryLang ? title.trim() : ''),
description: data.description?.trim() || '',
tour_feature: data.tour_feature || [],
travel_tips: data.travel_tips || {},
}
}
})
// Ensure primary language translation is also present if not defined
if (!cleanedTranslations[primaryLang]) {
cleanedTranslations[primaryLang] = {
title: title.trim(),
description: description.trim(),
tour_feature,
travel_tips,
}
}
const payload: CreateTourPayload = {
title: title.trim(),
description: description.trim(),
started_at: startedAt,
ended_at: endedAt,
capacity: Number(capacity),
price: String(price),
price_child: priceChild ? String(priceChild) : '0',
price_infant: priceInfant ? String(priceInfant) : '0',
percent_off: percentOff ? Number(percentOff) : 0,
language_code: primaryLang,
status,
tour_feature,
travel_tips,
visibility,
translations: cleanedTranslations,
image_id: imageId,
image_ids: [imageId],
}
const createdTour = await createTour(payload)
setSuccessMessage(`تور "${createdTour.title}" با موفقیت ایجاد و ترجمه‌ها در دیتابیس ثبت گردید.`)
setTimeout(() => {
if (onSuccess) onSuccess()
onOpenChange(false)
}, 900)
} catch (err: any) {
setErrorMessage(err?.message || 'خطا در ثبت تور جدید در سرور.')
} finally {
setIsSubmitting(false)
}
}
const activeTransConfig = SUPPORTED_LANGUAGES.find((l) => l.code === selectedTransLang)
const currentTransData = translations[selectedTransLang] || {
title: '',
description: '',
tour_feature: [],
travel_tips: {},
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
onPointerDownOutside={(e) => e.preventDefault()}
onInteractOutside={(e) => e.preventDefault()}
className="max-w-5xl xl:max-w-6xl w-[95vw] p-0 overflow-hidden h-[90vh] min-h-[600px] max-h-[92vh] flex flex-col border border-border-soft bg-background rounded-2xl shadow-2xl"
>
{/* Header */}
<DialogHeader className="px-6 pt-5 pb-4 border-b border-border/50 bg-secondary/15">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2.5">
<div className="flex size-9 items-center justify-center rounded-xl bg-primary/10 text-primary shrink-0">
<Ic name="plus" className="size-5" />
</div>
<div>
<DialogTitle className="text-base font-extrabold text-foreground">
افزودن تور زیارتی و گردشگری جدید
</DialogTitle>
<DialogDescription className="text-xs text-grey-3 mt-0.5">
ثبت مشخصات پایه، هزینه‌ها، ظرفیت، تصویر شاخص و ورودی‌های ترجمه 5 زبانه
</DialogDescription>
</div>
</div>
<div className="flex items-center gap-2">
{/* Step/Tab buttons */}
<div className="flex items-center gap-1 rounded-xl bg-surface-2 p-1 border border-border/40">
<button
type="button"
onClick={() => setActiveTab('basic')}
className={`px-3 py-1.5 text-xs font-bold rounded-lg transition-all cursor-pointer ${
activeTab === 'basic'
? 'bg-primary text-white shadow-xs'
: 'text-grey-3 hover:text-foreground'
}`}
>
1. مشخصات و قیمت
</button>
<button
type="button"
onClick={() => setActiveTab('features_tips')}
className={`px-3 py-1.5 text-xs font-bold rounded-lg transition-all cursor-pointer ${
activeTab === 'features_tips'
? 'bg-primary text-white shadow-xs'
: 'text-grey-3 hover:text-foreground'
}`}
>
2. ویژگی‌ها و نکات
</button>
<button
type="button"
onClick={() => setActiveTab('translations')}
className={`px-3 py-1.5 text-xs font-bold rounded-lg transition-all flex items-center gap-1.5 cursor-pointer ${
activeTab === 'translations'
? 'bg-primary text-white shadow-xs'
: 'text-grey-3 hover:text-foreground'
}`}
>
<Ic name="globe" className="size-3.5" />
<span>3. ترجمه‌های 5 زبانه</span>
</button>
</div>
{/* دکمه علامت ضربدر بالای مودال */}
<button
type="button"
onClick={() => onOpenChange(false)}
aria-label="بستن پنجره"
title="بستن"
className="flex size-8 items-center justify-center rounded-lg text-grey-3 hover:text-foreground hover:bg-white/10 transition-colors shrink-0 cursor-pointer"
>
<Ic name="x" className="size-4.5" />
</button>
</div>
</div>
</DialogHeader>
{/* Form Body */}
<form onSubmit={handleSubmit} className="flex-1 overflow-y-auto px-6 py-5 space-y-5 flex flex-col justify-between">
<div className="space-y-4">
{errorMessage && (
<div className="flex items-center gap-2 rounded-xl bg-rose-500/10 border border-rose-500/20 px-4 py-3 text-xs text-rose-400">
<Ic name="alert" className="size-4 shrink-0" />
<span>{errorMessage}</span>
</div>
)}
{successMessage && (
<div className="flex items-center gap-2 rounded-xl bg-emerald-500/10 border border-emerald-500/20 px-4 py-3 text-xs text-emerald-400">
<Ic name="check" className="size-4 shrink-0" />
<span>{successMessage}</span>
</div>
)}
{/* TAB 1: Basic Info & Pricing */}
{activeTab === 'basic' && (
<div className="space-y-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="sm:col-span-2">
<label className="block text-xs font-bold text-foreground mb-1.5">
عنوان اصلی تور <span className="text-rose-500">*</span>:
</label>
<Input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="مثال: تور زیارتی عتبات عالیات نجف و کربلا (ویژه اربعین)..."
className="bg-surface-2 border-border/50 text-sm font-medium"
required
/>
</div>
{/* بخش آپلود تصویر شاخص تور (الزامی) */}
<div className="sm:col-span-2">
<div className="flex items-center justify-between mb-1.5">
<label className="text-xs font-bold text-foreground flex items-center gap-1.5">
<span>تصویر شاخص تور (کاور)</span>
<span className="text-rose-500 font-black">*</span>
<span className="text-[11px] font-normal text-grey-3">(الزامی - JPG، PNG، WEBP)</span>
</label>
{imageId && (
<span className="inline-flex items-center gap-1 text-[11px] font-bold text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded-md border border-emerald-500/20">
<Ic name="check" className="size-3" />
تصویر بارگذاری شد (کد: #{imageId})
</span>
)}
</div>
<input
ref={fileInputRef}
type="file"
accept="image/jpeg,image/png,image/webp,image/jpg"
onChange={handleFileChange}
className="hidden"
/>
{imageUrl ? (
<div className="relative overflow-hidden rounded-2xl border border-border-soft bg-surface-2 p-3 flex items-center gap-4">
<div className="relative size-20 sm:size-24 rounded-xl overflow-hidden bg-black/40 border border-border-soft shrink-0">
<img
src={imageUrl}
alt="پیش‌نمایش کاور تور"
className="size-full object-cover"
/>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<p className="text-sm font-bold text-foreground truncate">
{imageName || 'تصویر تور'}
</p>
<span className="text-[10px] font-mono text-grey-3 bg-card px-1.5 py-0.5 rounded border border-border/40 shrink-0">
#{imageId}
</span>
</div>
<p className="text-xs text-grey-3 mt-1">
تصویر با موفقیت در سرور ذخیره شد و به عنوان تصویر شاخص تور نمایش داده می‌شود.
</p>
<div className="mt-2.5 flex items-center gap-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => fileInputRef.current?.click()}
disabled={isUploadingImage}
className="h-7 text-xs border-border/60 hover:bg-surface-3 cursor-pointer"
>
<Ic name="pencil" className="size-3 ml-1" />
تغییر تصویر
</Button>
<Button
type="button"
variant="ghost"
size="sm"
onClick={handleRemoveImage}
disabled={isUploadingImage}
className="h-7 text-xs text-rose-400 hover:text-rose-300 hover:bg-rose-500/10 cursor-pointer"
>
<Ic name="trash" className="size-3 ml-1" />
حذف تصویر
</Button>
</div>
</div>
</div>
) : (
<div
onClick={() => !isUploadingImage && fileInputRef.current?.click()}
onDragOver={(e) => {
e.preventDefault()
e.stopPropagation()
}}
onDrop={(e) => {
e.preventDefault()
e.stopPropagation()
const file = e.dataTransfer.files?.[0]
if (file) processImageUpload(file)
}}
className={`relative flex flex-col items-center justify-center rounded-2xl border-2 border-dashed p-6 text-center transition-all cursor-pointer ${
imageUploadError
? 'border-rose-500/40 bg-rose-500/5'
: 'border-border-soft hover:border-primary/50 bg-surface-2/60 hover:bg-surface-2'
}`}
>
{isUploadingImage ? (
<div className="flex flex-col items-center gap-2 py-2">
<ButtonSpinner size="large" className="text-primary size-8" />
<p className="text-xs font-bold text-foreground">در حال بارگذاری و بهینه‌سازی تصویر در سرور...</p>
</div>
) : (
<>
<div className="flex size-12 items-center justify-center rounded-2xl bg-primary/10 text-primary mb-3">
<Ic name="upload" className="size-6" />
</div>
<p className="text-xs font-bold text-foreground">
برای انتخاب تصویر شاخص کلیک کنید یا فایل را اینجا رها کنید (Drag & Drop)
</p>
<p className="text-[11px] text-grey-3 mt-1">
فرمت‌های پشتیبانی‌شده: JPG، PNG، WEBP — حداکثر حجم: 15 مگابایت
</p>
{imageUploadError && (
<p className="text-xs text-rose-400 font-medium mt-2">
{imageUploadError}
</p>
)}
</>
)}
</div>
)}
</div>
<div className="sm:col-span-2">
<label className="block text-xs font-bold text-foreground mb-1.5">
زبان اصلی ثبت داده:
</label>
<select
value={primaryLang}
onChange={(e) => setPrimaryLang(e.target.value as LangCode)}
className="w-full rounded-xl border border-border/50 bg-surface-2 px-3.5 py-2.5 text-sm text-foreground focus:border-primary focus:outline-none"
>
{SUPPORTED_LANGUAGES.map((l) => (
<option key={l.code} value={l.code}>
{l.flag} {l.label} ({l.code})
</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-bold text-foreground mb-1.5">
تاریخ شروع تور <span className="text-rose-500">*</span>:
</label>
<Input
type="date"
value={startedAt}
onChange={(e) => setStartedAt(e.target.value)}
className="bg-surface-2 border-border/50 text-sm font-mono"
required
/>
</div>
<div>
<label className="block text-xs font-bold text-foreground mb-1.5">
تاریخ پایان تور <span className="text-rose-500">*</span>:
</label>
<Input
type="date"
value={endedAt}
onChange={(e) => setEndedAt(e.target.value)}
className="bg-surface-2 border-border/50 text-sm font-mono"
required
/>
</div>
<div>
<label className="block text-xs font-bold text-foreground mb-1.5">
ظرفیت کل مسافران <span className="text-rose-500">*</span>:
</label>
<Input
type="number"
min="1"
value={capacity}
onChange={(e) => setCapacity(e.target.value)}
placeholder="40"
className="bg-surface-2 border-border/50 text-sm font-mono"
required
/>
</div>
<div>
<label className="block text-xs font-bold text-foreground mb-1.5">
وضعیت فروش اولیه:
</label>
<select
value={status}
onChange={(e) => setStatus(e.target.value)}
className="w-full rounded-xl border border-border/50 bg-surface-2 px-3.5 py-2.5 text-sm text-foreground focus:border-primary focus:outline-none"
>
<option value="AVAILABLE">در دسترس (Available)</option>
<option value="SOLD_OUT">تکمیل ظرفیت (Sold Out)</option>
</select>
</div>
<div>
<label className="block text-xs font-bold text-foreground mb-1.5">
نمایش در اپ و وب:
</label>
<select
value={visibility ? 'true' : 'false'}
onChange={(e) => setVisibility(e.target.value === 'true')}
className="w-full rounded-xl border border-border/50 bg-surface-2 px-3.5 py-2.5 text-sm text-foreground focus:border-primary focus:outline-none font-bold"
>
<option value="true">فعال (قابل مشاهده در اپ و وب)</option>
<option value="false">غیرفعال (مخفی در اپ و وب)</option>
</select>
</div>
</div>
{/* Pricing Sub-card */}
<div className="rounded-2xl bg-surface-2/70 border border-border/50 p-4 space-y-3">
<h4 className="text-xs font-extrabold text-foreground flex items-center gap-1.5">
<Ic name="dollar" className="size-4 text-emerald-500" />
<span>قیمت‌گذاری و تخفیفات (دلار)</span>
</h4>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div>
<label className="block text-[11px] font-bold text-grey-2 mb-1">
بزرگسال ($) <span className="text-rose-500">*</span>:
</label>
<Input
type="number"
step="0.01"
value={price}
onChange={(e) => setPrice(e.target.value)}
placeholder="520"
className="bg-surface-3 text-xs font-mono"
required
/>
</div>
<div>
<label className="block text-[11px] font-bold text-grey-2 mb-1">
کودک (2 تا 7 سال):
</label>
<Input
type="number"
step="0.01"
value={priceChild}
onChange={(e) => setPriceChild(e.target.value)}
placeholder="280"
className="bg-surface-3 text-xs font-mono"
/>
</div>
<div>
<label className="block text-[11px] font-bold text-grey-2 mb-1">
نوزاد (زیر 2 سال):
</label>
<Input
type="number"
step="0.01"
value={priceInfant}
onChange={(e) => setPriceInfant(e.target.value)}
placeholder="80"
className="bg-surface-3 text-xs font-mono"
/>
</div>
<div>
<label className="block text-[11px] font-bold text-grey-2 mb-1">
درصد تخفیف (%):
</label>
<Input
type="number"
min="0"
max="100"
value={percentOff}
onChange={(e) => setPercentOff(e.target.value)}
placeholder="0"
className="bg-surface-3 text-xs font-mono"
/>
</div>
</div>
</div>
{/* Description */}
<div>
<label className="block text-xs font-bold text-foreground mb-1.5">
شرح و توضیحات تور:
</label>
<textarea
rows={3}
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="شرح کامل خدمات، برنامه‌های زیارتی و تسهیلات اقامتی تور..."
className="w-full rounded-xl border border-border/50 bg-surface-2 px-3.5 py-2.5 text-sm text-foreground placeholder:text-grey-4 focus:border-primary focus:outline-none"
/>
</div>
</div>
)}
{/* TAB 2: Features and Tips */}
{activeTab === 'features_tips' && (
<div className="space-y-5">
{/* Features Section */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<label className="text-xs font-bold text-foreground flex items-center gap-1.5">
<Ic name="check" className="size-4 text-primary" />
<span>ویژگی‌ها و خدمات شاخص تور ({features.length})</span>
</label>
</div>
<div className="flex items-center gap-2">
<Input
value={newFeature}
onChange={(e) => setNewFeature(e.target.value)}
placeholder="مثال: اقامت در هتل 5 ستاره نزدیک حرم..."
className="bg-surface-2 text-xs"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault()
handleAddFeature()
}
}}
/>
<Button
type="button"
size="sm"
onClick={handleAddFeature}
className="shrink-0 bg-primary/20 text-primary hover:bg-primary hover:text-white text-xs h-9 rounded-xl"
>
<Ic name="plus" className="size-4" />
<span>افزودن</span>
</Button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 mt-2">
{features.length === 0 ? (
<p className="text-xs text-grey-4 col-span-2 text-center py-4 bg-surface-2/30 rounded-xl">
هنوز ویژگی برای این تور اضافه نشده است.
</p>
) : (
features.map((feat, idx) => (
<div
key={idx}
className="flex items-center justify-between gap-2 rounded-xl bg-surface-2 border border-border/40 px-3 py-2 text-xs"
>
<span className="font-medium text-foreground truncate">{feat}</span>
<button
type="button"
onClick={() => handleRemoveFeature(idx)}
className="text-grey-4 hover:text-rose-400 p-1 shrink-0"
>
<Ic name="x" className="size-3.5" />
</button>
</div>
))
)}
</div>
</div>
{/* Travel Tips Section */}
<div className="space-y-3 pt-3 border-t border-border/40">
<label className="text-xs font-bold text-foreground flex items-center gap-1.5">
<Ic name="info" className="size-4 text-amber-500" />
<span>نکات و راهنمای سفر ({tips.length})</span>
</label>
<div className="rounded-xl bg-surface-2/60 border border-border/40 p-3 space-y-2">
<Input
value={newTipTitle}
onChange={(e) => setNewTipTitle(e.target.value)}
placeholder="عنوان نکته (مثال: مدارک لازم / ارز مسافرتی)..."
className="bg-surface-2 text-xs"
/>
<textarea
rows={2}
value={newTipDesc}
onChange={(e) => setNewTipDesc(e.target.value)}
placeholder="توضیحات تکمیلی نکته سفر..."
className="w-full rounded-xl border border-border/50 bg-surface-2 px-3 py-2 text-xs text-foreground placeholder:text-grey-4 focus:border-primary focus:outline-none"
/>
<Button
type="button"
size="sm"
onClick={handleAddTip}
className="w-full bg-primary/20 text-primary hover:bg-primary hover:text-white text-xs h-8 rounded-xl"
>
<Ic name="plus" className="size-3.5 ml-1" />
<span>افزودن نکته سفر</span>
</Button>
</div>
<div className="space-y-2 mt-2">
{tips.length === 0 ? (
<p className="text-xs text-grey-4 text-center py-4 bg-surface-2/30 rounded-xl">
نکته سفری ثبت نشده است.
</p>
) : (
tips.map((tip, idx) => (
<div
key={idx}
className="rounded-xl bg-surface-2 border border-border/40 p-3 text-xs space-y-1 relative"
>
<div className="flex items-center justify-between">
<span className="font-bold text-foreground">{tip.title}</span>
<button
type="button"
onClick={() => handleRemoveTip(idx)}
className="text-grey-4 hover:text-rose-400 p-0.5"
>
<Ic name="x" className="size-3.5" />
</button>
</div>
<p className="text-grey-3 leading-relaxed">{tip.desc}</p>
</div>
))
)}
</div>
</div>
</div>
)}
{/* TAB 3: Multi-Language Translations */}
{activeTab === 'translations' && (
<div className="space-y-4">
{/* 5-Language Selector */}
<div className="flex items-center gap-1.5 overflow-x-auto pb-1 border-b border-border/40">
{SUPPORTED_LANGUAGES.map((lang) => {
const isSelected = lang.code === selectedTransLang
const transObj = translations[lang.code]
const hasData = Boolean(
transObj?.title?.trim() ||
transObj?.description?.trim() ||
(transObj?.tour_feature && transObj.tour_feature.length > 0)
)
return (
<button
key={lang.code}
type="button"
onClick={() => setSelectedTransLang(lang.code)}
className={`flex items-center gap-2 rounded-xl px-3.5 py-2 text-xs font-bold transition-all ${
isSelected
? 'bg-primary text-white shadow-md'
: 'bg-surface-2 text-grey-2 hover:bg-surface-3 hover:text-foreground border border-border/30'
}`}
>
<span className="text-sm">{lang.flag}</span>
<span>{lang.label}</span>
{hasData && !isSelected && (
<span className="size-1.5 rounded-full bg-emerald-500" />
)}
{lang.code === primaryLang && (
<span className="text-[10px] opacity-80">(اصلی)</span>
)}
</button>
)
})}
</div>
<div dir={activeTransConfig?.dir || 'rtl'} className="space-y-4">
<div>
<label className="block text-xs font-bold text-foreground mb-1.5">
عنوان تور به زبان {activeTransConfig?.label} ({selectedTransLang}):
</label>
<Input
value={currentTransData.title || ''}
onChange={(e) => handleTransFieldChange('title', e.target.value)}
placeholder={`عنوان تور به زبان ${activeTransConfig?.label}...`}
className="bg-surface-2 border-border/50 text-sm"
/>
</div>
<div>
<label className="block text-xs font-bold text-foreground mb-1.5">
شرح و توضیحات به زبان {activeTransConfig?.label}:
</label>
<textarea
rows={3}
value={currentTransData.description || ''}
onChange={(e) => handleTransFieldChange('description', e.target.value)}
placeholder={`شرح تور به زبان ${activeTransConfig?.label}...`}
className="w-full rounded-xl border border-border/50 bg-surface-2 px-3.5 py-2 text-xs text-foreground placeholder:text-grey-4 focus:border-primary focus:outline-none"
/>
</div>
{/* Features for this language */}
<div className="space-y-2">
<label className="block text-xs font-bold text-foreground">
ویژگی‌ها به زبان {activeTransConfig?.label} ({(currentTransData.tour_feature || []).length}):
</label>
<div className="flex items-center gap-2">
<Input
value={newTransFeatText}
onChange={(e) => setNewTransFeatText(e.target.value)}
placeholder={`افزودن ویژگی به ${activeTransConfig?.label}...`}
className="bg-surface-2 text-xs"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault()
handleAddTransFeature(newTransFeatText)
setNewTransFeatText('')
}
}}
/>
<Button
type="button"
size="sm"
onClick={() => {
handleAddTransFeature(newTransFeatText)
setNewTransFeatText('')
}}
className="shrink-0 bg-primary/20 text-primary hover:bg-primary hover:text-white text-xs h-9 rounded-xl"
>
<Ic name="plus" className="size-4" />
<span>افزودن</span>
</Button>
</div>
<div className="space-y-1.5">
{(currentTransData.tour_feature || []).map((feat, fIdx) => (
<div
key={fIdx}
className="flex items-center justify-between rounded-xl bg-surface-2 border border-border/40 px-3 py-1.5 text-xs"
>
<span className="font-medium text-foreground">{feat.title}</span>
<button
type="button"
onClick={() => handleRemoveTransFeature(fIdx)}
className="text-grey-4 hover:text-rose-400 p-0.5"
>
<Ic name="x" className="size-3.5" />
</button>
</div>
))}
</div>
</div>
{/* Travel Tips for this language */}
<div className="space-y-2 pt-2 border-t border-border/40">
<label className="block text-xs font-bold text-foreground">
نکات سفر به زبان {activeTransConfig?.label} ({Object.keys(currentTransData.travel_tips || {}).length}):
</label>
<div className="rounded-xl bg-surface-2/60 border border-border/40 p-2.5 space-y-2">
<Input
value={newTransTipTitle}
onChange={(e) => setNewTransTipTitle(e.target.value)}
placeholder={`عنوان نکته به ${activeTransConfig?.label}...`}
className="bg-surface-2 text-xs"
/>
<textarea
rows={2}
value={newTransTipDesc}
onChange={(e) => setNewTransTipDesc(e.target.value)}
placeholder={`شرح نکته به ${activeTransConfig?.label}...`}
className="w-full rounded-xl border border-border/50 bg-surface-2 px-3 py-1.5 text-xs text-foreground placeholder:text-grey-4 focus:border-primary focus:outline-none"
/>
<Button
type="button"
size="sm"
onClick={() => {
handleAddTransTip(newTransTipTitle, newTransTipDesc)
setNewTransTipTitle('')
setNewTransTipDesc('')
}}
className="w-full bg-primary/20 text-primary hover:bg-primary hover:text-white text-xs h-8 rounded-xl"
>
<Ic name="plus" className="size-3.5 ml-1" />
<span>افزودن نکته به زبان {activeTransConfig?.label}</span>
</Button>
</div>
<div className="space-y-1.5">
{Object.entries(currentTransData.travel_tips || {}).map(([tTitle, tDesc], tIdx) => (
<div
key={tIdx}
className="rounded-xl bg-surface-2 border border-border/40 p-2.5 text-xs space-y-1"
>
<div className="flex items-center justify-between">
<span className="font-bold text-foreground">{tTitle}</span>
<button
type="button"
onClick={() => handleRemoveTransTip(tTitle)}
className="text-grey-4 hover:text-rose-400 p-0.5"
>
<Ic name="x" className="size-3.5" />
</button>
</div>
<p className="text-grey-3 leading-relaxed">{tDesc}</p>
</div>
))}
</div>
</div>
</div>
</div>
)}
</div>
{/* Footer */}
<DialogFooter className="px-6 py-4 bg-secondary/10 border-t border-border/50 flex items-center justify-between -mx-6 -mb-5 mt-6">
<div className="flex items-center gap-2">
<span className="text-xs text-grey-3">
{activeTab === 'basic' && 'مرحله 1 از 3: اطلاعات پایه و قیمت'}
{activeTab === 'features_tips' && 'مرحله 2 از 3: ویژگی‌ها و نکات'}
{activeTab === 'translations' && 'مرحله 3 از 3: ترجمه‌های چندزبانه'}
</span>
</div>
<div className="flex items-center gap-2">
<DialogClose asChild>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => onOpenChange(false)}
className="rounded-xl text-xs cursor-pointer"
>
انصراف
</Button>
</DialogClose>
{activeTab === 'basic' && (
<Button
type="button"
size="sm"
onClick={() => setActiveTab('features_tips')}
className="rounded-xl bg-primary text-white hover:bg-primary/90 text-xs gap-1.5"
>
<span>مرحله بعد: ویژگی‌ها</span>
<Ic name="arrow-left" className="size-3.5" />
</Button>
)}
{activeTab === 'features_tips' && (
<Button
type="button"
size="sm"
onClick={() => setActiveTab('translations')}
className="rounded-xl bg-primary text-white hover:bg-primary/90 text-xs gap-1.5"
>
<span>مرحله بعد: ترجمه‌ها</span>
<Ic name="arrow-left" className="size-3.5" />
</Button>
)}
<Button
type="submit"
size="sm"
isLoading={isSubmitting}
loadingText="در حال ثبت تور..."
className="rounded-xl bg-emerald-600 text-white hover:bg-emerald-700 text-xs font-bold gap-1.5 shadow-sm"
>
<Ic name="check" className="size-3.5" />
<span>ایجاد و ثبت نهایی تور</span>
</Button>
</div>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
)
}