Compare commits

...

2 Commits

  1. 6
      src/config/navigation.ts
  2. 75
      src/features/custom-trip/CustomTripDestinationDetailPage.tsx
  3. 84
      src/features/custom-trip/components/CustomTripDestinationsTab.tsx
  4. 7
      src/features/custom-trip/services/custom-trip-api.ts
  5. 18
      src/features/custom-trip/types.ts
  6. 7
      src/features/settings/SettingsPage.tsx
  7. 1029
      src/features/settings/components/TestimonialsSettingsTab.tsx
  8. 72
      src/features/settings/services/settings-api.ts
  9. 55
      src/features/settings/types.ts
  10. 39
      src/locales/ar.json
  11. 39
      src/locales/en.json
  12. 39
      src/locales/fa.json

6
src/config/navigation.ts

@ -159,6 +159,12 @@ export function getNavigationConfig(t: (key: string) => string): NavGroupConfig[
to: '/settings/notifications', to: '/settings/notifications',
icon: 'bell', icon: 'bell',
}, },
{
title: t('navigation.settingsTestimonials'),
titleKey: 'navigation.settingsTestimonials',
to: '/settings/testimonials',
icon: 'star',
},
], ],
}, },
], ],

75
src/features/custom-trip/CustomTripDestinationDetailPage.tsx

