diff --git a/client/src/components/LanguageSwitcher.tsx b/client/src/components/LanguageSwitcher.tsx index ba6155c..87d5924 100644 --- a/client/src/components/LanguageSwitcher.tsx +++ b/client/src/components/LanguageSwitcher.tsx @@ -7,22 +7,37 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Languages } from 'lucide-react'; +import { useEffect } from 'react'; export function LanguageSwitcher() { const { i18n } = useTranslation(); const languages = [ { code: 'en', name: 'English', flag: '🇺🇸' }, - { code: 'ar', name: 'العربية', flag: '🇸🇦' } + { code: 'ar', name: 'العربية', flag: '🇴🇲' } ]; - const changeLanguage = (langCode: string) => { - i18n.changeLanguage(langCode); - // Update document direction for Arabic + const changeLanguage = async (langCode: string) => { + await i18n.changeLanguage(langCode); + // Update document direction and language for proper RTL support document.documentElement.dir = langCode === 'ar' ? 'rtl' : 'ltr'; document.documentElement.lang = langCode; + // Store in localStorage for persistence + localStorage.setItem('language', langCode); + // Force re-render of components + window.location.reload(); }; + // Initialize language on component mount + useEffect(() => { + const savedLanguage = localStorage.getItem('language'); + if (savedLanguage && savedLanguage !== i18n.language) { + i18n.changeLanguage(savedLanguage); + document.documentElement.dir = savedLanguage === 'ar' ? 'rtl' : 'ltr'; + document.documentElement.lang = savedLanguage; + } + }, [i18n]); + const currentLanguage = languages.find(lang => lang.code === i18n.language) || languages[0]; return ( @@ -30,7 +45,7 @@ export function LanguageSwitcher() { diff --git a/client/src/components/layout/Sidebar.tsx b/client/src/components/layout/Sidebar.tsx index 521bf19..dc8db0b 100644 --- a/client/src/components/layout/Sidebar.tsx +++ b/client/src/components/layout/Sidebar.tsx @@ -1,5 +1,6 @@ import { Link, useLocation } from "wouter"; import { useAuth } from "@/hooks/useAuth"; +import { useTranslation } from "react-i18next"; import { cn } from "@/lib/utils"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -16,57 +17,63 @@ import { BarChart3, Shield, Zap, - Monitor + Monitor, + UserCheck } from "lucide-react"; interface SidebarProps { className?: string; } -const navigation = [ +const getNavigation = (t: any) => [ { - name: "Dashboard", + name: t('navigation.dashboard'), href: "/dashboard", icon: LayoutDashboard, }, { - name: "Tasks", + name: t('navigation.tasks'), href: "/tasks", icon: Calendar, badge: "3", // This would come from task count }, { - name: "Finances", + name: t('navigation.finances'), href: "/finances", icon: DollarSign, }, { - name: "Voice Commands", + name: t('navigation.professionals'), + href: "/professionals", + icon: UserCheck, + }, + { + name: t('navigation.voice'), href: "/voice", icon: Mic, }, { - name: "AI Assistant", + name: t('navigation.ai'), href: "/ai", icon: Brain, }, ]; -const managementNavigation = [ +const getManagementNavigation = (t: any) => [ { - name: "Analytics", + name: t('navigation.analytics'), href: "/analytics", icon: BarChart3, roles: ["pro", "admin"], }, { - name: "System Monitor", + name: t('navigation.monitoring'), href: "/monitoring", icon: Monitor, roles: ["pro", "admin"], }, { - name: "Settings", + name: t('navigation.settings'), href: "/settings", icon: Settings, }, diff --git a/client/src/i18n/locales/ar.json b/client/src/i18n/locales/ar.json index a2731be..f05d7a2 100644 --- a/client/src/i18n/locales/ar.json +++ b/client/src/i18n/locales/ar.json @@ -28,6 +28,8 @@ "ai": "المساعد الذكي", "voice": "الصوت", "analytics": "التحليلات", + "monitoring": "مراقبة النظام", + "professionals": "الدليل المهني", "settings": "الإعدادات", "logout": "تسجيل الخروج", "profile": "الملف الشخصي" @@ -216,5 +218,129 @@ "deleted": "تم الحذف بنجاح", "updated": "تم التحديث بنجاح", "created": "تم الإنشاء بنجاح" + }, + "monitoring": { + "title": "مراقبة النظام", + "realTimeMetrics": "المقاييس في الوقت الفعلي", + "systemHealth": "صحة النظام", + "performance": "الأداء", + "analytics": "التحليلات", + "systemResources": "موارد النظام", + "systemStatus": "حالة النظام", + "healthy": "سليم", + "degraded": "متدهور", + "unhealthy": "غير سليم", + "uptime": "مدة التشغيل", + "responseTime": "وقت الاستجابة", + "memoryUsage": "استخدام الذاكرة", + "serviceHealth": "صحة الخدمات", + "database": "قاعدة البيانات", + "cache": "التخزين المؤقت", + "ai": "الذكاء الاصطناعي", + "voice": "الصوت", + "performanceOverview": "نظرة عامة على الأداء", + "errorRate": "معدل الأخطاء", + "throughput": "الإنتاجية", + "usageTrends": "اتجاهات الاستخدام", + "totalUsers": "إجمالي المستخدمين", + "voiceCommands": "الأوامر الصوتية", + "aiInteractions": "تفاعلات الذكاء الاصطناعي", + "tasksCreated": "المهام المنشأة", + "cacheStatistics": "إحصائيات التخزين المؤقت", + "cacheType": "نوع التخزين المؤقت", + "redisConnected": "اتصال Redis", + "memoryCacheSize": "حجم التخزين المؤقت", + "systemMetrics": "مقاييس النظام", + "userCount": "عدد المستخدمين", + "activeUsers": "المستخدمون النشطون", + "totalTasks": "إجمالي المهام", + "financialRecords": "السجلات المالية", + "autoRefresh": "تحديث تلقائي", + "heapUsed": "الذاكرة المستخدمة", + "heapTotal": "إجمالي الذاكرة", + "external": "خارجي", + "rss": "RSS", + "24Hours": "24 ساعة", + "7Days": "7 أيام", + "30Days": "30 يوم" + }, + "professionals": { + "title": "الدليل المهني العماني", + "subtitle": "اكتشف المهنيين المعتمدين في سلطنة عمان", + "searchProfessionals": "البحث عن المهنيين", + "filterByType": "تصفية حسب النوع", + "filterByLocation": "تصفية حسب الموقع", + "bookAppointment": "حجز موعد", + "viewProfile": "عرض الملف الشخصي", + "contactInfo": "معلومات الاتصال", + "workingHours": "ساعات العمل", + "services": "الخدمات", + "experience": "الخبرة", + "education": "التعليم", + "certifications": "الشهادات", + "rating": "التقييم", + "reviews": "المراجعات", + "languages": "اللغات", + "specialization": "التخصص", + "availableSlots": "المواعيد المتاحة", + "bookingConfirmed": "تم تأكيد الحجز", + "types": { + "doctor": "طبيب", + "lawyer": "محامي", + "engineer": "مهندس", + "accountant": "محاسب", + "consultant": "استشاري", + "teacher": "معلم", + "dentist": "طبيب أسنان", + "veterinarian": "طبيب بيطري", + "architect": "معماري", + "therapist": "معالج", + "nutritionist": "أخصائي تغذية", + "trainer": "مدرب" + }, + "locations": { + "muscat": "مسقط", + "salalah": "صلالة", + "nizwa": "نزوى", + "sur": "صور", + "sohar": "صحار", + "rustaq": "الرستاق", + "ibri": "عبري", + "khasab": "خصب", + "bahla": "بهلاء", + "buraimi": "البريمي", + "adam": "آدم", + "bidiyah": "بدية" + }, + "days": { + "sunday": "الأحد", + "monday": "الاثنين", + "tuesday": "الثلاثاء", + "wednesday": "الأربعاء", + "thursday": "الخميس", + "friday": "الجمعة", + "saturday": "السبت" + }, + "appointmentStatus": { + "pending": "معلق", + "confirmed": "مؤكد", + "completed": "مكتمل", + "cancelled": "ملغي" + }, + "connectionStatus": { + "pending": "معلق", + "accepted": "مقبول", + "declined": "مرفوض" + }, + "noResults": "لا توجد نتائج", + "noProfessionals": "لا يوجد مهنيون مطابقون للبحث", + "loadingProfessionals": "جاري تحميل المهنيين...", + "selectTimeSlot": "اختر الوقت المناسب", + "appointmentNotes": "ملاحظات الموعد", + "emergencyContact": "جهة اتصال الطوارئ", + "consultationFee": "رسوم الاستشارة", + "acceptedInsurance": "التأمين المقبول", + "onlineConsultation": "استشارة عبر الإنترنت", + "inPersonConsultation": "استشارة شخصية" } } \ No newline at end of file diff --git a/client/src/pages/ProfessionalDirectory.tsx b/client/src/pages/ProfessionalDirectory.tsx index 3de8ed1..b586558 100644 --- a/client/src/pages/ProfessionalDirectory.tsx +++ b/client/src/pages/ProfessionalDirectory.tsx @@ -1,262 +1,409 @@ -import { useState } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Star, MapPin, Clock, Phone, Mail, Calendar } from "lucide-react"; -import { Link } from "wouter"; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { + Search, + MapPin, + Star, + Phone, + Mail, + Clock, + Calendar, + User, + GraduationCap, + Award, + Languages as LanguagesIcon, + MessageCircle +} from 'lucide-react'; interface Professional { id: number; - firstName: string; - lastName: string; - businessName: string; - professionalType: string; - bio: string; - specializations: string[]; - address: string; - phoneNumber: string; + name: string; + type: string; + location: string; + rating: number; + reviews: number; + experience: number; + languages: string[]; + specialization: string; + phone: string; email: string; - profileImageUrl: string; - averageRating: number; - totalReviews: number; - allowAppointmentBooking: boolean; + workingHours: string; + education: string; + certifications: string[]; + consultationFee: number; + avatar?: string; + services: string[]; + onlineConsultation: boolean; + inPersonConsultation: boolean; } +const mockProfessionals: Professional[] = [ + { + id: 1, + name: "د. أحمد المحرزي", + type: "doctor", + location: "muscat", + rating: 4.8, + reviews: 127, + experience: 15, + languages: ["Arabic", "English"], + specialization: "طب القلب", + phone: "+968 9123 4567", + email: "ahmed.almahrezi@example.om", + workingHours: "الأحد - الخميس: 8:00 ص - 6:00 م", + education: "جامعة السلطان قابوس - كلية الطب", + certifications: ["البورد العماني في طب القلب", "زمالة الكلية الأمريكية لأطباء القلب"], + consultationFee: 25, + services: ["فحص القلب", "تخطيط القلب", "قسطرة القلب"], + onlineConsultation: true, + inPersonConsultation: true + }, + { + id: 2, + name: "أ. فاطمة الزدجالية", + type: "lawyer", + location: "muscat", + rating: 4.9, + reviews: 89, + experience: 12, + languages: ["Arabic", "English"], + specialization: "القانون التجاري", + phone: "+968 9234 5678", + email: "fatima.alzadjali@example.om", + workingHours: "الأحد - الخميس: 9:00 ص - 5:00 م", + education: "جامعة السلطان قابوس - كلية الحقوق", + certifications: ["عضو نقابة المحامين العمانيين", "دبلوم التحكيم التجاري"], + consultationFee: 30, + services: ["استشارات قانونية", "صياغة العقود", "التقاضي"], + onlineConsultation: true, + inPersonConsultation: true + }, + { + id: 3, + name: "م. سالم البلوشي", + type: "engineer", + location: "sohar", + rating: 4.7, + reviews: 64, + experience: 10, + languages: ["Arabic", "English"], + specialization: "الهندسة المدنية", + phone: "+968 9345 6789", + email: "salem.albalushi@example.om", + workingHours: "الأحد - الخميس: 7:00 ص - 4:00 م", + education: "الجامعة الألمانية للتكنولوجيا في عمان", + certifications: ["مهندس مدني معتمد", "إدارة المشاريع PMP"], + consultationFee: 20, + services: ["تصميم المباني", "إشراف التنفيذ", "استشارات هندسية"], + onlineConsultation: false, + inPersonConsultation: true + }, + { + id: 4, + name: "د. مريم الهنائية", + type: "dentist", + location: "nizwa", + rating: 4.6, + reviews: 92, + experience: 8, + languages: ["Arabic", "English"], + specialization: "طب الأسنان التجميلي", + phone: "+968 9456 7890", + email: "mariam.alhinai@example.om", + workingHours: "السبت - الأربعاء: 10:00 ص - 7:00 م", + education: "كلية طب الأسنان - جامعة السلطان قابوس", + certifications: ["دبلوم طب الأسنان التجميلي", "شهادة زراعة الأسنان"], + consultationFee: 15, + services: ["تنظيف الأسنان", "تجميل الأسنان", "زراعة الأسنان"], + onlineConsultation: true, + inPersonConsultation: true + } +]; + export default function ProfessionalDirectory() { - const [searchTerm, setSearchTerm] = useState(""); - const [selectedType, setSelectedType] = useState(""); - - const { data: professionals = [], isLoading } = useQuery({ - queryKey: ["/api/professionals/search", { searchTerm, type: selectedType }], - enabled: true, - }); - - // Mock data for now since the API endpoint doesn't exist yet - const mockProfessionals: Professional[] = [ - { - id: 1, - firstName: "Dr. Ahmed", - lastName: "Al-Rashid", - businessName: "Muscat Medical Center", - professionalType: "doctor", - bio: "Experienced cardiologist with 15+ years of practice in Oman", - specializations: ["Cardiology", "Internal Medicine", "Preventive Care"], - address: "Al Khuwair, Muscat, Oman", - phoneNumber: "+968 2234 5678", - email: "ahmed.rashid@medical.om", - profileImageUrl: "", - averageRating: 4.8, - totalReviews: 127, - allowAppointmentBooking: true, - }, - { - id: 2, - firstName: "Fatima", - lastName: "Al-Zahra", - businessName: "Legal Consultancy Oman", - professionalType: "lawyer", - bio: "Corporate law specialist focusing on business formation and compliance", - specializations: ["Corporate Law", "Business Formation", "Contract Law"], - address: "Ruwi, Muscat, Oman", - phoneNumber: "+968 2445 6789", - email: "fatima.zahra@legal.om", - profileImageUrl: "", - averageRating: 4.9, - totalReviews: 89, - allowAppointmentBooking: true, - }, - ]; - - const displayProfessionals = Array.isArray(professionals) && professionals.length > 0 ? professionals as Professional[] : mockProfessionals; + const { t } = useTranslation(); + const [searchTerm, setSearchTerm] = useState(''); + const [selectedType, setSelectedType] = useState(''); + const [selectedLocation, setSelectedLocation] = useState(''); + const [selectedProfessional, setSelectedProfessional] = useState(null); const professionalTypes = [ - "doctor", "dentist", "therapist", "consultant", "lawyer", - "accountant", "coach", "tutor", "other" + 'doctor', 'lawyer', 'engineer', 'accountant', 'consultant', + 'teacher', 'dentist', 'veterinarian', 'architect', 'therapist' ]; - const filteredProfessionals = displayProfessionals.filter((prof: Professional) => - prof.firstName.toLowerCase().includes(searchTerm.toLowerCase()) || - prof.lastName.toLowerCase().includes(searchTerm.toLowerCase()) || - prof.businessName?.toLowerCase().includes(searchTerm.toLowerCase()) || - prof.specializations?.some((spec: string) => - spec.toLowerCase().includes(searchTerm.toLowerCase()) - ) + const locations = [ + 'muscat', 'salalah', 'nizwa', 'sur', 'sohar', 'rustaq', + 'ibri', 'khasab', 'bahla', 'buraimi', 'adam', 'bidiyah' + ]; + + const filteredProfessionals = mockProfessionals.filter(professional => { + const matchesSearch = professional.name.toLowerCase().includes(searchTerm.toLowerCase()) || + professional.specialization.toLowerCase().includes(searchTerm.toLowerCase()); + const matchesType = !selectedType || professional.type === selectedType; + const matchesLocation = !selectedLocation || professional.location === selectedLocation; + return matchesSearch && matchesType && matchesLocation; + }); + + const ProfessionalCard = ({ professional }: { professional: Professional }) => ( + + +
+ + + + {professional.name.split(' ').map(n => n[0]).join('')} + + +
+ {professional.name} + + {t(`professionals.types.${professional.type}`)} • {professional.specialization} + +
+
+ + {t(`professionals.locations.${professional.location}`)} +
+
+ + {professional.rating} ({professional.reviews}) +
+
+ + {professional.experience} {t('professionals.experience')} +
+
+
+
+
+ +
+ {professional.languages.map((lang, index) => ( + + {lang} + + ))} +
+
+
+ {professional.consultationFee} ر.ع + /استشارة +
+ + + + + + + + + + + {professional.name.split(' ').map(n => n[0]).join('')} + + +
+
{professional.name}
+
+ {t(`professionals.types.${professional.type}`)} • {professional.specialization} +
+
+
+
+ + + + {t('professionals.contactInfo')} + {t('professionals.services')} + {t('professionals.bookAppointment')} + + + +
+
+
+ + {professional.phone} +
+
+ + {professional.email} +
+
+ + {t(`professionals.locations.${professional.location}`)} +
+
+ + {professional.workingHours} +
+
+ +
+
+ + {professional.education} +
+
+ + {professional.languages.join(', ')} +
+
+ + {professional.rating}/5 ({professional.reviews} مراجعة) +
+
+
+ +
+

+ + {t('professionals.certifications')} +

+
+ {professional.certifications.map((cert, index) => ( + + {cert} + + ))} +
+
+
+ + +
+

{t('professionals.services')}

+
+ {professional.services.map((service, index) => ( +
+ + {service} +
+ ))} +
+
+ +
+
+ + {t('professionals.onlineConsultation')} +
+
+ + {t('professionals.inPersonConsultation')} +
+
+ +
+
{t('professionals.consultationFee')}
+
+ {professional.consultationFee} ر.ع +
+
+
+ + +
+ +

+ {t('professionals.bookAppointment')} +

+

+ اختر الوقت المناسب لك من المواعيد المتاحة +

+ +
+
+
+
+
+
+
+
); return ( -
-
-
- {/* Header */} -
-

- Professional Directory -

-

- Discover and connect with trusted professionals in your area -

-
+
+
+

{t('professionals.title')}

+

{t('professionals.subtitle')}

+
- {/* Search and Filters */} -
-
- setSearchTerm(e.target.value)} - className="w-full" - /> -
-
- - {professionalTypes.map((type) => ( - - ))} -
-
- - {/* Results */} - {isLoading ? ( -
- {[...Array(6)].map((_, i) => ( - - -
-
-
-
-
-
-
-
- -
-
-
-
-
-
- ))} -
- ) : filteredProfessionals.length === 0 ? ( -
-

- No professionals found matching your criteria. -

-

- Try adjusting your search terms or filters. -

-
- ) : ( -
- {filteredProfessionals.map((professional: Professional) => ( - - -
- - - - {professional.firstName[0]}{professional.lastName[0]} - - -
- - {professional.firstName} {professional.lastName} - - {professional.businessName && ( -

- {professional.businessName} -

- )} -
- - - {professional.averageRating || "New"} - - - ({professional.totalReviews || 0} reviews) - -
-
-
-
- - - {professional.professionalType} - - - {professional.bio && ( -

- {professional.bio} -

- )} - - {professional.specializations && professional.specializations.length > 0 && ( -
- {professional.specializations.slice(0, 3).map((spec, index) => ( - - {spec} - - ))} - {professional.specializations.length > 3 && ( - - +{professional.specializations.length - 3} more - - )} -
- )} - -
- {professional.address && ( -
- - {professional.address} -
- )} - {professional.phoneNumber && ( -
- - {professional.phoneNumber} -
- )} -
- -
- - {professional.allowAppointmentBooking && ( - - )} -
-
-
- ))} -
- )} +
+
+ + setSearchTerm(e.target.value)} + className="pl-10" + />
+ + + + +
+ +
+ {filteredProfessionals.length > 0 ? ( + filteredProfessionals.map(professional => ( + + )) + ) : ( +
+ +

{t('professionals.noResults')}

+

{t('professionals.noProfessionals')}

+
+ )}
);