import axios from 'axios' import Head from 'next/head' import React, { ReactElement, useCallback, useEffect, useMemo, useState } from 'react' import LayoutAuthenticated from '../../layouts/Authenticated' import SectionMain from '../../components/SectionMain' import CardBox from '../../components/CardBox' import BaseButton from '../../components/BaseButton' import { getPageTitle } from '../../config' import { useAppSelector } from '../../stores/hooks' type Option = { id: string label?: string name?: string company_name?: string name_ar?: string name_en?: string } type Order = { id: string order_code?: string customer_name?: string customer_phone?: string delivery_address?: string package_description?: string weight_size?: string cod_amount?: string | number current_status?: string previous_status?: string notes?: string delivery_lat?: string | number delivery_lng?: string | number createdAt?: string last_status_at?: string merchant?: Option delivery_city?: Option delivery_region?: Option assigned_driver?: Option order_status_logs_order?: StatusLog[] } type StatusLog = { id: string from_status?: string to_status?: string comment?: string changed_at?: string createdAt?: string } type FormState = { merchant: string customer_name: string customer_phone: string delivery_city: string delivery_region: string delivery_address: string package_description: string weight_size: string cod_amount: string notes: string } const teal = '#01696f' const statusMeta = { pending: { label: 'قيد الانتظار', color: 'bg-slate-100 text-slate-700 ring-slate-200' }, assigned: { label: 'تم التعيين', color: 'bg-blue-100 text-blue-700 ring-blue-200' }, picked_up: { label: 'تم الاستلام', color: 'bg-yellow-100 text-yellow-800 ring-yellow-200' }, out_for_delivery: { label: 'في الطريق للتسليم', color: 'bg-orange-100 text-orange-700 ring-orange-200' }, delivered: { label: 'تم التسليم', color: 'bg-emerald-100 text-emerald-700 ring-emerald-200' }, failed_attempt: { label: 'محاولة فاشلة', color: 'bg-red-100 text-red-700 ring-red-200' }, rescheduled: { label: 'أعيدت الجدولة', color: 'bg-cyan-100 text-cyan-700 ring-cyan-200' }, cancelled: { label: 'ملغي', color: 'bg-zinc-200 text-zinc-800 ring-zinc-300' }, returned: { label: 'مرتجع', color: 'bg-purple-100 text-purple-700 ring-purple-200' }, } const nextStatuses = { pending: ['assigned', 'cancelled'], assigned: ['picked_up'], picked_up: ['out_for_delivery'], out_for_delivery: ['delivered', 'failed_attempt'], failed_attempt: ['rescheduled'], rescheduled: ['out_for_delivery'], delivered: [], cancelled: [], returned: [], } const emptyForm: FormState = { merchant: '', customer_name: '', customer_phone: '', delivery_city: '', delivery_region: '', delivery_address: '', package_description: '', weight_size: '', cod_amount: '', notes: '', } const labelFor = (status?: string) => statusMeta[status || 'pending']?.label || status || '—' const badgeClass = (status?: string) => `inline-flex items-center rounded-full px-3 py-1 text-xs font-bold ring-1 ${ statusMeta[status || 'pending']?.color || statusMeta.pending.color }` const optionLabel = (option?: Option) => option?.company_name || option?.name_ar || option?.name || option?.label || option?.name_en || '—' const todayIso = () => new Date().toISOString().slice(0, 10) const OperationsOrdersPage = () => { const { currentUser } = useAppSelector((state) => state.auth) const [orders, setOrders] = useState([]) const [selectedOrder, setSelectedOrder] = useState(null) const [merchants, setMerchants] = useState([]) const [drivers, setDrivers] = useState([]) const [cities, setCities] = useState([]) const [regions, setRegions] = useState([]) const [form, setForm] = useState(emptyForm) const [filters, setFilters] = useState({ query: '', status: '' }) const [nextStatus, setNextStatus] = useState('') const [statusDriver, setStatusDriver] = useState('') const [statusComment, setStatusComment] = useState('') const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [notice, setNotice] = useState<{ type: 'success' | 'error'; text: string } | null>(null) const fetchOrders = useCallback(async () => { const params: Record = { page: 0, limit: 50, field: 'createdAt', sort: 'desc' } if (filters.status) params.current_status = filters.status if (filters.query) { if (/^[0-9+\-\s]+$/.test(filters.query)) { params.customer_phone = filters.query } else { params.order_code = filters.query } } const { data } = await axios.get('orders', { params }) setOrders(Array.isArray(data.rows) ? data.rows : []) }, [filters.query, filters.status]) const fetchLookups = useCallback(async () => { const [merchantRes, driverRes, cityRes, regionRes] = await Promise.all([ axios.get('merchants', { params: { page: 0, limit: 100 } }), axios.get('drivers', { params: { page: 0, limit: 100 } }), axios.get('cities', { params: { page: 0, limit: 100 } }), axios.get('regions', { params: { page: 0, limit: 100 } }), ]) setMerchants(merchantRes.data.rows || []) setDrivers(driverRes.data.rows || []) setCities(cityRes.data.rows || []) setRegions(regionRes.data.rows || []) }, []) const refresh = useCallback(async () => { setLoading(true) try { await Promise.all([fetchOrders(), fetchLookups()]) } catch (error) { console.error('Failed to load parcel operations data', error) setNotice({ type: 'error', text: 'تعذر تحميل بيانات العمليات. تحقق من الصلاحيات أو الاتصال.' }) } finally { setLoading(false) } }, [fetchLookups, fetchOrders]) useEffect(() => { if (!currentUser?.id) { setLoading(false) return undefined } refresh().catch((error) => { console.error('Initial operations refresh failed', error) }) const timer = window.setInterval(() => { fetchOrders().catch((error) => { console.error('Auto refresh failed', error) }) }, 30000) return () => window.clearInterval(timer) }, [currentUser?.id, fetchOrders, refresh]) const selectedAllowedStatuses = useMemo(() => { if (!selectedOrder) return [] return nextStatuses[selectedOrder.current_status || 'pending'] || [] }, [selectedOrder]) const stats = useMemo(() => { const today = todayIso() const todaysOrders = orders.filter((order) => order.createdAt?.slice(0, 10) === today) return [ { label: 'طلبات اليوم', value: todaysOrders.length, hint: 'تحديث تلقائي كل 30 ثانية' }, { label: 'قيد الانتظار', value: orders.filter((order) => order.current_status === 'pending').length, hint: 'تحتاج إجراء' }, { label: 'تم التسليم', value: orders.filter((order) => order.current_status === 'delivered').length, hint: 'طلبات مكتملة' }, { label: 'فشل التسليم', value: orders.filter((order) => order.current_status === 'failed_attempt').length, hint: 'تحتاج متابعة' }, ] }, [orders]) const revenueToday = useMemo( () => orders .filter((order) => order.createdAt?.slice(0, 10) === todayIso()) .reduce((sum, order) => sum + Number(order.cod_amount || 0), 0), [orders], ) const handleFormChange = (field: keyof FormState, value: string) => { setForm((current) => ({ ...current, [field]: value })) } const handleCreateOrder = async (event: React.FormEvent) => { event.preventDefault() if (!form.customer_name.trim() || !form.customer_phone.trim() || !form.delivery_address.trim()) { setNotice({ type: 'error', text: 'اسم العميل، الهاتف، والعنوان حقول مطلوبة.' }) return } setSaving(true) setNotice(null) try { const orderCode = `ORD-${Date.now().toString().slice(-8)}` await axios.post('orders', { data: { ...form, order_code: orderCode, current_status: 'pending', previous_status: 'pending', placed_at: new Date().toISOString(), last_status_at: new Date().toISOString(), merchant: form.merchant || null, delivery_city: form.delivery_city || null, delivery_region: form.delivery_region || null, cod_amount: form.cod_amount || 0, }, }) setForm(emptyForm) await fetchOrders() setNotice({ type: 'success', text: `تم إنشاء الطلب ${orderCode} وأصبح جاهزاً للتعيين.` }) } catch (error) { console.error('Create order failed', error) setNotice({ type: 'error', text: 'تعذر إنشاء الطلب. تأكد من البيانات والصلاحيات.' }) } finally { setSaving(false) } } const openOrder = async (order: Order) => { setSelectedOrder(order) setNextStatus('') setStatusDriver(order.assigned_driver?.id || '') setStatusComment('') try { const { data } = await axios.get(`orders/${order.id}`) setSelectedOrder(data) setStatusDriver(data?.assigned_driver?.id || '') } catch (error) { console.error('Load order detail failed', error) setNotice({ type: 'error', text: 'تعذر فتح تفاصيل الطلب.' }) } } const handleStatusChange = async () => { if (!selectedOrder || !nextStatus) { setNotice({ type: 'error', text: 'اختر الحالة التالية أولاً.' }) return } if (nextStatus === 'assigned' && !statusDriver) { setNotice({ type: 'error', text: 'يجب اختيار سائق قبل تحويل الطلب إلى تم التعيين.' }) return } setSaving(true) setNotice(null) try { const { data } = await axios.put(`orders/${selectedOrder.id}/status`, { data: { current_status: nextStatus, assigned_driver: statusDriver || undefined, comment: statusComment, }, }) setSelectedOrder(data) setNextStatus('') setStatusComment('') await fetchOrders() setNotice({ type: 'success', text: 'تم تحديث حالة الطلب وتسجيلها في السجل.' }) } catch (error) { console.error('Status update failed', error) setNotice({ type: 'error', text: 'رفض الخادم هذا الانتقال. تأكد من التسلسل والصلاحيات.' }) } finally { setSaving(false) } } const sortedLogs = [...(selectedOrder?.order_status_logs_order || [])].sort( (a, b) => new Date(b.changed_at || b.createdAt || '').getTime() - new Date(a.changed_at || a.createdAt || '').getTime(), ) return ( <> {getPageTitle('Parcel Operations')}
مركز عمليات الطرود · [اسم شركتك]

إدارة طلبات التوصيل من الإدخال حتى تغيير الحالة

شريحة تشغيلية أولى تربط إنشاء الطلب، قائمة الطلبات، تفاصيل الطلب، وتحديث الحالة وفق مسار العمل المعتمد وبصلاحيات الخادم.

document.getElementById('create-order-form')?.scrollIntoView({ behavior: 'smooth' })} />

المستخدم الحالي

{currentUser?.firstName || currentUser?.email || 'مشغل النظام'}

الدور: {currentUser?.app_role?.name || 'غير محدد'}

تحصيل اليوم COD

{revenueToday.toLocaleString()} د.أ

{notice && (
{notice.text}
)}
{stats.map((stat) => (

{stat.label}

{loading ? '…' : stat.value}

{stat.hint}

))}

طلب جديد

إدخال طلب يدوي

الحالة الافتراضية: قيد الانتظار
handleFormChange('cod_amount', event.target.value)} placeholder="0.00" />
handleFormChange('customer_name', event.target.value)} placeholder="مثال: أحمد محمد" /> handleFormChange('customer_phone', event.target.value)} placeholder="07xxxxxxxx" />