@ -89,6 +89,9 @@ export default function CustomTripDestinationDetailPage() {
const [destNameEn, setDestNameEn] = useState('') const [destNameEn, setDestNameEn] = useState('')
const [destCountry, setDestCountry] = useState('عراق') const [destCountry, setDestCountry] = useState('عراق')
const [destFlag, setDestFlag] = useState('🇮🇶') const [destFlag, setDestFlag] = useState('🇮🇶')
const [destImage, setDestImage] = useState('')
const [destBlogSlug, setDestBlogSlug] = useState('')
const [destJourneysCount, setDestJourneysCount] = useState<number>(10)
const [destDescription, setDestDescription] = useState('') const [destDescription, setDestDescription] = useState('')
const [destHolyPlaces, setDestHolyPlaces] = useState('') const [destHolyPlaces, setDestHolyPlaces] = useState('')
const [destIsActive, setDestIsActive] = useState(true) const [destIsActive, setDestIsActive] = useState(true)
@ -151,6 +154,9 @@ export default function CustomTripDestinationDetailPage() {
setDestNameEn(target.nameEn || '') setDestNameEn(target.nameEn || '')
setDestCountry(target.country) setDestCountry(target.country)
setDestFlag(target.flag || '📍') setDestFlag(target.flag || '📍')
setDestImage(target.image || '')
setDestBlogSlug(target.blogSlug || target.blog_slug || '')
setDestJourneysCount(target.journeysCount ?? target.journeys_count ?? 10)
setDestDescription(target.description || '') setDestDescription(target.description || '')
setDestHolyPlaces((target.holyPlaces || []).join('، ')) setDestHolyPlaces((target.holyPlaces || []).join('، '))
setDestIsActive(target.isActive) setDestIsActive(target.isActive)
@ -433,6 +439,9 @@ export default function CustomTripDestinationDetailPage() {
nameEn: primaryNameEn, nameEn: primaryNameEn,
country: destCountry.trim(), country: destCountry.trim(),
flag: destFlag, flag: destFlag,
image: destImage.trim() || undefined,
blogSlug: destBlogSlug.trim() || undefined,
journeysCount: Number(destJourneysCount) || 10,
description: primaryDesc, description: primaryDesc,
holyPlaces: primaryPlaces.length ? primaryPlaces : undefined, holyPlaces: primaryPlaces.length ? primaryPlaces : undefined,
isActive: destIsActive, isActive: destIsActive,
@ -884,8 +893,21 @@ export default function CustomTripDestinationDetailPage() {
<div className="rounded-2xl bg-card border border-border/70 p-5 sm:p-6 shadow-xs space-y-6"> <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 flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b border-border/60 pb-5">
<div className="flex items-start gap-3">
<span className="text-3xl select-none">{destFlag || destination.flag || '📍'}</span>
<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>
<div className="flex items-center gap-2.5 flex-wrap"> <div className="flex items-center gap-2.5 flex-wrap">
<h2 className="text-lg sm:text-xl font-black text-foreground"> <h2 className="text-lg sm:text-xl font-black text-foreground">
@ -1085,6 +1107,55 @@ export default function CustomTripDestinationDetailPage() {
{/* فرم: بدنه اسکرول‌شونده و فوتر فیکس‌شده */} {/* فرم: بدنه اسکرول‌شونده و فوتر فیکس‌شده */}
<form onSubmit={handleSaveDestinationInfo} className="flex flex-col flex-1 overflow-hidden min-h-0"> <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="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="grid grid-cols-1 sm:grid-cols-2 gap-3 p-3.5 rounded-xl bg-card border border-border/70 shadow-2xs">
{/* انتخاب کشور مقصد */} {/* انتخاب کشور مقصد */}

84
src/features/custom-trip/components/CustomTripDestinationsTab.tsx

@ -324,6 +324,9 @@ export function CustomTripDestinationsTab({
const [formFlag, setFormFlag] = useState('🇮🇶') const [formFlag, setFormFlag] = useState('🇮🇶')
const [selectedCityPresetId, setSelectedCityPresetId] = useState<string>('') const [selectedCityPresetId, setSelectedCityPresetId] = useState<string>('')
const [formIsActive, setFormIsActive] = useState(true) const [formIsActive, setFormIsActive] = useState(true)
const [formImage, setFormImage] = useState<string>('')
const [formBlogSlug, setFormBlogSlug] = useState<string>('')
const [formJourneysCount, setFormJourneysCount] = useState<number>(10)
const currentLang = const currentLang =
SERVICE_LANGUAGES.find((l) => l.code === activeLangTab) || SERVICE_LANGUAGES[0] SERVICE_LANGUAGES.find((l) => l.code === activeLangTab) || SERVICE_LANGUAGES[0]
@ -360,6 +363,9 @@ export function CustomTripDestinationsTab({
setFormFlag('🇮🇶') setFormFlag('🇮🇶')
setSelectedCityPresetId('') setSelectedCityPresetId('')
setFormIsActive(true) setFormIsActive(true)
setFormImage('')
setFormBlogSlug('')
setFormJourneysCount(10)
setValidationError(null) setValidationError(null)
setIsFormOpen(true) setIsFormOpen(true)
} }
@ -392,6 +398,9 @@ export function CustomTripDestinationsTab({
setDescriptionsMap(initialDesc) setDescriptionsMap(initialDesc)
setFormCountry(item.country) setFormCountry(item.country)
setFormFlag(item.flag || '📍') setFormFlag(item.flag || '📍')
setFormImage(item.image || '')
setFormBlogSlug(item.blogSlug || item.blog_slug || '')
setFormJourneysCount(item.journeysCount ?? item.journeys_count ?? 10)
// Find if item corresponds to an official preset city // Find if item corresponds to an official preset city
const matched = OFFICIAL_PRESET_CITIES.find( const matched = OFFICIAL_PRESET_CITIES.find(
@ -473,6 +482,9 @@ export function CustomTripDestinationsTab({
nameEn: names.en.trim() || undefined, nameEn: names.en.trim() || undefined,
country: formCountry.trim(), country: formCountry.trim(),
flag: formFlag, flag: formFlag,
image: formImage.trim() || undefined,
blogSlug: formBlogSlug.trim() || undefined,
journeysCount: Number(formJourneysCount) || 10,
description: descriptionsMap.fa.trim() || descriptionsMap.ar.trim() || undefined, description: descriptionsMap.fa.trim() || descriptionsMap.ar.trim() || undefined,
holyPlaces: holyPlacesArray.length ? holyPlacesArray : undefined, holyPlaces: holyPlacesArray.length ? holyPlacesArray : undefined,
isActive: formIsActive, isActive: formIsActive,
@ -486,6 +498,9 @@ export function CustomTripDestinationsTab({
nameEn: names.en.trim() || undefined, nameEn: names.en.trim() || undefined,
country: formCountry.trim() || 'عراق', country: formCountry.trim() || 'عراق',
flag: formFlag, flag: formFlag,
image: formImage.trim() || undefined,
blogSlug: formBlogSlug.trim() || undefined,
journeysCount: Number(formJourneysCount) || 10,
description: descriptionsMap.fa.trim() || descriptionsMap.ar.trim() || undefined, description: descriptionsMap.fa.trim() || descriptionsMap.ar.trim() || undefined,
holyPlaces: holyPlacesArray.length ? holyPlacesArray : undefined, holyPlaces: holyPlacesArray.length ? holyPlacesArray : undefined,
hotelsCount: 0, hotelsCount: 0,
@ -643,8 +658,21 @@ export function CustomTripDestinationsTab({
> >
<td className="px-4 py-3.5 font-mono text-muted-foreground">{toFa(idx + 1)}</td> <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"> <td className="px-4 py-3.5 font-bold text-foreground">
<div className="flex items-center gap-2.5">
<span className="text-xl select-none">{item.flag || '📍'}</span>
<div className="flex items-center gap-3">
{item.image ? (
<div className="size-11 rounded-xl overflow-hidden shrink-0 border border-border/70 bg-muted/40 shadow-2xs">
<img
src={item.image}
alt={item.name}
className="size-full object-cover group-hover:scale-105 transition-transform duration-300"
onError={(e) => {
(e.currentTarget as HTMLElement).style.display = 'none'
}}
/>
</div>
) : (
<span className="text-xl select-none">{item.flag || '📍'}</span>
)}
<div> <div>
<div className="group-hover:text-primary transition-colors flex items-center gap-1.5 font-bold text-sm"> <div className="group-hover:text-primary transition-colors flex items-center gap-1.5 font-bold text-sm">
<span>{item.name}</span> <span>{item.name}</span>
@ -801,6 +829,58 @@ export function CustomTripDestinationsTab({
/> />
</div> </div>
{/* فیلدهای تنظیمات رسانه‌ای، تصویر و پیوند به راهنمای بلاگ */}
<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>
{formImage && (
<span className="text-[10px] text-emerald-600 font-medium">دارای تصویر</span>
)}
</label>
<Input
value={formImage}
onChange={(e) => setFormImage(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={formJourneysCount}
onChange={(e) => setFormJourneysCount(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={formBlogSlug}
onChange={(e) => setFormBlogSlug(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="grid grid-cols-1 sm:grid-cols-2 gap-3 p-3.5 rounded-xl bg-card border border-border/70 shadow-2xs">
{/* فیلد انتخابی کشور مقصد */} {/* فیلد انتخابی کشور مقصد */}

7
src/features/custom-trip/services/custom-trip-api.ts

@ -451,6 +451,13 @@ export async function saveCustomTripDestination(
name_en: item.nameEn, name_en: item.nameEn,
country: item.country, country: item.country,
flag: item.flag, flag: item.flag,
image: item.image,
blog_slug: item.blogSlug || item.blog_slug,
blogSlug: item.blogSlug || item.blog_slug,
journeys_count: item.journeysCount ?? item.journeys_count,
journeysCount: item.journeysCount ?? item.journeys_count,
short_description: item.shortDescription || item.short_description,
shortDescription: item.shortDescription || item.short_description,
description: item.description, description: item.description,
holyPlaces: item.holyPlaces, holyPlaces: item.holyPlaces,
holy_places: item.holyPlaces, holy_places: item.holyPlaces,

18
src/features/custom-trip/types.ts

@ -87,12 +87,28 @@ export interface CustomTripDestinationItem {
nameEn?: string nameEn?: string
country: string country: string
flag?: string flag?: string
image?: string
blogSlug?: string
blog_slug?: string
journeysCount?: number
journeys_count?: number
shortDescription?: string
short_description?: string
description?: string description?: string
holyPlaces?: string[] holyPlaces?: string[]
hotelsCount: number hotelsCount: number
transportsCount?: number transportsCount?: number
isActive: boolean isActive: boolean
translations?: Record<string, { name?: string; description?: string; holyPlaces?: string[] }>
translations?: Record<
string,
{
name?: string
description?: string
short_description?: string
shortDescription?: string
holyPlaces?: string[]
}
>
} }
export interface CustomTripTransportRelationItem { export interface CustomTripTransportRelationItem {

7
src/features/settings/SettingsPage.tsx

@ -6,6 +6,7 @@ import { Ic } from '@/icons'
import { AdminManagementTab } from './components/AdminManagementTab' import { AdminManagementTab } from './components/AdminManagementTab'
import { NotificationsTab } from './components/NotificationsTab' import { NotificationsTab } from './components/NotificationsTab'
import { SocialMediaSettingsTab } from './components/SocialMediaSettingsTab' import { SocialMediaSettingsTab } from './components/SocialMediaSettingsTab'
import { TestimonialsSettingsTab } from './components/TestimonialsSettingsTab'
export default function SettingsPage() { export default function SettingsPage() {
const { t } = useTranslation() const { t } = useTranslation()
@ -13,12 +14,12 @@ export default function SettingsPage() {
const navigate = useNavigate() const navigate = useNavigate()
React.useEffect(() => { React.useEffect(() => {
if (!section || !['admins', 'social', 'notifications'].includes(section)) {
if (!section || !['admins', 'social', 'notifications', 'testimonials'].includes(section)) {
navigate('/settings/admins', { replace: true }) navigate('/settings/admins', { replace: true })
} }
}, [section, navigate]) }, [section, navigate])
const activeSection = section && ['admins', 'social', 'notifications'].includes(section)
const activeSection = section && ['admins', 'social', 'notifications', 'testimonials'].includes(section)
? section ? section
: 'admins' : 'admins'
@ -54,6 +55,8 @@ export default function SettingsPage() {
<AdminManagementTab /> <AdminManagementTab />
) : activeSection === 'social' ? ( ) : activeSection === 'social' ? (
<SocialMediaSettingsTab /> <SocialMediaSettingsTab />
) : activeSection === 'testimonials' ? (
<TestimonialsSettingsTab />
) : ( ) : (
<NotificationsTab /> <NotificationsTab />
)} )}

1029
src/features/settings/components/TestimonialsSettingsTab.tsx
File diff suppressed because it is too large
View File

72
src/features/settings/services/settings-api.ts

@ -131,3 +131,75 @@ export async function saveSocialMediaSettings(
}) })
} }
/**
* دریافت تنظیمات سربرگ بخش نظرات زائران
*/
export async function fetchTestimonialSectionSettings(): Promise<import('../types').TestimonialSectionConfigData> {
return apiFetch<import('../types').TestimonialSectionConfigData>('home/settings/testimonials/section/')
}
/**
* ذخیره تنظیمات سربرگ بخش نظرات زائران
*/
export async function saveTestimonialSectionSettings(
payload: Partial<import('../types').TestimonialSectionConfigData>
): Promise<import('../types').TestimonialSectionConfigData> {
return apiFetch<import('../types').TestimonialSectionConfigData>('home/settings/testimonials/section/', {
method: 'POST',
body: payload,
})
}
/**
* دریافت فهرست مدیریت نظرات زائران
*/
export async function fetchAdminTestimonials(params?: {
search?: string
is_active?: string
rating?: string
}): Promise<import('../types').AdminTestimonialItem[]> {
const query = new URLSearchParams()
if (params?.search && params.search.trim()) query.set('search', params.search.trim())
if (params?.is_active && params.is_active !== 'all') query.set('is_active', params.is_active)
if (params?.rating && params.rating !== 'all') query.set('rating', params.rating)
const qs = query.toString()
return apiFetch<import('../types').AdminTestimonialItem[]>(
qs ? `home/settings/testimonials/?${qs}` : 'home/settings/testimonials/'
)
}
/**
* ایجاد یک نظر جدید زائر
*/
export async function createAdminTestimonial(
body: FormData | import('../types').CreateOrUpdateTestimonialPayload
): Promise<import('../types').AdminTestimonialItem> {
return apiFetch<import('../types').AdminTestimonialItem>('home/settings/testimonials/', {
method: 'POST',
body,
})
}
/**
* ویرایش نظر زائر
*/
export async function updateAdminTestimonial(
id: number,
body: FormData | Partial<import('../types').CreateOrUpdateTestimonialPayload>
): Promise<import('../types').AdminTestimonialItem> {
return apiFetch<import('../types').AdminTestimonialItem>(`home/settings/testimonials/${id}/`, {
method: 'PATCH',
body,
})
}
/**
* حذف نظر زائر
*/
export async function deleteAdminTestimonial(id: number): Promise<{ success: boolean; detail?: string }> {
return apiFetch<{ success: boolean; detail?: string }>(`home/settings/testimonials/${id}/`, {
method: 'DELETE',
})
}

55
src/features/settings/types.ts

@ -68,3 +68,58 @@ export interface SaveSocialMediaPayload {
contactus?: SocialMediaContactUs contactus?: SocialMediaContactUs
} }
export interface TestimonialTranslationItem {
author_name?: string
location?: string
tour_title?: string
content?: string
}
export type TestimonialTranslations = Record<string, TestimonialTranslationItem>
export interface SectionTranslations {
[lang: string]: {
title?: string
subtitle?: string
}
}
export interface TestimonialSectionConfigData {
id?: number
key: string
is_active: boolean
title?: string
subtitle?: string
translations: SectionTranslations
updated_at?: string
}
export interface AdminTestimonialItem {
id: number
slug: string
avatar?: string | null
avatar_url?: string
rating: number
order: number
is_active: boolean
author_name: string
location: string
tour_title: string
meta: string
content: string
translations: TestimonialTranslations
created_at: string
updated_at: string
}
export interface CreateOrUpdateTestimonialPayload {
slug?: string
avatar_url?: string
rating?: number
order?: number
is_active?: boolean
remove_avatar?: boolean
translations: TestimonialTranslations
}

39
src/locales/ar.json

@ -43,7 +43,8 @@
"logout": "تسجيل الخروج", "logout": "تسجيل الخروج",
"soon": "قريباً", "soon": "قريباً",
"comingSoon": "قريباً", "comingSoon": "قريباً",
"adminRole": "مدير النظام العام"
"adminRole": "مدير النظام العام",
"settingsTestimonials": "تجارب وآراء الزوار"
}, },
"topbar": { "topbar": {
"serverOnline": "الخادم متصل", "serverOnline": "الخادم متصل",
@ -556,7 +557,41 @@
"notificationsUpcomingFeaturesTitle": "الميزات قيد التنفيذ لهذا القسم:", "notificationsUpcomingFeaturesTitle": "الميزات قيد التنفيذ لهذا القسم:",
"notificationsFeature1": "إرسال إشعارات مخصصة لمستخدمي التطبيق والويب مصنفة حسب اللغة", "notificationsFeature1": "إرسال إشعارات مخصصة لمستخدمي التطبيق والويب مصنفة حسب اللغة",
"notificationsFeature2": "جدولة تلقائية للتذكير بالرحلات والجولات والمدفوعات المعلقة", "notificationsFeature2": "جدولة تلقائية للتذكير بالرحلات والجولات والمدفوعات المعلقة",
"notificationsFeature3": "تقارير تحليلية لمعدلات المشاهدة والنقر والتسليم الناجح"
"notificationsFeature3": "تقارير تحليلية لمعدلات المشاهدة والنقر والتسليم الناجح",
"tabTestimonials": "تجارب وآراء الزوار",
"badgeTestimonials": "قسم الآراء والملاحظات",
"testimonialsDescription": "إدارة آراء وتجارب الزوار المعنوية في الصفحة الرئيسية وصفحة من نحن",
"sectionHeaderCard": "إعدادات ترويسة قسم الآراء",
"sectionHeaderCardDesc": "ضبط العنوان والعنوان الفرعي لهذا القسم بمختلف اللغات وحالة العرض في الموقع",
"testimonialsListTitle": "قائمة آراء الزوار",
"addTestimonial": "إضافة رأي جديد",
"editTestimonial": "تعديل رأي الزائر",
"deleteTestimonial": "حذف الرأي",
"deleteTestimonialConfirm": "هل أنت متأكد من حذف رأي هذا الزائر؟ لا يمكن التراجع عن هذا الإجراء.",
"authorName": "اسم الزائر",
"location": "بلد وإقامة الزائر",
"tourTitle": "عنوان الرحلة / الجولة",
"rating": "التقييم (النجوم)",
"displayOrder": "ترتيب العرض",
"avatarImage": "الصورة الشخصية (الأفاتار)",
"uploadAvatar": "تحميل صورة جديدة",
"avatarUrlOptional": "رابط الصورة (اختياري)",
"testimonialContent": "نص وتجربة الزائر",
"sectionActive": "تفعيل عرض القسم بالموقع",
"testimonialActive": "الحالة نشطة",
"totalTestimonials": "إجمالي الآراء المسجلة",
"activeTestimonials": "الآراء النشطة",
"averageRating": "متوسط التقييمات",
"saveSettingsSuccess": "تم حفظ الإعدادات بنجاح.",
"testimonialSavedSuccess": "تم حفظ رأي الزائر بنجاح.",
"testimonialDeletedSuccess": "تم حذف الرأي بنجاح.",
"noTestimonialsFound": "لم يتم العثور على أي آراء.",
"searchTestimonialPlaceholder": "البحث في اسم الزائر، المدينة، عنوان الرحلة أو النص...",
"filterAllRatings": "جميع التقييمات",
"starsCount": "نجوم",
"slugLabel": "المعرف الفريد (الرابط المختصر)",
"sectionTitleLabel": "عنوان القسم",
"sectionSubtitleLabel": "العنوان الفرعي ووصف القسم"
}, },
"customTrip": { "customTrip": {
"title": "إدارة الرحلات المخصصة", "title": "إدارة الرحلات المخصصة",

39
src/locales/en.json

@ -43,7 +43,8 @@
"logout": "Logout", "logout": "Logout",
"soon": "Soon", "soon": "Soon",
"comingSoon": "Coming Soon", "comingSoon": "Coming Soon",
"adminRole": "System Administrator"
"adminRole": "System Administrator",
"settingsTestimonials": "Pilgrim Testimonials"
}, },
"topbar": { "topbar": {
"serverOnline": "Server is Online", "serverOnline": "Server is Online",
@ -556,7 +557,41 @@
"notificationsUpcomingFeaturesTitle": "Features under development for this section:", "notificationsUpcomingFeaturesTitle": "Features under development for this section:",
"notificationsFeature1": "Send targeted notifications to app and web users segmented by language", "notificationsFeature1": "Send targeted notifications to app and web users segmented by language",
"notificationsFeature2": "Automated scheduling for trip reminders, tours, and pending payments", "notificationsFeature2": "Automated scheduling for trip reminders, tours, and pending payments",
"notificationsFeature3": "Analytics reports on impressions, open rates, and successful delivery"
"notificationsFeature3": "Analytics reports on impressions, open rates, and successful delivery",
"tabTestimonials": "Pilgrim Testimonials",
"badgeTestimonials": "Testimonials & Reviews",
"testimonialsDescription": "Manage pilgrim reviews, feedback, and spiritual reflections on home and about pages",
"sectionHeaderCard": "Section Header Settings",
"sectionHeaderCardDesc": "Configure multi-language title, subtitle, and visibility on the website",
"testimonialsListTitle": "Pilgrim Testimonials List",
"addTestimonial": "Add New Testimonial",
"editTestimonial": "Edit Pilgrim Testimonial",
"deleteTestimonial": "Delete Testimonial",
"deleteTestimonialConfirm": "Are you sure you want to delete this pilgrim testimonial? This action cannot be undone.",
"authorName": "Pilgrim Name",
"location": "Country / City",
"tourTitle": "Tour / Pilgrimage Title",
"rating": "Rating (Stars)",
"displayOrder": "Display Order",
"avatarImage": "Avatar Photo",
"uploadAvatar": "Upload New Photo",
"avatarUrlOptional": "Image URL (Optional)",
"testimonialContent": "Testimonial Content",
"sectionActive": "Show Section on Site",
"testimonialActive": "Active Status",
"totalTestimonials": "Total Testimonials",
"activeTestimonials": "Active Testimonials",
"averageRating": "Average Rating",
"saveSettingsSuccess": "Settings saved successfully.",
"testimonialSavedSuccess": "Pilgrim testimonial saved successfully.",
"testimonialDeletedSuccess": "Testimonial deleted successfully.",
"noTestimonialsFound": "No testimonials found.",
"searchTestimonialPlaceholder": "Search by pilgrim name, city, tour, or text...",
"filterAllRatings": "All Ratings",
"starsCount": "Stars",
"slugLabel": "Unique Slug",
"sectionTitleLabel": "Section Title",
"sectionSubtitleLabel": "Section Subtitle & Description"
}, },
"customTrip": { "customTrip": {
"title": "Custom Trip Management", "title": "Custom Trip Management",

39
src/locales/fa.json

@ -43,7 +43,8 @@
"logout": "خروج از سیستم", "logout": "خروج از سیستم",
"soon": "به‌زودی", "soon": "به‌زودی",
"comingSoon": "به‌زودی", "comingSoon": "به‌زودی",
"adminRole": "مدیر کل سیستم"
"adminRole": "مدیر کل سیستم",
"settingsTestimonials": "تجارب و نظرات زائران"
}, },
"topbar": { "topbar": {
"serverOnline": "سرور متصل است", "serverOnline": "سرور متصل است",
@ -556,7 +557,41 @@
"notificationsUpcomingFeaturesTitle": "قابلیت‌های در دست پیاده‌سازی این بخش:", "notificationsUpcomingFeaturesTitle": "قابلیت‌های در دست پیاده‌سازی این بخش:",
"notificationsFeature1": "ارسال اعلان اختصاصی به کاربران اپلیکیشن و وب با تفکیک زبان", "notificationsFeature1": "ارسال اعلان اختصاصی به کاربران اپلیکیشن و وب با تفکیک زبان",
"notificationsFeature2": "زمان‌بندی خودکار یادآورهای سفر، تور و پرداخت‌های مالی", "notificationsFeature2": "زمان‌بندی خودکار یادآورهای سفر، تور و پرداخت‌های مالی",
"notificationsFeature3": "گزارش‌های تحلیلی نرخ مشاهده، کلیک و تحویل موفق پیام‌ها"
"notificationsFeature3": "گزارش‌های تحلیلی نرخ مشاهده، کلیک و تحویل موفق پیام‌ها",
"tabTestimonials": "تجارب و دیدگاه‌های زائران",
"badgeTestimonials": "بخش نظرات و بازخوردها",
"testimonialsDescription": "مدیریت نظرات، بازخوردها و تجربیات معنوی زائران در صفحه اصلی و درباره ما",
"sectionHeaderCard": "تنظیمات سربرگ بخش نظرات",
"sectionHeaderCardDesc": "تنظیم عنوان و زیرعنوان این بخش در زبان‌های مختلف و وضعیت نمایش در سایت",
"testimonialsListTitle": "فهرست نظرات زائران",
"addTestimonial": "افزودن نظر جدید",
"editTestimonial": "ویرایش نظر زائر",
"deleteTestimonial": "حذف نظر",
"deleteTestimonialConfirm": "آیا از حذف این نظر زائر اطمینان دارید؟ این عملیات قابل بازگشت نیست.",
"authorName": "نام زائر",
"location": "محل سکونت / کشور و شهر",
"tourTitle": "عنوان تور / سفر زیارتی",
"rating": "امتیاز (ستاره)",
"displayOrder": "ترتیب نمایش",
"avatarImage": "تصویر چهره (آواتار)",
"uploadAvatar": "بارگذاری تصویر جدید",
"avatarUrlOptional": "آدرس اینترنتی تصویر (اختیاری)",
"testimonialContent": "متن بازخورد و تجربه زائر",
"sectionActive": "نمایش بخش در سایت",
"testimonialActive": "وضعیت فعال",
"totalTestimonials": "کل نظرات ثبت‌شده",
"activeTestimonials": "نظرات فعال",
"averageRating": "میانگین امتیازات",
"saveSettingsSuccess": "تنظیمات با موفقیت ذخیره شد.",
"testimonialSavedSuccess": "نظر زائر با موفقیت ذخیره شد.",
"testimonialDeletedSuccess": "نظر با موفقیت حذف شد.",
"noTestimonialsFound": "هیچ نظری یافت نشد.",
"searchTestimonialPlaceholder": "جستجو در نام زائر، شهر، عنوان تور یا متن...",
"filterAllRatings": "همه امتیازها",
"starsCount": "ستاره",
"slugLabel": "شناسه یکتا (اسلاگ)",
"sectionTitleLabel": "عنوان بخش",
"sectionSubtitleLabel": "زیرعنوان و توضیحات بخش"
}, },
"customTrip": { "customTrip": {
"title": "مدیریت سفرهای اختصاصی", "title": "مدیریت سفرهای اختصاصی",

Loading…
Cancel
Save