// 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) => (
))}
)}
{/* 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 (
);
}
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) => (
))}
);
}
// ═══════════════════════════════════════════════════════════
// 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,
});