diff --git a/app/(main)/worker/profile/page.tsx b/app/(main)/worker/profile/page.tsx index 9ef3252..44b0773 100644 --- a/app/(main)/worker/profile/page.tsx +++ b/app/(main)/worker/profile/page.tsx @@ -21,6 +21,7 @@ export default function ProfilePage() { const supabase = createClient() const router = useRouter() const [subscription, setSubscription] = useState(null) + const [orgMembership, setOrgMembership] = useState<{ org_name: string; org_rating: number; org_id: string } | null>(null) useEffect(() => { if (!profile?.id) return @@ -31,6 +32,24 @@ export default function ProfilePage() { fetchSub() }, [profile]) + useEffect(() => { + if (!profile?.id) return + const fetchOrg = async () => { + const { data } = await supabase + .from('organization_workers') + .select('organization_id, organizations!inner(id, name, rating)') + .eq('linked_user_id', profile.id) + .eq('is_active', true) + .limit(1) + .maybeSingle() + if (data?.organizations) { + const org = data.organizations as any + setOrgMembership({ org_name: org.name, org_rating: org.rating || 0, org_id: org.id }) + } + } + fetchOrg() + }, [profile?.id, supabase]) + const handleLogout = async () => { await supabase.auth.signOut() router.push('/login') @@ -159,6 +178,28 @@ export default function ProfilePage() { + {/* Org Membership Rating */} + {orgMembership && ( +
+
router.push(`/client/organization/${orgMembership.org_id}`)} + className="bg-gradient-to-r from-[#FF8A00]/10 to-[#FFB800]/5 border border-[#FF8A00]/20 rounded-2xl p-4 flex items-center gap-3 cursor-pointer active:scale-[0.98] transition-transform" + > +
+ +
+
+

تقييم كجزء من المؤسسة

+

{orgMembership.org_name}

+
+
+ + {orgMembership.org_rating || '0.0'} +
+
+
+ )} +
حرفة - الإصدار 2.4.0
diff --git a/app/(main)/worker/profile/summary/page.tsx b/app/(main)/worker/profile/summary/page.tsx index d1e44c0..dc9bad2 100644 --- a/app/(main)/worker/profile/summary/page.tsx +++ b/app/(main)/worker/profile/summary/page.tsx @@ -1,9 +1,9 @@ 'use client' -import React from 'react' +import React, { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' import Image from 'next/image' -import { CheckCircle2, Power, Briefcase, Star } from 'lucide-react' +import { CheckCircle2, Power, Briefcase, Star, Building2 } from 'lucide-react' import { PageHeader } from '@/components/ui/PageHeader' import { SubPageLayout } from '@/components/ui/SubPageLayout' import { StatCard } from '@/components/ui/profile/StatCard' @@ -12,10 +12,31 @@ import { ServiceSummaryCard } from '@/components/ui/profile/ServiceSummaryCard' import { ContactInfoCard } from '@/components/ui/profile/ContactInfoCard' import { useProfileSummary } from '@/hooks/useProfileSummary' import { PageLoader } from '@/components/ui/PageLoader' +import { createClient } from '@/lib/supabase/client' export default function ProfileSummaryPage() { const router = useRouter() const { profile, services, reviews, loading } = useProfileSummary() + const supabase = createClient() + const [orgMembership, setOrgMembership] = useState<{ org_name: string; org_rating: number; org_id: string } | null>(null) + + useEffect(() => { + if (!profile?.id) return + const fetchOrg = async () => { + const { data } = await supabase + .from('organization_workers') + .select('organization_id, organizations!inner(id, name, rating)') + .eq('linked_user_id', profile.id) + .eq('is_active', true) + .limit(1) + .maybeSingle() + if (data?.organizations) { + const org = data.organizations as any + setOrgMembership({ org_name: org.name, org_rating: org.rating || 0, org_id: org.id }) + } + } + fetchOrg() + }, [profile?.id, supabase]) return ( @@ -63,6 +84,26 @@ export default function ProfileSummaryPage() { )} + + {/* Org Membership Rating */} + {orgMembership && ( +
router.push(`/client/organization/${orgMembership.org_id}`)} + className="mt-8 bg-gradient-to-r from-[#FF8A00]/10 to-[#FFB800]/5 border border-[#FF8A00]/20 rounded-2xl p-4 flex items-center gap-3 cursor-pointer active:scale-[0.98] transition-transform" + > +
+ +
+
+

تقييم كجزء من المؤسسة

+

{orgMembership.org_name}

+
+
+ + {orgMembership.org_rating || '0.0'} +
+
+ )}
) diff --git a/app/client/booking/[workerId]/ClientPage.tsx b/app/client/booking/[workerId]/ClientPage.tsx index 0b141d5..d709f17 100644 --- a/app/client/booking/[workerId]/ClientPage.tsx +++ b/app/client/booking/[workerId]/ClientPage.tsx @@ -3,7 +3,7 @@ import { useState, useMemo, use, useEffect } from 'react' import { useRouter } from 'next/navigation' import Image from 'next/image' -import { ArrowLeft, Plus, Clock, CalendarDays } from 'lucide-react' +import { ArrowLeft, Plus, Clock, CalendarDays, Building2, Briefcase } from 'lucide-react' import { createClient } from '@/lib/supabase/client' import LocationPicker from '@/components/shared/LocationPicker' @@ -76,11 +76,22 @@ function generateTimeSlots(start: string, end: string): string[] { return slots } +interface OrgService { + id: string + name: string + description: string | null + price: number | null + icon: string | null +} + export default function BookingPage({ params }: { params: Promise<{ workerId: string }> }) { const router = useRouter() const { workerId } = use(params) const supabase = createClient() - + + const searchParams = typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : null + const orgId = searchParams?.get('organization_id') || '' + const weekDates = useMemo(() => getDateRange(14), []) const [selectedDate, setSelectedDate] = useState(todayStr) const [selectedTime, setSelectedTime] = useState('') @@ -91,44 +102,83 @@ export default function BookingPage({ params }: { params: Promise<{ workerId: st const [images, setImages] = useState([]) const [workerProfile, setWorkerProfile] = useState(null) + const [orgProfile, setOrgProfile] = useState(null) + const [orgServices, setOrgServices] = useState([]) + const [selectedService, setSelectedService] = useState(null) const [schedule, setSchedule] = useState([]) const [bookedTimes, setBookedTimes] = useState([]) const [loading, setLoading] = useState(true) + const isOrg = !!orgId + useEffect(() => { let active = true async function loadData() { setLoading(true) - const { data: prof } = await supabase.from('profiles').select('*').eq('id', workerId).single() - if (!active) return - setWorkerProfile(prof) - - if (prof) { - const { data: sched } = await supabase.from('worker_schedule').select('*').eq('worker_id', workerId) - if (active && sched) setSchedule(sched) - + + if (isOrg) { + const { data: org } = await supabase + .from('organizations') + .select('*') + .eq('id', orgId) + .eq('is_active', true) + .single() + if (!active) return + setOrgProfile(org) + + const { data: services } = await supabase + .from('organization_services') + .select('*') + .eq('organization_id', orgId) + .order('sort_order', { ascending: true }) + if (active && services) { + setOrgServices(services) + if (services.length === 1) setSelectedService(services[0]) + } + const { data: books } = await supabase .from('bookings') .select('appointment_time') - .eq('worker_id', workerId) + .eq('organization_id', orgId) .eq('appointment_date', selectedDate) .not('status', 'eq', 'cancelled') - + if (active && books) { setBookedTimes(books.map(b => b.appointment_time.slice(0, 5))) } + } else { + const { data: prof } = await supabase.from('profiles').select('*').eq('id', workerId).single() + if (!active) return + setWorkerProfile(prof) + + if (prof) { + const { data: sched } = await supabase.from('worker_schedule').select('*').eq('worker_id', workerId) + if (active && sched) setSchedule(sched) + + const { data: books } = await supabase + .from('bookings') + .select('appointment_time') + .eq('worker_id', workerId) + .eq('appointment_date', selectedDate) + .not('status', 'eq', 'cancelled') + + if (active && books) { + setBookedTimes(books.map(b => b.appointment_time.slice(0, 5))) + } + } } setLoading(false) } loadData() - return () => { - active = false - } - }, [workerId, selectedDate, supabase]) + return () => { active = false } + }, [workerId, orgId, isOrg, selectedDate, supabase]) const currentDayKey = useMemo(() => getDayKey(selectedDate), [selectedDate]) - + const daySched = useMemo(() => { + if (isOrg) { + return defaultSchedule[currentDayKey] + } const dbSched = schedule.find(s => s.day_id === currentDayKey) if (dbSched) { return { @@ -138,7 +188,7 @@ export default function BookingPage({ params }: { params: Promise<{ workerId: st } } return defaultSchedule[currentDayKey] - }, [schedule, currentDayKey]) + }, [schedule, currentDayKey, isOrg]) const timeSlots = useMemo(() => { if (!daySched || !daySched.active) return [] @@ -160,6 +210,7 @@ export default function BookingPage({ params }: { params: Promise<{ workerId: st const scheduleSummary = useMemo(() => { const dayKeys = ['sat', 'sun', 'mon', 'tue', 'wed', 'thu', 'fri'] return dayKeys.map(key => { + if (isOrg) return { key, ...defaultSchedule[key] } const db = schedule.find(s => s.day_id === key) if (db) { return { @@ -171,7 +222,7 @@ export default function BookingPage({ params }: { params: Promise<{ workerId: st } return { key, ...defaultSchedule[key] } }) - }, [schedule]) + }, [schedule, isOrg]) const handleContinue = () => { if (!selectedTime) return @@ -179,7 +230,6 @@ export default function BookingPage({ params }: { params: Promise<{ workerId: st alert('يرجى إرفاق صورة توضيحية واحدة على الأقل') return } - if (!address && (!lat || !lng)) { alert('يرجى تحديد موقعك الحالي أو إدخال العنوان التفصيلي') return @@ -192,18 +242,16 @@ export default function BookingPage({ params }: { params: Promise<{ workerId: st return } - const params = new URLSearchParams(window.location.search) - const serviceName = params.get('serviceName') || '' - const servicePrice = params.get('servicePrice') || '' - const orgId = params.get('organization_id') || '' - + const serviceName = isOrg ? (selectedService?.name || '') : (new URLSearchParams(window.location.search).get('serviceName') || '') + const servicePrice = isOrg ? (selectedService?.price?.toString() || '') : (new URLSearchParams(window.location.search).get('servicePrice') || '') + let url = `/client/booking/confirm?workerId=${workerId}&date=${selectedDate}&time=${selectedTime}¬es=${encodeURIComponent(notes)}` - if (orgId) url += `&organization_id=${orgId}` + if (isOrg) url += `&organization_id=${orgId}` if (serviceName) url += `&serviceName=${encodeURIComponent(serviceName)}` if (servicePrice) url += `&servicePrice=${servicePrice}` if (address) url += `&address=${encodeURIComponent(address)}` if (lat && lng) url += `&lat=${lat}&lng=${lng}` - + router.push(url) } @@ -236,12 +284,20 @@ export default function BookingPage({ params }: { params: Promise<{ workerId: st if (loading) { return (
-
جاري التحميل...
+
+
+ ) + } + + if (isOrg && !orgProfile) { + return ( +
+
المؤسسة غير موجودة
) } - if (!workerProfile) { + if (!isOrg && !workerProfile) { return (
الحرفي غير موجود
@@ -256,39 +312,103 @@ export default function BookingPage({ params }: { params: Promise<{ workerId: st -

تحديد الموعد

+

{isOrg ? 'حجز خدمة مؤسسة' : 'تحديد الموعد'}

-
-
- -

أوقات العمل الأسبوعية

+ {/* Org Header */} + {isOrg && orgProfile && ( +
+
+ {orgProfile.logo_url ? ( + + ) : ( + + )} +
+
+

{orgProfile.name}

+ {orgProfile.category &&

{orgProfile.category}

} +
-
- {scheduleSummary.map(s => ( -
- {DAY_NAMES_FULL[s.key]?.short} - {s.active && ( - - {to12Hour(s.start)} - {to12Hour(s.end)} - - )} -
- ))} + )} + + {/* Org Services Selection */} + {isOrg && orgServices.length > 0 && ( +
+

+ + اختر الخدمة +

+
+ {orgServices.map(service => { + const isSelected = selectedService?.id === service.id + return ( +
setSelectedService(isSelected ? null : service)} + className={`bg-[#0F172A]/40 border rounded-xl p-3 flex items-center justify-between cursor-pointer transition-all active:scale-[0.99] ${ + isSelected ? 'border-[#FF8A00] bg-[#FF8A00]/5' : 'border-white/5' + }`} + > +
+ {service.name} + {service.description && ( +

{service.description}

+ )} +
+
+ {service.price != null && ( + + {service.price} ج.م + + )} +
+ {isSelected ? '✓' : '+'} +
+
+
+ ) + })} +
-
+ )} + + {/* Worker Schedule Summary (worker only) */} + {!isOrg && ( +
+
+ +

أوقات العمل الأسبوعية

+
+
+ {scheduleSummary.map(s => ( +
+ {DAY_NAMES_FULL[s.key]?.short} + {s.active && ( + + {to12Hour(s.start)} - {to12Hour(s.end)} + + )} +
+ ))} +
+
+ )} + {/* Date Picker */}
@@ -321,6 +441,7 @@ export default function BookingPage({ params }: { params: Promise<{ workerId: st
+ {/* Time Slots */}

المواعيد المتاحة @@ -333,7 +454,7 @@ export default function BookingPage({ params }: { params: Promise<{ workerId: st {!daySched.active ? (
- الحرفي في إجازة هذا اليوم + {isOrg ? 'المؤسسة غير متاحة هذا اليوم' : 'الحرفي في إجازة هذا اليوم'}
) : timeSlots.length === 0 ? (
@@ -345,7 +466,7 @@ export default function BookingPage({ params }: { params: Promise<{ workerId: st const isSelected = selectedTime === time const available = isSlotAvailable(time) const isDisabled = !available - + return (