Update: 2026-07-25 18:05:37
This commit is contained in:
+171
-20
@@ -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) => `
|
||||
<tr>
|
||||
<td><strong>#${esc(r.id)}</strong></td>
|
||||
<td>${esc(r.passenger_full_name || 'Unknown')}</td>
|
||||
<td>${esc(r.driver_full_name || 'Unassigned')}</td>
|
||||
<td>${esc(r.passenger_full_name || t('Unknown'))}</td>
|
||||
<td>${esc(r.driver_full_name || t('Unassigned'))}</td>
|
||||
<td class="route-cell">${routeCell(r)}</td>
|
||||
<td><strong>${fmtMoney(rideFare(r))}</strong></td>
|
||||
<td><span class="badge ${badgeClass(r.status)}">${esc(labelStatus(r.status))}</span></td>
|
||||
@@ -1147,8 +1298,8 @@
|
||||
const payload = await api('/Admin/AdminCaptain/getCaptainDetailsByEmailOrIDOrPhone.php', { params });
|
||||
const rows = normaliseRows(payload);
|
||||
renderDrivers(rows, rows.length);
|
||||
el.driversMeta.textContent = `Search results for “${term}”`;
|
||||
if (!rows.length) tableMessage(el.driversTableBody, 8, `No captain matches “${term}”.`);
|
||||
el.driversMeta.textContent = t('Search results for') + ` “${term}”`;
|
||||
if (!rows.length) tableMessage(el.driversTableBody, 8, t('No captain matches') + ` “${term}”.`);
|
||||
} catch (err) {
|
||||
if (!handleApiError(err, 'captain-lookup')) tableMessage(el.driversTableBody, 8, err.message, true);
|
||||
}
|
||||
@@ -1163,7 +1314,7 @@
|
||||
const payload = await api('/Admin/getPassengerbyEmail.php', { params });
|
||||
const rows = normaliseRows(payload);
|
||||
renderPassengers(rows);
|
||||
if (!rows.length) tableMessage(el.passengersTableBody, 8, `No passenger matches “${term}”.`);
|
||||
if (!rows.length) tableMessage(el.passengersTableBody, 8, t('No passenger matches') + ` “${term}”.`);
|
||||
} catch (err) {
|
||||
if (!handleApiError(err, 'passenger-lookup')) tableMessage(el.passengersTableBody, 8, err.message, true);
|
||||
}
|
||||
@@ -1180,12 +1331,12 @@
|
||||
|
||||
// ── Profile drawers ──────────────────────────────────────────────────────
|
||||
async function openCaptainProfile(id) {
|
||||
openProfile(`Captain #${id}`, () =>
|
||||
openProfile(t('Captain #') + id, () =>
|
||||
api('/Admin/AdminCaptain/getCaptainDetailsById.php', { params: { driver_id: id } }));
|
||||
}
|
||||
|
||||
async function openPassengerProfile(id) {
|
||||
openProfile(`Passenger #${id}`, () =>
|
||||
openProfile(t('Passenger #') + id, () =>
|
||||
api('/Admin/getPassengerDetailsByPassengerID.php', { params: { passengerID: id } }));
|
||||
}
|
||||
|
||||
@@ -1196,13 +1347,13 @@
|
||||
<h3>${esc(title)}</h3>
|
||||
<button class="btn-icon" onclick="closeModal()"><i class="ph ph-x"></i></button>
|
||||
</div>
|
||||
<div class="table-msg">Loading profile…</div>`;
|
||||
<div class="table-msg">${t('Loading profile…')}</div>`;
|
||||
$('detailsModal').classList.add('active');
|
||||
|
||||
try {
|
||||
const payload = await fetcher();
|
||||
const record = normaliseRows(payload)[0];
|
||||
if (!record) throw new ApiError('No profile returned for this record.', 0);
|
||||
if (!record) throw new ApiError(t('No profile returned for this record.'), 0);
|
||||
|
||||
const entries = Object.entries(record)
|
||||
.filter(([k]) => !/token|password|fingerprint/i.test(k))
|
||||
@@ -2585,7 +2736,7 @@
|
||||
<div class="card">
|
||||
<div class="card-header"><h3 class="card-title">${t('Add a staff account')}</h3></div>
|
||||
<p class="card-note">
|
||||
${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.'}
|
||||
</p>
|
||||
<div class="tariff-grid">
|
||||
<label class="tariff-field">
|
||||
|
||||
Reference in New Issue
Block a user