From 477ebb999203c66c101d7b41b7c488564e226437 Mon Sep 17 00:00:00 2001 From: Muhmd <149331205+ByMuhmd@users.noreply.github.com> Date: Tue, 30 Jun 2026 21:26:16 +0300 Subject: [PATCH 1/8] fix: return dummy param instead of empty array for generateStaticParams --- app/(main)/worker/booking/[id]/page.tsx | 2 +- app/(main)/worker/messages/[id]/page.tsx | 2 +- app/client/addresses/edit/[id]/page.tsx | 2 +- app/client/booking/[workerId]/page.tsx | 2 +- app/client/chat/[id]/page.tsx | 2 +- app/client/craftsman/[id]/page.tsx | 2 +- app/client/order/cancel/[id]/page.tsx | 2 +- app/client/rate-review/[bookingId]/page.tsx | 2 +- app/client/services/[categoryId]/page.tsx | 2 +- app/client/wallet/edit-card/[id]/page.tsx | 2 +- fix_dynamic_routes_v4.py | 44 +++++++++++++++++++++ 11 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 fix_dynamic_routes_v4.py diff --git a/app/(main)/worker/booking/[id]/page.tsx b/app/(main)/worker/booking/[id]/page.tsx index cdba03d..e72c5ca 100644 --- a/app/(main)/worker/booking/[id]/page.tsx +++ b/app/(main)/worker/booking/[id]/page.tsx @@ -1,7 +1,7 @@ import ClientPage from './ClientPage'; export function generateStaticParams() { - return []; + return [{ id: 'dummy' }]; } export default function Page() { diff --git a/app/(main)/worker/messages/[id]/page.tsx b/app/(main)/worker/messages/[id]/page.tsx index cdba03d..e72c5ca 100644 --- a/app/(main)/worker/messages/[id]/page.tsx +++ b/app/(main)/worker/messages/[id]/page.tsx @@ -1,7 +1,7 @@ import ClientPage from './ClientPage'; export function generateStaticParams() { - return []; + return [{ id: 'dummy' }]; } export default function Page() { diff --git a/app/client/addresses/edit/[id]/page.tsx b/app/client/addresses/edit/[id]/page.tsx index cdba03d..e72c5ca 100644 --- a/app/client/addresses/edit/[id]/page.tsx +++ b/app/client/addresses/edit/[id]/page.tsx @@ -1,7 +1,7 @@ import ClientPage from './ClientPage'; export function generateStaticParams() { - return []; + return [{ id: 'dummy' }]; } export default function Page() { diff --git a/app/client/booking/[workerId]/page.tsx b/app/client/booking/[workerId]/page.tsx index cdba03d..0a86efe 100644 --- a/app/client/booking/[workerId]/page.tsx +++ b/app/client/booking/[workerId]/page.tsx @@ -1,7 +1,7 @@ import ClientPage from './ClientPage'; export function generateStaticParams() { - return []; + return [{ workerId: 'dummy' }]; } export default function Page() { diff --git a/app/client/chat/[id]/page.tsx b/app/client/chat/[id]/page.tsx index cdba03d..e72c5ca 100644 --- a/app/client/chat/[id]/page.tsx +++ b/app/client/chat/[id]/page.tsx @@ -1,7 +1,7 @@ import ClientPage from './ClientPage'; export function generateStaticParams() { - return []; + return [{ id: 'dummy' }]; } export default function Page() { diff --git a/app/client/craftsman/[id]/page.tsx b/app/client/craftsman/[id]/page.tsx index cdba03d..e72c5ca 100644 --- a/app/client/craftsman/[id]/page.tsx +++ b/app/client/craftsman/[id]/page.tsx @@ -1,7 +1,7 @@ import ClientPage from './ClientPage'; export function generateStaticParams() { - return []; + return [{ id: 'dummy' }]; } export default function Page() { diff --git a/app/client/order/cancel/[id]/page.tsx b/app/client/order/cancel/[id]/page.tsx index cdba03d..e72c5ca 100644 --- a/app/client/order/cancel/[id]/page.tsx +++ b/app/client/order/cancel/[id]/page.tsx @@ -1,7 +1,7 @@ import ClientPage from './ClientPage'; export function generateStaticParams() { - return []; + return [{ id: 'dummy' }]; } export default function Page() { diff --git a/app/client/rate-review/[bookingId]/page.tsx b/app/client/rate-review/[bookingId]/page.tsx index cdba03d..fb45e5d 100644 --- a/app/client/rate-review/[bookingId]/page.tsx +++ b/app/client/rate-review/[bookingId]/page.tsx @@ -1,7 +1,7 @@ import ClientPage from './ClientPage'; export function generateStaticParams() { - return []; + return [{ bookingId: 'dummy' }]; } export default function Page() { diff --git a/app/client/services/[categoryId]/page.tsx b/app/client/services/[categoryId]/page.tsx index cdba03d..b98fd86 100644 --- a/app/client/services/[categoryId]/page.tsx +++ b/app/client/services/[categoryId]/page.tsx @@ -1,7 +1,7 @@ import ClientPage from './ClientPage'; export function generateStaticParams() { - return []; + return [{ categoryId: 'dummy' }]; } export default function Page() { diff --git a/app/client/wallet/edit-card/[id]/page.tsx b/app/client/wallet/edit-card/[id]/page.tsx index cdba03d..e72c5ca 100644 --- a/app/client/wallet/edit-card/[id]/page.tsx +++ b/app/client/wallet/edit-card/[id]/page.tsx @@ -1,7 +1,7 @@ import ClientPage from './ClientPage'; export function generateStaticParams() { - return []; + return [{ id: 'dummy' }]; } export default function Page() { diff --git a/fix_dynamic_routes_v4.py b/fix_dynamic_routes_v4.py new file mode 100644 index 0000000..2944040 --- /dev/null +++ b/fix_dynamic_routes_v4.py @@ -0,0 +1,44 @@ +import os +import glob +import re + +routes = [ + "app/client/rate-review/[bookingId]", + "app/client/addresses/edit/[id]", + "app/client/order/cancel/[id]", + "app/client/craftsman/[id]", + "app/client/services/[categoryId]", + "app/client/booking/[workerId]", + "app/client/chat/[id]", + "app/client/wallet/edit-card/[id]", + "app/(main)/worker/booking/[id]", + "app/(main)/worker/messages/[id]", + "app/(onboarding)/intro/[step]" +] + +for route in routes: + page_path = os.path.join(route, "page.tsx") + + # extract parameter name + match = re.search(r'\[([^\]]+)\]$', route) + if not match: + continue + param_name = match.group(1) + + if route == "app/(onboarding)/intro/[step]": + continue # intro is already fine with 1, 2, 3, 4 + + new_page_content = f"""import ClientPage from './ClientPage'; + +export function generateStaticParams() {{ + return [{{ {param_name}: 'dummy' }}]; +}} + +export default function Page() {{ + return ; +}} +""" + with open(page_path, 'w') as f: + f.write(new_page_content) + print(f"Updated {page_path} to return [{{ {param_name}: 'dummy' }}]") + From 033299d8a33c6cf321aafd54e0a87e92061a694b Mon Sep 17 00:00:00 2001 From: Muhmd <149331205+ByMuhmd@users.noreply.github.com> Date: Tue, 30 Jun 2026 21:34:27 +0300 Subject: [PATCH 2/8] refactor: pass params to ClientPage components across all dynamic routes --- app/(main)/worker/booking/[id]/page.tsx | 4 ++-- app/(main)/worker/messages/[id]/page.tsx | 4 ++-- app/(onboarding)/intro/[step]/page.tsx | 4 ++-- app/client/addresses/edit/[id]/page.tsx | 4 ++-- app/client/booking/[workerId]/page.tsx | 4 ++-- app/client/chat/[id]/page.tsx | 4 ++-- app/client/craftsman/[id]/page.tsx | 4 ++-- app/client/order/cancel/[id]/page.tsx | 4 ++-- app/client/rate-review/[bookingId]/page.tsx | 4 ++-- app/client/services/[categoryId]/page.tsx | 4 ++-- app/client/wallet/edit-card/[id]/page.tsx | 4 ++-- package-lock.json | 17 +++-------------- 12 files changed, 25 insertions(+), 36 deletions(-) diff --git a/app/(main)/worker/booking/[id]/page.tsx b/app/(main)/worker/booking/[id]/page.tsx index e72c5ca..a7e5424 100644 --- a/app/(main)/worker/booking/[id]/page.tsx +++ b/app/(main)/worker/booking/[id]/page.tsx @@ -4,6 +4,6 @@ export function generateStaticParams() { return [{ id: 'dummy' }]; } -export default function Page() { - return ; +export default function Page({ params }: { params: any }) { + return ; } diff --git a/app/(main)/worker/messages/[id]/page.tsx b/app/(main)/worker/messages/[id]/page.tsx index e72c5ca..a7e5424 100644 --- a/app/(main)/worker/messages/[id]/page.tsx +++ b/app/(main)/worker/messages/[id]/page.tsx @@ -4,6 +4,6 @@ export function generateStaticParams() { return [{ id: 'dummy' }]; } -export default function Page() { - return ; +export default function Page({ params }: { params: any }) { + return ; } diff --git a/app/(onboarding)/intro/[step]/page.tsx b/app/(onboarding)/intro/[step]/page.tsx index 8b38c43..46548cb 100644 --- a/app/(onboarding)/intro/[step]/page.tsx +++ b/app/(onboarding)/intro/[step]/page.tsx @@ -9,6 +9,6 @@ export function generateStaticParams() { ]; } -export default function Page() { - return ; +export default function Page({ params }: { params: any }) { + return ; } diff --git a/app/client/addresses/edit/[id]/page.tsx b/app/client/addresses/edit/[id]/page.tsx index e72c5ca..a7e5424 100644 --- a/app/client/addresses/edit/[id]/page.tsx +++ b/app/client/addresses/edit/[id]/page.tsx @@ -4,6 +4,6 @@ export function generateStaticParams() { return [{ id: 'dummy' }]; } -export default function Page() { - return ; +export default function Page({ params }: { params: any }) { + return ; } diff --git a/app/client/booking/[workerId]/page.tsx b/app/client/booking/[workerId]/page.tsx index 0a86efe..1b7fe34 100644 --- a/app/client/booking/[workerId]/page.tsx +++ b/app/client/booking/[workerId]/page.tsx @@ -4,6 +4,6 @@ export function generateStaticParams() { return [{ workerId: 'dummy' }]; } -export default function Page() { - return ; +export default function Page({ params }: { params: any }) { + return ; } diff --git a/app/client/chat/[id]/page.tsx b/app/client/chat/[id]/page.tsx index e72c5ca..a7e5424 100644 --- a/app/client/chat/[id]/page.tsx +++ b/app/client/chat/[id]/page.tsx @@ -4,6 +4,6 @@ export function generateStaticParams() { return [{ id: 'dummy' }]; } -export default function Page() { - return ; +export default function Page({ params }: { params: any }) { + return ; } diff --git a/app/client/craftsman/[id]/page.tsx b/app/client/craftsman/[id]/page.tsx index e72c5ca..a7e5424 100644 --- a/app/client/craftsman/[id]/page.tsx +++ b/app/client/craftsman/[id]/page.tsx @@ -4,6 +4,6 @@ export function generateStaticParams() { return [{ id: 'dummy' }]; } -export default function Page() { - return ; +export default function Page({ params }: { params: any }) { + return ; } diff --git a/app/client/order/cancel/[id]/page.tsx b/app/client/order/cancel/[id]/page.tsx index e72c5ca..a7e5424 100644 --- a/app/client/order/cancel/[id]/page.tsx +++ b/app/client/order/cancel/[id]/page.tsx @@ -4,6 +4,6 @@ export function generateStaticParams() { return [{ id: 'dummy' }]; } -export default function Page() { - return ; +export default function Page({ params }: { params: any }) { + return ; } diff --git a/app/client/rate-review/[bookingId]/page.tsx b/app/client/rate-review/[bookingId]/page.tsx index fb45e5d..54ccb03 100644 --- a/app/client/rate-review/[bookingId]/page.tsx +++ b/app/client/rate-review/[bookingId]/page.tsx @@ -4,6 +4,6 @@ export function generateStaticParams() { return [{ bookingId: 'dummy' }]; } -export default function Page() { - return ; +export default function Page({ params }: { params: any }) { + return ; } diff --git a/app/client/services/[categoryId]/page.tsx b/app/client/services/[categoryId]/page.tsx index b98fd86..de0c360 100644 --- a/app/client/services/[categoryId]/page.tsx +++ b/app/client/services/[categoryId]/page.tsx @@ -4,6 +4,6 @@ export function generateStaticParams() { return [{ categoryId: 'dummy' }]; } -export default function Page() { - return ; +export default function Page({ params }: { params: any }) { + return ; } diff --git a/app/client/wallet/edit-card/[id]/page.tsx b/app/client/wallet/edit-card/[id]/page.tsx index e72c5ca..a7e5424 100644 --- a/app/client/wallet/edit-card/[id]/page.tsx +++ b/app/client/wallet/edit-card/[id]/page.tsx @@ -4,6 +4,6 @@ export function generateStaticParams() { return [{ id: 'dummy' }]; } -export default function Page() { - return ; +export default function Page({ params }: { params: any }) { + return ; } diff --git a/package-lock.json b/package-lock.json index 8b22722..492735a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2969,7 +2969,7 @@ "version": "19.2.17", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -3743,7 +3743,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/data-uri-to-buffer": { @@ -5789,17 +5789,6 @@ } } }, - "node_modules/next-intl/node_modules/@swc/helpers": { - "version": "0.5.23", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", - "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -7545,7 +7534,7 @@ "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", From db589ff2fdc4a1271dc2972e24fe916eabc9731a Mon Sep 17 00:00:00 2001 From: Muhmd <149331205+ByMuhmd@users.noreply.github.com> Date: Tue, 30 Jun 2026 21:42:03 +0300 Subject: [PATCH 3/8] refactor: implement client-side UI components and update routing logic across core pages --- README.md | 58 +-- app/(main)/worker/booking/[id]/page.tsx | 396 +++++++++++++++++- app/(main)/worker/messages/[id]/page.tsx | 48 ++- app/(onboarding)/intro/[step]/page.tsx | 439 +++++++++++++++++++- app/api/auth/callback/route.ts | 2 - app/client/addresses/edit/[id]/page.tsx | 158 ++++++- app/client/booking/[workerId]/page.tsx | 344 ++++++++++++++- app/client/chat/[id]/page.tsx | 48 ++- app/client/craftsman/[id]/page.tsx | 300 ++++++++++++- app/client/order/cancel/[id]/page.tsx | 78 +++- app/client/rate-review/[bookingId]/page.tsx | 205 ++++++++- app/client/services/[categoryId]/page.tsx | 104 ++++- app/client/wallet/edit-card/[id]/page.tsx | 172 +++++++- capacitor.config.ts | 2 +- next.config.mjs | 4 - package-lock.json | 17 +- package.json | 2 - tsconfig.tsbuildinfo | 1 + 18 files changed, 2268 insertions(+), 110 deletions(-) create mode 100644 tsconfig.tsbuildinfo diff --git a/README.md b/README.md index 924d9ef..bdf69a9 100644 --- a/README.md +++ b/README.md @@ -2,41 +2,42 @@ # Craftsmen Maintenance Marketplace - حِرفة (Hirfa) -**A production-ready, fully functional Arabic-first home maintenance marketplace.** The all-in-one home services platform for the Arabic world. Browse verified craftsmen, compare prices, view before/after portfolios, book instantly, and track your orders, all from your phone. +**A full-stack, mobile-first app, The all-in-one home services platform for the Arabic world. Browse verified craftsmen, compare prices, view before/after portfolios, book instantly, and track your orders , all from your phone.** --- -## ✨ Overview +## Overview -Hirfa is a complete home maintenance platform built with Next.js 16 (App Router) and Supabase. It serves two user types: +Hirfa is a complete home maintenance platform built with Next.js 16 and Supabase. It serves two user types: - **Clients** — Browse craftsmen, book services, request emergency help, manage orders and wallet - **Craftsmen (Workers)** — Manage gallery with before/after photos, accept/reject orders, set services, manage schedule and payments --- -## 🚀 Features +## ✨ Features ### 🔐 Authentication & Onboarding -- Mail-based login with 6-digit OTP verification and auto-focus + +- Phone-based login with OTP verification - Role selection (Client / Craftsman) -- Multi-step onboarding flow with splash, welcome, intro carousel, and success animation with particle effects +- Multi-step onboarding flow with splash, welcome, intro carousel, and success animation -### 📱 Client App +### Client App | Feature | Description | | --------------------- | ---------------------------------------------------------------------- | -| **Home** | Category grid, featured craftsmen, nearby listings, availability indicators | -| **Craftsman Profile** | Full profile with before/after gallery, services, reviews, tier badges | +| **Home** | Category grid, featured craftsmen, nearby listings | +| **Craftsman Profile** | Full profile with before/after gallery, services, reviews, booking CTA | | **Gallery Modal** | Card-based photo viewer with navigation (before/after comparison) | | **Booking** | 4-step wizard with date picker, time slots, address, payment method | -| **Emergency SOS** | Quick emergency booking with live tracking animation and ETA | +| **Emergency SOS** | Quick emergency booking with live tracking simulation | | **Orders** | Track order status (pending → confirmed → completed) | | **Wallet** | Deposit, cards management, transaction history | | **Addresses** | Saved addresses with CRUD | | **Search** | Search craftsmen by name or profession | | **Notifications** | Real-time notifications | -| **Profile** | Edit personal info, settings menu, quick action buttons | +| **Profile** | Edit personal info, settings menu | ### 🔧 Craftsman (Worker) App @@ -53,12 +54,13 @@ Hirfa is a complete home maintenance platform built with Next.js 16 (App Router) | **Wallet** | Earnings overview, withdrawals | | **Calendar** | View booked appointments | -### 👑 Admin Dashboard +### Admin Dashboard + - User management, system rules, content moderation --- -## 🏗️ Tech Stack & Architecture +## Tech Stack | Layer | Technology | | ------------------------ | ----------------------------------- | @@ -74,12 +76,6 @@ Hirfa is a complete home maintenance platform built with Next.js 16 (App Router) | **State** | React Hooks + TanStack Query | | **UI Primitives** | shadcn/ui + Base UI | -**Architecture Highlights:** -- **Mobile-first approach:** Max-width 390px centered, flexbox layout, touch-optimized. -- **Type Safety:** Full TypeScript implementation with robust interfaces. -- **Performance:** Turbopack for fast compilation (~9 seconds), optimized images via Next.js Image, code splitting with Suspense. -- **Internationalization Ready:** RTL layout support (`dir="rtl"`), Arabic typography optimized (Cairo font). - --- ## 📁 Project Structure @@ -88,7 +84,7 @@ Hirfa is a complete home maintenance platform built with Next.js 16 (App Router) Hirfa/ ├── app/ │ ├── (auth)/ # Login, register, OTP, verification -│ ├── (main)/ # Worker/Client pages (home, orders, etc.) +│ ├── (main)/ # Worker pages (home, orders, gallery, etc.) │ ├── (onboarding)/ # Splash, welcome, intro, success │ ├── admin/ # Admin dashboard │ ├── api/ # Supabase API routes @@ -100,17 +96,23 @@ Hirfa/ │ ├── auth/ # ProtectedRoute, Route guards │ ├── shared/ # CraftsmanCard, CategoryCard, OTPInput │ └── ui/ # BeforeAfterCard, ImageUploader, modals, etc. -├── contexts/ # Context providers (e.g. AuthContext) -├── hooks/ # Custom React hooks -├── lib/ # Types, mock-data, utils, Supabase clients -├── services/ # Auth and profile API services +├── contexts/ # AuthContext +├── hooks/ # useGallery, useCraftsmanProfile, etc. +├── lib/ # Types, utils, Supabase client/server +├── services/ # Auth and profile services ├── supabase/ # Migrations, schema └── public/ # Static assets ``` --- -## 💻 Getting Started +### Emergency SOS + +One-tap emergency booking that auto-fills user address and creates an urgent order with real-time tracking simulation. + +--- + +## 🚀 Getting Started ### Prerequisites @@ -138,11 +140,11 @@ SMTP_FROM=noreply@example.com NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=your_google_maps_api_key ``` -### 🛠️ Installation & Local Development +### 💻 Installation & Local Development 1. Clone the repository and install dependencies: ```bash -git clone https://github.com/codestcode/Hirfa.git +git clone https://github.com/your-username/Hirfa.git cd Hirfa pnpm install ``` @@ -152,7 +154,7 @@ pnpm install pnpm run dev ``` -3. Open [http://localhost:3000](http://localhost:3000) in your browser. The app starts at `/splash` and flows through onboarding. +3. Open [http://localhost:3000](http://localhost:3000) in your browser. --- diff --git a/app/(main)/worker/booking/[id]/page.tsx b/app/(main)/worker/booking/[id]/page.tsx index a7e5424..ca48995 100644 --- a/app/(main)/worker/booking/[id]/page.tsx +++ b/app/(main)/worker/booking/[id]/page.tsx @@ -1,9 +1,393 @@ -import ClientPage from './ClientPage'; +'use client' +import React, { useState, useEffect, useCallback } from 'react' +import { useParams, useRouter } from 'next/navigation' +import { ArrowRight, MapPin, Clock, CalendarDays, Phone, MessageSquare, CheckCircle2, Truck, Hammer, XCircle, Map as MapIcon, Image as ImageIcon } from 'lucide-react' +import { PageLoader } from '@/components/ui/PageLoader' +import { BookingStatusBadge } from '@/components/ui/BookingStatusBadge' +import { useAuth } from '@/contexts/AuthContext' +import { createClient } from '@/lib/supabase/client' +import { createNotification } from '@/lib/notifications' +import { Geolocation } from '@capacitor/geolocation' +import dynamic from 'next/dynamic' +const LeafletTrackingMap = dynamic( + () => import('@/components/shared/LeafletTrackingMap'), + { ssr: false } +) +export default function WorkerBookingDetailsPage() { + const { id } = useParams() + const router = useRouter() + const { profile } = useAuth() + const supabase = createClient() + const [booking, setBooking] = useState(null) + const [loading, setLoading] = useState(true) + const [watchId, setWatchId] = useState(null) + const [clientLat, setClientLat] = useState(30.0444) + const [clientLng, setClientLng] = useState(31.2357) + const [displayAddress, setDisplayAddress] = useState('عنوان غير محدد') + const [notesText, setNotesText] = useState('') + const [images, setImages] = useState([]) + const [statusNote, setStatusNote] = useState('') + const fetchBooking = useCallback(async () => { + setLoading(true) + const { data, error } = await supabase + .from('bookings') + .select('*, client:client_id(*)') + .eq('id', id) + .single() + if (error) { + console.error(error) + router.push('/worker/schedule') + return + } + if (data) { + setBooking(data) + let addr = data.address || 'عنوان غير محدد' + if (addr.includes('|')) { + const parts = addr.split('|') + addr = parts[0].trim() + const coordsStr = parts[1]?.trim() + if (coordsStr) { + const [latStr, lngStr] = coordsStr.split(',') + const parsedLat = parseFloat(latStr) + const parsedLng = parseFloat(lngStr) + if (!isNaN(parsedLat) && !isNaN(parsedLng)) { + setClientLat(parsedLat) + setClientLng(parsedLng) + } + } + } + setDisplayAddress(addr) + if (data.notes) { + if (data.notes.trim().startsWith('{')) { + try { + const parsed = JSON.parse(data.notes) + setNotesText(parsed.text || '') + if (Array.isArray(parsed.images)) setImages(parsed.images) + } catch (e) { + setNotesText(data.notes) + } + } else { + setNotesText(data.notes) + } + } + } + setLoading(false) + }, [id, supabase, router]) + useEffect(() => { + fetchBooking() + }, [fetchBooking]) + useEffect(() => { + return () => { + if (watchId) Geolocation.clearWatch({ id: watchId }) + } + }, [watchId]) + const stopTracking = async () => { + if (watchId) { + await Geolocation.clearWatch({ id: watchId }) + setWatchId(null) + } + } + const startTracking = async () => { + try { + const perm = await Geolocation.checkPermissions() + if (perm.location !== 'granted') { + const req = await Geolocation.requestPermissions() + if (req.location !== 'granted') { + alert('يجب الموافقة على صلاحية الموقع لتفعيل التتبع المباشر') + return + } + } + const wid = await Geolocation.watchPosition({ enableHighAccuracy: true }, (pos, err) => { + if (pos) { + supabase.from('bookings').update({ + craftsman_lat: pos.coords.latitude, + craftsman_lng: pos.coords.longitude + }).eq('id', id).then() + } + }) + setWatchId(wid) + } catch (e) { + console.error(e) + } + } + const openMaps = () => { + if (booking?.address?.includes('|')) { + const coordsStr = booking.address.split('|')[1]?.trim() + if (coordsStr) { + window.open(`https://maps.google.com/?q=${coordsStr}`) + return + } + } + window.open(`https://maps.google.com/?q=${encodeURIComponent(booking?.address || '')}`) + } + const updateTrackingStatus = async (nextStatus: string, label: string) => { + try { + const currentHistory = Array.isArray(booking?.status_history) ? booking.status_history : [] + const newHistoryEntry = { + tracking_status: nextStatus, + notes: statusNote.trim() || null, + timestamp: new Date().toISOString() + } -export function generateStaticParams() { - return [{ id: 'dummy' }]; -} + const updates: any = { + tracking_status: nextStatus, + status_history: [...currentHistory, newHistoryEntry] + } + if (statusNote.trim()) { + updates.status_notes = statusNote.trim() + } + if (nextStatus === 'accepted' && booking.status === 'pending') { + updates.status = 'confirmed' + } + const { error } = await supabase.from('bookings').update(updates).eq('id', booking.id) + if (error) throw error + await createNotification( + booking.client_id, + 'تحديث حالة الطلب', + `حالة طلبك (${booking.service_name}) تغيرت إلى: ${label}` + ) + if (nextStatus === 'on_the_way') { + await startTracking() + } else if (['arrived', 'work_started', 'completed'].includes(nextStatus)) { + await stopTracking() + } + fetchBooking() + setStatusNote('') + } catch (err) { + alert('فشل تحديث الحالة') + } + } + const completeBooking = async () => { + try { + await stopTracking() + const { processBookingCompletion } = await import('@/lib/supabase/booking-payments') + await processBookingCompletion(supabase, booking.id) + const currentHistory = Array.isArray(booking?.status_history) ? booking.status_history : [] + const newHistoryEntry = { + tracking_status: 'completed', + timestamp: new Date().toISOString() + } -export default function Page({ params }: { params: any }) { - return ; + const { error } = await supabase + .from('bookings') + .update({ + status: 'completed', + tracking_status: 'completed', + status_history: [...currentHistory, newHistoryEntry] + }) + .eq('id', booking.id) + if (error) throw error + await createNotification( + booking.client_id, + 'اكتملت الخدمة', + `تم إكمال خدمة ${booking.service_name} بنجاح.` + ) + fetchBooking() + } catch (err) { + alert('فشل إنهاء الطلب') + } + } + const cancelBooking = async () => { + try { + await stopTracking() + const { error } = await supabase + .from('bookings') + .update({ status: 'cancelled' }) + .eq('id', booking.id) + if (error) throw error + await createNotification( + booking.client_id, + 'تم إلغاء الموعد', + `تم إلغاء موعد خدمة ${booking.service_name} بواسطة الحرفي.` + ) + fetchBooking() + } catch (err) { + alert('فشل إلغاء الطلب') + } + } + if (loading) return + if (!booking) return null + return ( +
+
+ +

تفاصيل الطلب #{booking.id.slice(0,6)}

+
+
+
+
+
+
+

العميل

+

{booking.client?.full_name}

+
+
+ + +
+
+
+
+
+

تفاصيل الحجز #{booking.id.slice(0, 8)}

+ +
+ {booking.is_emergency && ( + + طلب طوارئ عاجل + + )} +
+
+
+
+

الخدمة المطلوبة

+

{booking.service_name}

+
+
+

التكلفة الإجمالية

+

{booking.price} ج.م

+
+
+
+
+ +
+

التاريخ

+

{booking.appointment_date}

+
+
+
+ +
+

الوقت

+

{booking.appointment_time?.slice(0,5)}

+
+
+
+
+
+
+
+ +
+

الموقع

+

{displayAddress}

+
+
+ +
+
+ +
+
+ {(notesText || images.length > 0) && ( +
+

+ + التفاصيل المرفقة +

+ {notesText && ( +

{notesText}

+ )} + {images.length > 0 && ( +
+ {images.map((img, i) => ( + مرفق + ))} +
+ )} +
+ )} +
+

إدارة حالة الطلب

+ {(booking.status === 'confirmed' || booking.status === 'pending') && ( +
+ +