diff --git a/dashboard/siro-admin/js/app.js b/dashboard/siro-admin/js/app.js index fb8e0731..c518448a 100644 --- a/dashboard/siro-admin/js/app.js +++ b/dashboard/siro-admin/js/app.js @@ -402,7 +402,7 @@ 'Completed trips (passenger)': 'الرحلات المكتملة (الراكب)', 'Captain phone': 'هاتف الكابتن', 'Captain completed / cancelled': 'الكابتن مكتملة / ملغاة', - 'Pickup': 'نقطة الت Pickup', + 'Pickup': 'نقطة الاستلام', 'Drop-off': 'نقطة النزول', 'Passenger fare': 'أجرة الراكب', 'Captain earning': 'أرباح الكابتن', @@ -464,6 +464,157 @@ 'Search rides by phone number': 'بحث في الرحلات برقم الهاتف', 'Search by captain ID, phone or email': 'بحث بمعرّف الكابتن أو البريد الإلكتروني أو الهاتف', 'Search by passenger ID, phone or email': 'بحث بمعرّف الراكب أو البريد الإلكتروني أو الهاتف', + // API error messages + ' — the endpoint returned HTML, check the API base URL': ' — الـ API أرجع HTML، تحقق من عنوان الخادم', + 'Session rejected by the server': 'الخادم رفض الجلسة', + 'Request failed': 'فشل الطلب', + // Auth + 'your WhatsApp': 'واتسابك', + 'Verification code sent.': 'تم إرسال رمز التحقق.', + 'No token issued.': 'لم يُصدر توكن.', + 'Admin': 'المشرف', + 'Welcome back,': 'مرحباً بعودتك،', + // Load everything + 'No data — check diagnostics': 'لا توجد بيانات — تحقق من التشخيص', + 'Every endpoint failed.': 'فشل كل الـ APIs.', + 'Open "Session & Security" → Run diagnostics.': 'افتح "الجلسة والأمان" ← شغّل التشخيص.', + // Rides table + 'Unknown': 'غير معروف', + 'Unassigned': 'لم يُعيَّن', + 'No rides found for': 'لم تُعثر على رحلات لـ', + 'Search results for': 'نتائج البحث عن', + 'No captain matches': 'لا يوجد كابتن يطابق', + 'No passenger matches': 'لا يوجد راكب يطابق', + 'Unnamed': 'بدون اسم', + // Captain/Passenger profile + 'Captain #': 'كابتن #', + 'Passenger #': 'راكب #', + 'Loading profile…': 'جارٍ تحميل الملف الشخصي…', + 'No profile returned for this record.': 'لم تُرجَع ملفات لهذا السجل.', + // Approvals + 'Set admin': 'تعيين المشرف', + 'to': 'إلى', + 'approved': 'تم اعتماده', + 'rejected': 'تم رفضه', + // Blacklist + 'Enter the phone number of the account to remove.': 'أدخل رقم الهاتف المراد حذفه.', + 'The retyped phone does not match — nothing was deleted.': 'رقم الهاتف المُعاد إدخاله غير متطابق — لم يُحذف شيء.', + 'Removing a captain requires their account ID.': 'حذف الكابتن يتطلب معرّف حسابه.', + 'Permanently delete': 'حذف نهائياً', + 'and blacklist that number?': 'وإضافة الرقم للقائمة السوداء؟', + 'The account is deleted from the database. This cannot be undone.': 'الحساب يُحذف من قاعدة البيانات. لا يمكن التراجع.', + 'Deleting…': 'جارٍ الحذف…', + 'Captain removed and blacklisted.': 'تم حذف الكابتن وإضافته للقائمة السوداء.', + 'Passenger removed and blacklisted.': 'تم حذف الراكب وإضافته للقائمة السوداء.', + 'Remove and blacklist': 'حذف و加入 القائمة السوداء', + 'Delete and blacklist': 'حذف و加入 القائمة السوداء', + // Lift block + 'Enter the blocked phone number.': 'أدخل رقم الهاتف المحظور.', + 'They will be able to register again.': 'سيتمكن من التسجيل مرة أخرى.', + 'Working…': 'جارٍ التنفيذ…', + 'Block lifted.': 'تم رفع الحظر.', + 'Lift block': 'رفع الحظر', + // Campaign + 'Parameters changed since the preview': 'تغيّرت المعايير منذ المعاينة', + 'preview again before launching': 'عاين مرة أخرى قبل الإطلاق', + 'Launch this campaign in': 'إطلاق الحملة في', + 'It creates a discount code valid for 7 days and pushes a notification to every passenger there.': 'ينشئ كود خصم صالحاً لمدة ٧ أيام ويرسل إشعاراً لكل راكب هناك.', + 'This cannot be undone.': 'لا يمكن التراجع.', + 'The AI found no opportunity — launching would still send.': 'لم يجد الذكاء الاصطناعي فرصة — الإطلاق سيرسل على أي حال.', + 'Launch is now enabled.': 'الإطلاق متاح الآن.', + 'Campaign dispatched.': 'تم إرسال الحملة.', + // App versions + 'Version unchanged.': 'الإصدار لم يتغيّر.', + 'Use digits separated by dots, e.g. 1.4.2': 'استخدم أرقاماً مفصولة بنقاط، مثال: 1.4.2', + 'Users on an older build may be prompted or forced to update.': 'قد يُطلب من المستخدمين التحديث أو يُجبرون عليه.', + // Driver docs + 'Loading documents…': 'جارٍ تحميل الوثائق…', + 'Activate captain': 'تفعيل الكابتن', + 'They will be able to accept rides immediately.': 'سيتمكّن من قبول الرحلات فوراً.', + 'WARNING: no documents are on file for this captain.': 'تحذير: لا توجد وثائق مسجّلة لهذا الكابتن.', + 'document(s) reviewed': 'وثيقة/وثائق تمت مراجعتها', + 'Captain activated.': 'تم تفعيل الكابتن.', + // Staff + 'Activate account': 'تفعيل الحساب', + 'They will be able to sign in immediately.': 'سيتمكن من تسجيل الدخول فوراً.', + 'Name and password are required.': 'الاسم وكلمة المرور مطلوبان.', + 'Use a password of at least 8 characters.': 'استخدم كلمة مرور من ٨ أحرف على الأقل.', + 'Only a super admin can create administrator accounts.': 'المدير العام فقط يمكنه إنشاء حسابات مشرفين.', + 'ADMINISTRATOR': 'مدير', + 'customer service': 'خدمة عملاء', + 'Create a account for': 'إنشاء حساب لـ', + 'Administrators can see and change platform data.': 'المشرفون يمكنهم رؤية وتعديل بيانات المنصة.', + 'Customer service staff can view operational data.': 'موظفو خدمة العملاء يمكنهم رؤية البيانات التشغيلية.', + 'Creating…': 'جارٍ الإنشاء…', + 'Staff account created.': 'تم إنشاء حساب الموظف.', + 'Could not create account:': 'تعذّر إنشاء الحساب:', + // Routes + 'approve': 'اعتماد', + 'reject': 'رفض', + 'The route goes live and passengers can ride it.': 'المسار يصبح نشطاً ويمكن للركاب استخدامه.', + 'The organisation will have to resubmit the route.': 'سيتعيّن على المؤسسة إعادة تقديم المسار.', + 'Approve route': 'اعتماد المسار', + 'Reject route': 'رفض المسار', + 'from': 'من', + 'Route': 'المسار', + 'Route approved.': 'تم اعتماد المسار.', + 'Route rejected.': 'تم رفض المسار.', + // Broadcast + 'Broadcasting is restricted to super admins.': 'الإرسال مقتصر على المدير العام فقط.', + 'every captain': 'كل كابتن', + 'every passenger': 'كل راكب', + 'Enter both a title and a message.': 'أدخل عنواناً ونصاً للرسالة.', + 'Send this notification to': 'إرسال هذا الإشعار إلى', + 'on the platform?': 'على المنصة؟', + 'It is delivered immediately and cannot be recalled.': 'يُسلَّم فوراً ولا يمكن استرجاعه.', + 'Sent to': 'أُرسل إلى', + 'at': 'في', + 'Notification delivered to': 'تم تسليم الإشعار إلى', + 'Send failed:': 'فشل الإرسال:', + 'Notification title': 'عنوان الإشعار', + 'Message text': 'نص الرسالة', + // Tariff + 'Tariff': 'التعرفة', + 'row #': 'صف #', + 'Only a super admin can change pricing.': 'المدير العام فقط يمكنه تغيير الأسعار.', + 'Nothing changed on this tariff row.': 'لم يتغيّر شيء في هذا السطر.', + 'Apply these pricing changes to': 'تطبيق تغييرات التسعير على', + 'This takes effect immediately for passengers.': 'يسري فوراً للركاب.', + 'Tariff updated and recorded in the audit log.': 'تم تحديث التعرفة وتسجيلها في سجل العمليات.', + 'Update failed:': 'فشل التحديث:', + // Diagnostics + 'Running…': 'جارٍ التشخيص…', + 'Console build:': 'إصدار المنصة:', + 'MISSING — not signed in': 'غير موجود — لم يُسجَّل الدخول', + 'NETWORK FAILURE —': 'فشل الشبكة —', + '(blocked by CORS, DNS, mixed content, or the host is unreachable)': '(محظور بواسطة CORS أو DNS أو محتوى مختلط أو الخادم غير متاح)', + 'Run diagnostics': 'تشغيل التشخيص', + '(empty response body)': '(استجابة فارغة)', + 'Custom…': 'مخصص…', + // WhatsApp + 'Sending messages is restricted to super admins.': 'إرسال الرسائل مقتصر على المدير العام فقط.', + 'Enter both a recipient and a message.': 'أدخل المستلم والرسالة.', + 'Send this WhatsApp message to': 'إرسال رسالة واتساب إلى', + 'Message sent.': 'تم إرسال الرسالة.', + // Crypto tool + 'This tool is restricted to super admins.': 'هذه الأداة مقتصرة على المدير العام فقط.', + 'Enter both your admin phone and the value.': 'أدخل هاتف المشرف والقيمة المراد فك/تشفيرها.', + // API base + 'API base set to': 'تم تغيير عنوان API إلى', + 'Reloading data…': 'جارٍ إعادة تحميل البيانات…', + 'Diagnostics report copied.': 'تم نسخ تقرير التشخيص.', + 'Copy failed — select the text manually.': 'فشل النسخ — حدد النص يدوياً.', + // Charts + 'No ride data': 'لا توجد بيانات رحلات', + 'TOTAL RIDES': 'إجمالي الرحلات', + // Helpers + 'unrated': 'بدون تقييم', + // Login error + 'The server accepted the request but issued no code and no token.': 'قبل الخادم الطلب لكنه لم يُصدر رمزاً ولا توكن.', + 'Enter the 3-digit code.': 'أدخل الرمز المكوّن من ٣ أرقام.', + 'Verifying…': 'جارٍ التحقق…', + 'Session ended: ': 'انتهت الجلسة: ', + 'n/a': 'غير متاح', }; const t = (key) => (lang === 'ar' ? (AR[key] || key) : key); @@ -710,7 +861,7 @@ function handleApiError(err, context) { console.error(`[${context}]`, err); if (err instanceof ApiError && (err.status === 401 || err.status === 403)) { - signOut(`Session ended: ${err.message}`); + signOut(t('Session ended: ') + err.message); return true; } setConnection('error', t('Data unavailable')); @@ -761,11 +912,11 @@ if (payload?.status === 'otp_required') { pendingPhone = phone; - el.otpPhoneText.textContent = `Enter the 3-digit code sent to ${payload.phone || 'your WhatsApp'}.`; + el.otpPhoneText.textContent = t('Enter the 3-digit code sent to ') + (payload.phone || t('your WhatsApp')) + '.'; el.otpModal.classList.add('active'); el.otpInput.value = ''; el.otpInput.focus(); - toast(payload.message || 'Verification code sent.', 'info'); + toast(payload.message || t('Verification code sent.'), 'info'); } else if (payload?.jwt) { // Trusted-device renewal path — no OTP required. establishSession(payload); @@ -786,11 +937,11 @@ async function onVerifyOtp() { const otp = el.otpInput.value.trim(); if (otp.length < 3) { - toast('Enter the 3-digit code.', 'warning'); + toast(t('Enter the 3-digit code.'), 'warning'); return; } - busy(el.submitOtpBtn, true, 'Verifying…'); + busy(el.submitOtpBtn, true, t('Verifying…')); try { const form = new FormData(); form.append('otp', otp); @@ -798,7 +949,7 @@ form.append('aud', 'admin'); const payload = await api('/Admin/auth/verify_login.php', { method: 'POST', body: form, auth: false }); - if (!payload?.jwt) throw new ApiError('No token issued.', 0); + if (!payload?.jwt) throw new ApiError(t('No token issued.'), 0); el.otpModal.classList.remove('active'); establishSession(payload); @@ -823,7 +974,7 @@ }; localStorage.setItem(SESSION_KEY, JSON.stringify(session)); showConsole(); - toast(`Welcome back, ${session.name}.`, 'success'); + toast(t('Welcome back,') + ' ' + session.name + '.', 'success'); loadEverything(); } @@ -881,8 +1032,8 @@ el.lastUpdated.textContent = t('Updated') + ' ' + new Date().toLocaleTimeString(); } else if (session) { setConnection('error', t('No data from API')); - el.lastUpdated.textContent = 'No data — check diagnostics'; - toast('Every endpoint failed. Open “Session & Security” → Run diagnostics.', 'danger'); + el.lastUpdated.textContent = t('No data — check diagnostics'); + toast(t('Every endpoint failed.') + ' ' + t('Open “Session & Security” → Run diagnostics.'), 'danger'); } }); } @@ -1052,8 +1203,8 @@ el.ridesTableBody.innerHTML = rides.map((r) => `
- ${lang === 'ar' ? 'يُنشئ حساب دخول لأدوات سير للمشرفين. اختر كلمة المرور مع العضو الجديد، أو دعهم يغيّروها عند أول تسجيل دخول —她是 مشفرة ولا يمكن قراءتها.' : 'Creates a login for the Siro admin tools. Choose the password with the new member present, or have them change it at first sign-in — it is stored hashed and cannot be read back.'} + ${lang === 'ar' ? 'يُنشئ حساب دخول لأدوات سير للمشرفين. اختر كلمة المرور مع العضو الجديد، أو دعهم يغيّروها عند أول تسجيل دخول — هي مشفرة ولا يمكن قراءتها.' : 'Creates a login for the Siro admin tools. Choose the password with the new member present, or have them change it at first sign-in — it is stored hashed and cannot be read back.'}