// ResDri — Earnings, Wallet, Profile tab screens. // All idle screens use the same chrome: AppTopBar at top, content scrolling, // TabBar at bottom. Layouts assume content padding accounts for both. const TOP_PAD = 108; // clearance below the app top bar const BOT_PAD = 92; // clearance above the tab bar // ═══════════════════════════════════════════════════════════ // ORDERS — full-screen pool list, no map // Pure list view of available orders. Same sort/filter as Home sheet. // ═══════════════════════════════════════════════════════════ function OrdersScreen({ online, today, openOrder, acceptOrder, radiusKm = 2.0, orders, driverName }) { const [sort, setSort] = React.useState('best'); const [filter, setFilter] = React.useState('all'); // Live API orders only — no demo fallback. const pool = Array.isArray(orders) ? orders : []; const sortOpts = [ { id: 'best', lbl: 'الأفضل' }, { id: 'nearest', lbl: 'الأقرب' }, { id: 'highest', lbl: 'الأعلى ربحاً' }, ]; const filterOpts = [ { id: 'all', lbl: 'الكل', icon: null }, { id: 'food', lbl: 'طعام', icon: 'food' }, { id: 'parcel', lbl: 'طرود', icon: 'package' }, { id: 'grocery', lbl: 'بقالة', icon: 'cart' }, ]; const all = pool.filter(o => filter === 'all' ? true : o.kind === filter); const inRadius = all.filter(o => (o.toPickup ?? o.distance) <= radiusKm); const outRadius = all.filter(o => (o.toPickup ?? o.distance) > radiusKm); const score = (o) => (o.payout + (o.bonus || 0)) / Math.max(0.5, o.distance); const sortFn = sort === 'nearest' ? ((a, b) => (a.toPickup ?? a.distance) - (b.toPickup ?? b.distance)) : sort === 'highest' ? ((a, b) => (b.payout + b.bonus) - (a.payout + a.bonus)) : ((a, b) => score(b) - score(a)); const sortedIn = [...inRadius].sort(sortFn); const sortedOut = [...outRadius].sort(sortFn); const topPickId = sortedIn[0]?.id; return (
{/* Title + radius indicator */}
الطلبات
{inRadius.length} في نطاقك · {outRadius.length} خارج النطاق
نطاق {radiusKm.toFixed(1).replace(/\.0$/, '')} كم
{/* Sort + filter row */}
{/* Sort segmented */}
{sortOpts.map(o => ( ))}
{/* Filter chips */}
{filterOpts.map(o => { const active = filter === o.id; return ( ); })}
{/* IN-RADIUS list */}
{sortedIn.length === 0 && (
لا توجد طلبات في نطاقك حالياً
وسّع نطاق العمل من الشاشة الرئيسية للوصول إلى المزيد.
)} {sortedIn.map(o => ( openOrder(o.id)} onAccept={() => acceptOrder ? acceptOrder(o.id) : openOrder(o.id)} /> ))}
{/* OUT-OF-RADIUS section */} {sortedOut.length > 0 && ( <>
خارج نطاقك
{sortedOut.map(o => (
openOrder(o.id)} onAccept={() => acceptOrder ? acceptOrder(o.id) : openOrder(o.id)} /> {/* Distance badge overlay top-left */} {(o.toPickup ?? o.distance).toFixed(1)} كم
))}
)}
); } // ═══════════════════════════════════════════════════════════ // EARNINGS — today / week / month + performance tile // ═══════════════════════════════════════════════════════════ // HH:MM in Latin digits regardless of locale. function fmtClock(ts) { if (!ts) return ''; const d = new Date(ts); if (isNaN(d.getTime())) return ''; return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); } const EARN_EMPTY = { today: { total: 0, orders: 0 }, week: { total: 0, orders: 0 }, month: { total: 0, orders: 0 }, days: [], }; function EarningsScreen({ online, driverName }) { const [tab, setTab] = React.useState('week'); const [earn, setEarn] = React.useState(null); // null = loading const [history, setHistory] = React.useState(null); // Pull real earnings (today/week/month + 7-day series) and recent orders. React.useEffect(() => { let cancelled = false; (async () => { try { const [e, h] = await Promise.all([ window.api.earnings(), window.api.history(6).catch(() => []), ]); if (cancelled) return; setEarn(e || EARN_EMPTY); setHistory(Array.isArray(h) ? h.filter(o => o && o.status === 'COMPLETED') : []); } catch (err) { if (!cancelled) { setEarn(EARN_EMPTY); setHistory([]); } } })(); return () => { cancelled = true; }; }, []); const loading = earn === null; const totals = earn || EARN_EMPTY; const sel = totals[tab] || { total: 0, orders: 0 }; const days = totals.days || []; const max = Math.max(1, ...days.map(d => d.total || 0)); const avg = sel.orders > 0 ? (sel.total / sel.orders) : 0; return (
{/* Page title */}
الأرباح
{/* Segmented tabs */}
{[ { id: 'today', lbl: 'اليوم' }, { id: 'week', lbl: 'آخر ٧ أيام' }, { id: 'month', lbl: 'آخر ٣٠ يوم' }, ].map(t => ( ))}
{/* Total + breakdown card */}
إجمالي الأرباح
{sel.orders} طلب {avg.toFixed(1)} متوسط/طلب
{/* Bar chart — 7-day series */} {tab === 'week' && days.length > 0 && (
{days.map((d, i) => (
{d.total}
{d.label}
))}
)}
{/* Recent orders — real completed history */}
آخر الطلبات
{history && history.length === 0 && (
لا توجد طلبات مكتملة بعد
)} {(history || []).map(o => ( ))}
); } function KPI({ label, value, suffix, tone }) { const colors = { accent: 'var(--accent-700)', primary: 'var(--primary)', success: 'var(--success)', danger: 'var(--danger)', }; const c = colors[tone] || 'var(--text)'; return (
{label}
{value} {suffix}
); } function HistoryRow({ business, customer, amt, time, kind }) { return (
{business}
للزبون {customer} · {time}
); } // ═══════════════════════════════════════════════════════════ // WALLET — commission owed vs cash held + settle button // ═══════════════════════════════════════════════════════════ function WalletScreen({ online, driver, driverName }) { // Real driver wallet from /drivers/me. Cash cap from server config. const cash = Number(driver?.cashHeld) || 0; // cash currently in driver's hand const commission = Number(driver?.commissionOwed) || 0; // commission owed to platform const cap = (window.RESDRI_CONFIG && window.RESDRI_CONFIG.cashCapIls) || 500; const pct = Math.min(1, cap > 0 ? commission / cap : 0); const nearCap = pct > 0.7; return (
المحفظة
{/* Big split card */}
عمولة مستحقة للمنصة من حد ₪{cap}
{/* Progress bar */}
{nearCap && (
اقتربت من الحد. سدّد قريباً للاستمرار في تلقي الطلبات.
)}
{/* Cash held */}
نقد بحوزتك
منه ₪{commission} للمنصة
{/* Settle now CTA */}
تسوية الآن (₪{commission})
{/* Settlement points */}
نقاط التسوية القريبة
{[ { name: 'مكتب ريسدري — البلدة القديمة', dist: 1.2, open: true }, { name: 'دكان أبو وليد', dist: 2.4, open: true }, { name: 'محطة وقود الزيتون', dist: 3.1, open: false }, ].map((p, i, arr) => (
{p.name}
{p.dist.toFixed(1)} كم · {p.open ? 'مفتوح الآن' : 'مغلق'}
))}
{/* History */}
آخر التسويات
{[ { d: 'الأمس 16:42', amt: 320 }, { d: 'الإثنين 18:10', amt: 285 }, { d: 'الأحد 19:05', amt: 410 }, ].map((s, i) => (
تم تسوية النقد
{s.d}
))}
); } // ═══════════════════════════════════════════════════════════ // PROFILE — avatar + rows // ═══════════════════════════════════════════════════════════ // Vehicle kind → (Arabic label, VehicleIcon kind) const VEHICLE_LABEL = { CAR: 'سيارة', MOTORBIKE: 'دراجة نارية', BICYCLE: 'دراجة هوائية' }; const VEHICLE_ICON = { CAR: 'car', MOTORBIKE: 'bike', BICYCLE: 'bike' }; // Account status → Arabic label const STATUS_LABEL = { APPROVED: 'حساب معتمد', AWAITING_REVIEW: 'قيد المراجعة', PENDING_KYC: 'بانتظار المستندات', SUSPENDED: 'موقوف', REJECTED: 'مرفوض', }; function ProfileScreen({ online, driver, driverName, onLogout }) { // Everything here is the real logged-in driver from /drivers/me. const name = driver?.fullName || driverName || '—'; const phone = driver?.phone || ''; const initial = (name && name.trim()[0]) || '؟'; const vKind = driver?.vehicleKind || null; const vLabel = vKind ? (VEHICLE_LABEL[vKind] || 'مركبة') : null; const vIcon = vKind ? (VEHICLE_ICON[vKind] || 'car') : 'car'; const plate = driver?.vehiclePlate || ''; const color = driver?.vehicleColor || ''; const docs = Array.isArray(driver?.documents) ? driver.documents.length : 0; const statusLabel = STATUS_LABEL[driver?.status] || ''; return (
{/* Big profile card */}
{initial}
{name}
{phone &&
{phone}
} {statusLabel && (
{statusLabel}
)}
{/* Vehicle card — only when a vehicle is on file */} {vKind && (
{vLabel}
{(plate || color) && (
{[plate && `لوحة ${plate}`, color].filter(Boolean).join(' · ')}
)}
)} {/* Menu rows */}
); } function Section({ children }) { return (
{children}
); } function Row({ name, label, detail, warning, tint }) { const tintMap = { success: { bg: 'color-mix(in oklch, var(--success) 12%, transparent)', fg: 'var(--success)' }, danger: { bg: 'color-mix(in oklch, var(--danger) 12%, transparent)', fg: 'var(--danger)' }, }; const t = tint ? tintMap[tint] : null; return (
{label}
{detail &&
{detail}
}
); } Object.assign(window, { OrdersScreen, EarningsScreen, WalletScreen, ProfileScreen, });