(function () { const supportedLanguages = ["en", "ar", "tr", "id"]; const languageLabels = { en: "EN", ar: "AR", tr: "TR", id: "ID" }; const API_BASE_URL = "https://jvapi.haiyihy.com"; const fallbackMessages = { en: { document_title: "BD Leader Center", page_label: "BD Leader Center", back: "Back", title: "BD Leader Center", language_button_aria: "Change language", role: "BD Leader", uid_prefix: "UID:", my_available_salary: "Salary", details: "Withdraw/Exchange", bd_number: "BD number", team_salary: "Team salary", bd_leader_history: "Salary History", agency_list_link: "Agency List", bd_list_link: "BD List", invite_become_agent: "Invite Agent", invite_become_bd: "Invite BD", invite_agent_title: "Invite Agent", invite_bd_title: "Invite BD", enter_user_id: "Enter User ID", search: "Search", searching: "Searching...", invite: "Invite", inviting: "Inviting...", cancel: "Cancel", success: "Success", bd_list_preview: "BD List", bd_list_preview_subtitle: "Current settlement period", more: "More", total_income: "Total income", previous_period_income: "Previous period income", salary: "Salary", agencies: "Agencies", agency_number: "Agency number", team_recharge: "Team recharge", recharge: "Recharge", host: "Host", loading_data: "Loading...", no_data: "No data", in_progress: "In Progress", pending: "Pending", completed: "Completed", out_of_account: "Out of account", no_users_found: "No users found", invitation_submitted: "Invitation submitted", application_cancelled: "Application cancelled", something_went_wrong: "Something went wrong", failed_to_load_invites: "Failed to load invitations", cancellation_failed_try_again: "Cancellation failed. Try again.", close: "Close", failed_to_load: "Failed to load data", failed_to_load_history: "Failed to load history" }, ar: { document_title: "مركز قائد BD", page_label: "مركز قائد BD", back: "رجوع", title: "مركز قائد BD", language_button_aria: "تغيير اللغة", role: "قائد BD", uid_prefix: "UID:", my_available_salary: "الراتب", details: "سحب/تبديل", bd_number: "عدد BD", team_salary: "راتب الفريق", bd_leader_history: "سجل الراتب", agency_list_link: "قائمة الوكالات", bd_list_link: "قائمة BD", invite_become_agent: "دعوة وكيل", invite_become_bd: "دعوة BD", invite_agent_title: "دعوة وكيل", invite_bd_title: "دعوة BD", enter_user_id: "أدخل معرف المستخدم", search: "بحث", searching: "جار البحث...", invite: "دعوة", inviting: "جار الدعوة...", cancel: "إلغاء", success: "نجاح", bd_list_preview: "قائمة BD", bd_list_preview_subtitle: "فترة التسوية الحالية", more: "المزيد", total_income: "إجمالي الدخل", previous_period_income: "دخل الفترة السابقة", salary: "الراتب", agencies: "الوكالات", loading_data: "جار التحميل...", no_data: "لا توجد بيانات", in_progress: "قيد التنفيذ", pending: "قيد الانتظار", completed: "مكتمل", out_of_account: "خارج الحساب", no_users_found: "لم يتم العثور على مستخدمين", invitation_submitted: "تم إرسال الدعوة", application_cancelled: "تم إلغاء الطلب", something_went_wrong: "حدث خطأ ما", failed_to_load_invites: "فشل تحميل الدعوات", cancellation_failed_try_again: "فشل الإلغاء. حاول مرة أخرى.", close: "إغلاق", failed_to_load: "فشل تحميل البيانات", failed_to_load_history: "فشل تحميل السجل" }, tr: { document_title: "BD Leader Merkezi", page_label: "BD Leader Merkezi", back: "Geri", title: "BD Leader Merkezi", language_button_aria: "Dili değiştir", role: "BD Leader", uid_prefix: "UID:", my_available_salary: "Maaş", details: "Withdraw/Exchange", bd_number: "BD sayısı", team_salary: "Takım maaşı", bd_leader_history: "Maaş Geçmişi", agency_list_link: "Ajans listesi", bd_list_link: "BD Listesi", invite_become_agent: "Ajans Davet Et", invite_become_bd: "BD Davet Et", invite_agent_title: "Ajans Davet Et", invite_bd_title: "BD Davet Et", enter_user_id: "Kullanıcı ID gir", search: "Ara", searching: "Aranıyor...", invite: "Davet et", inviting: "Davet ediliyor...", cancel: "İptal", success: "Başarılı", bd_list_preview: "BD Listesi", bd_list_preview_subtitle: "Mevcut ödeme dönemi", more: "Daha fazla", total_income: "Toplam gelir", previous_period_income: "Önceki dönem geliri", salary: "Maaş", agencies: "Ajanslar", loading_data: "Yükleniyor...", no_data: "Veri yok", in_progress: "Devam ediyor", pending: "Beklemede", completed: "Tamamlandı", out_of_account: "Hesap dışı", no_users_found: "Kullanıcı bulunamadı", invitation_submitted: "Davet gönderildi", application_cancelled: "Başvuru iptal edildi", something_went_wrong: "Bir şeyler ters gitti", failed_to_load_invites: "Davetler yüklenemedi", cancellation_failed_try_again: "İptal başarısız. Tekrar deneyin.", close: "Kapat", failed_to_load: "Veri yüklenemedi", failed_to_load_history: "Geçmiş yüklenemedi" }, id: { document_title: "Pusat BD Leader", page_label: "Pusat BD Leader", back: "Kembali", title: "Pusat BD Leader", language_button_aria: "Ubah bahasa", role: "BD Leader", uid_prefix: "UID:", my_available_salary: "Gaji", details: "Withdraw/Exchange", bd_number: "Jumlah BD", team_salary: "Gaji tim", bd_leader_history: "Riwayat Gaji", agency_list_link: "Daftar agensi", bd_list_link: "Daftar BD", invite_become_agent: "Invite Agent", invite_become_bd: "Invite BD", invite_agent_title: "Invite Agent", invite_bd_title: "Invite BD", enter_user_id: "Masukkan ID Pengguna", search: "Cari", searching: "Mencari...", invite: "Undang", inviting: "Mengundang...", cancel: "Batal", success: "Berhasil", bd_list_preview: "Daftar BD", bd_list_preview_subtitle: "Periode settlement saat ini", more: "Lainnya", total_income: "Total pendapatan", previous_period_income: "Pendapatan periode sebelumnya", salary: "Gaji", agencies: "Agensi", loading_data: "Memuat...", no_data: "Tidak ada data", in_progress: "Sedang diproses", pending: "Menunggu", completed: "Selesai", out_of_account: "Di luar akun", no_users_found: "Pengguna tidak ditemukan", invitation_submitted: "Undangan terkirim", application_cancelled: "Pengajuan dibatalkan", something_went_wrong: "Terjadi kesalahan", failed_to_load_invites: "Gagal memuat undangan", cancellation_failed_try_again: "Gagal membatalkan. Coba lagi.", close: "Tutup", failed_to_load: "Gagal memuat data", failed_to_load_history: "Gagal memuat riwayat" } }; const params = readURLParams(); let currentLang = normalizeLanguage(params.get("lang")); let currentMessages = fallbackMessages[currentLang] || fallbackMessages.en; const state = { profile: null, identity: null, teamEntry: null, balanceTotal: {}, bill: {}, agencyBill: null, history: null, historyMore: null, teamList: { type: "bd", loading: false, error: "" }, invite: { mode: "agent", invited: [], searched: [], showSearch: false, query: "", locked: false, searching: false, statusText: "", statusType: "" } }; function readURLParams() { const result = new URLSearchParams(window.location.search); const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : ""; const hashParams = new URLSearchParams(hashQuery); hashParams.forEach((value, key) => { if (!result.has(key)) result.set(key, value); }); return result; } function readRawParam(name) { const queries = [window.location.search.replace(/^\?/, "")]; if (window.location.hash.includes("?")) queries.push(window.location.hash.split("?")[1]); for (const query of queries) { for (const part of query.split("&")) { if (!part) continue; const equalIndex = part.indexOf("="); const rawKey = equalIndex >= 0 ? part.slice(0, equalIndex) : part; const rawValue = equalIndex >= 0 ? part.slice(equalIndex + 1) : ""; let decodedKey = ""; try { decodedKey = decodeURIComponent(rawKey); } catch (error) { continue; } if (decodedKey !== name) continue; try { return decodeURIComponent(rawValue.replace(/\+/g, "%2B")); } catch (error) { return rawValue; } } } return ""; } function normalizeLanguage(lang) { const value = String(lang || "en").toLowerCase(); if (value.startsWith("ar")) return "ar"; if (value.startsWith("tr")) return "tr"; if (value.startsWith("id")) return "id"; return supportedLanguages.includes(value) ? value : "en"; } function message(key, fallback = "") { return currentMessages[key] || fallbackMessages.en[key] || fallback || key; } function renderMessages(messages) { document.querySelectorAll("[data-i18n]").forEach((node) => { const value = messages[node.dataset.i18n] || fallbackMessages.en[node.dataset.i18n]; if (value) node.textContent = value; }); document.querySelectorAll("[data-i18n-aria]").forEach((node) => { const value = messages[node.dataset.i18nAria] || fallbackMessages.en[node.dataset.i18nAria]; if (value) node.setAttribute("aria-label", value); }); document.querySelectorAll("[data-i18n-placeholder]").forEach((node) => { const value = messages[node.dataset.i18nPlaceholder] || fallbackMessages.en[node.dataset.i18nPlaceholder]; if (value) node.setAttribute("placeholder", value); }); } function apiBaseURL() { const override = window.HYAPP_API_BASE_URL || params.get("apiBase"); if (override) return String(override).replace(/\/+$/, ""); return API_BASE_URL.replace(/\/+$/, ""); } function normalizeAuthorization(token) { const value = String(token || "").trim(); if (!value) return ""; return /^bearer\s+/i.test(value) ? value : `Bearer ${value}`; } function authHeadersFromToken(token) { return { Authorization: normalizeAuthorization(token), "req-lang": currentLang, "req-client": "H5", "req-version": "V2", "req-zone": Intl.DateTimeFormat().resolvedOptions().timeZone || "Asia/Shanghai", "req-app-intel": "version=1.0.0;build=1;model=H5;sysVersion=Web;channel=Web", "req-sys-origin": "origin=ATYOU;originChild=ATYOU" }; } async function requestJSON(path, options = {}) { const token = readRawParam("token"); if (!token) throw new Error("Missing token"); const headers = authHeadersFromToken(token); const request = { method: options.method || "GET", cache: "no-store", headers }; if (options.body !== undefined) { headers["Content-Type"] = "application/json"; request.body = JSON.stringify(options.body); } const response = await fetch(new URL(path, `${apiBaseURL()}/`).toString(), request); const data = await response.json().catch(() => ({})); if (!response.ok || data.status === false) { const error = new Error(data.errorMsg || data.message || `Request failed: ${response.status}`); error.response = data; throw error; } return data.body ?? data.data ?? null; } function setLoading(isLoading) { const root = document.querySelector(".bd-leader-center"); const mask = document.querySelector("#loadingMask"); if (root) root.dataset.loading = isLoading ? "true" : "false"; if (mask) mask.hidden = !isLoading; } function updateModalLock() { const hasOpen = Array.from(document.querySelectorAll(".history-modal, .details-modal, .invite-modal")).some((modal) => !modal.hidden); document.body.classList.toggle("modal-open", hasOpen); } function setModalOpen(selector, open) { const modal = document.querySelector(selector); if (!modal) return; modal.hidden = !open; updateModalLock(); } function showToast(text) { const toast = document.querySelector("#toast"); if (!toast) return; toast.textContent = text || ""; toast.hidden = !text; window.clearTimeout(showToast.timer); showToast.timer = window.setTimeout(() => { toast.hidden = true; }, 2500); } function formatMoney(value) { const number = Number(value); if (!Number.isFinite(number)) return "0.00"; return number.toFixed(2); } function formatCount(value) { if (value === null || value === undefined || value === "") return "0"; const number = Number(value); if (Number.isFinite(number) && Number.isInteger(number)) return String(number); if (Number.isFinite(number)) return String(Number(number.toFixed(2))); return String(value); } function pickSpecialAccount(profile) { return profile?.ownSpecialId?.account || profile?.specialId?.account || profile?.specialAccount || profile?.shortAccount || profile?.shortId || ""; } function pickProfileName(profile) { return profile?.userNickname || profile?.name || "-"; } function pickProfileAccount(profile) { return pickSpecialAccount(profile) || profile?.account || profile?.id || "-"; } function renderAvatar(profile) { const image = document.querySelector("#profileAvatar"); const fallback = document.querySelector("#profileAvatarFallback"); if (!image || !fallback) return; const name = pickProfileName(profile); const avatar = profile?.userAvatar || profile?.avatar || ""; fallback.textContent = String(name || "B").slice(0, 1).toUpperCase() || "B"; if (!avatar) { image.hidden = true; image.removeAttribute("src"); fallback.hidden = false; return; } image.alt = name; fallback.hidden = true; image.hidden = false; image.onerror = () => { image.hidden = true; image.removeAttribute("src"); fallback.hidden = false; }; image.src = avatar; } function renderProfile(profile = state.profile) { const name = pickProfileName(profile); const account = pickProfileAccount(profile); const nameNode = document.querySelector("#profileName"); const uidNode = document.querySelector("#profileUid"); if (nameNode) nameNode.textContent = name; if (uidNode) uidNode.textContent = `${message("uid_prefix")} ${account}`.trim(); renderAvatar(profile); } function renderBalance() { const balance = state.balanceTotal?.availableBalance ?? state.balanceTotal?.balance ?? 0; const node = document.querySelector("#availableBalance"); if (node) node.textContent = formatMoney(balance); } function balanceAmount(balance) { const value = Number(balance?.availableBalance ?? balance?.balance ?? balance?.currentSalary ?? 0); return Number.isFinite(value) ? value : 0; } function renderBill() { const bill = state.bill || {}; const members = Array.isArray(bill.memberBillList) ? bill.memberBillList : []; const bdNumber = document.querySelector("#bdNumber"); const teamSalary = document.querySelector("#teamSalary"); if (bdNumber) bdNumber.textContent = formatCount(bill.memberCount ?? bill.bdNumber ?? members.length); if (teamSalary) teamSalary.textContent = formatMoney(bill.totalSalary ?? bill.teamSalary ?? 0); renderMembers(members); } function createFallbackAvatar(name) { const span = document.createElement("span"); span.className = "member-fallback"; span.textContent = String(name || "B").slice(0, 1).toUpperCase() || "B"; return span; } function createMemberRow(item, options = {}) { const type = options.type || "bd"; const name = item?.agentName || item?.bdUserName || "-"; const account = item?.agentAccount || item?.account || "-"; const row = document.createElement("article"); row.className = "member-row"; const avatar = document.createElement("div"); avatar.className = "member-avatar"; const avatarUrl = item?.agentAvatar || item?.bdUserAvatar || ""; if (avatarUrl) { const image = document.createElement("img"); image.src = avatarUrl; image.alt = ""; image.addEventListener("error", () => { avatar.replaceChildren(createFallbackAvatar(name)); }); avatar.appendChild(image); } else { avatar.appendChild(createFallbackAvatar(name)); } const main = document.createElement("div"); main.className = "member-main"; const nameNode = document.createElement("div"); nameNode.className = "member-name"; nameNode.textContent = name; const metaNode = document.createElement("div"); metaNode.className = "member-meta"; metaNode.textContent = `${message("uid_prefix")} ${account}`.trim(); main.append(nameNode, metaNode); const side = document.createElement("div"); side.className = "member-side"; const sideLines = type === "agency" ? [ `${message("team_salary")}: $${formatMoney(item?.teamSalaryAmount || 0)}`, `${message("recharge")}: $${formatMoney(item?.teamRechargeAmount || 0)}`, `${message("host")}: ${formatCount(item?.teamMemberCount || 0)}` ] : [ `${message("team_salary")}: $${formatMoney(item?.teamSalaryAmount || 0)}`, `${message("agencies")}: ${formatCount(item?.teamMemberCount || item?.agencyCount || 0)}` ]; sideLines.forEach((line) => { const itemNode = document.createElement("div"); itemNode.textContent = line; side.appendChild(itemNode); }); row.append(avatar, main, side); return row; } function replaceChildren(node, children) { while (node.firstChild) node.removeChild(node.firstChild); children.forEach((child) => node.appendChild(child)); } function renderMembers(members) { const list = document.querySelector("#bdMemberList"); if (!list) return; const preview = members.slice(0, 5); if (!preview.length) { const empty = document.createElement("div"); empty.className = "empty-state"; empty.textContent = message("no_data"); replaceChildren(list, [empty]); return; } replaceChildren(list, preview.map(createMemberRow)); } function normalizeTeamListType(type) { return type === "agency" ? "agency" : "bd"; } function teamListBill(type = state.teamList.type) { return normalizeTeamListType(type) === "agency" ? state.agencyBill : state.bill; } function hasTeamListData(data) { return Boolean(data && (Array.isArray(data.memberBillList) || data.billTitle || data.memberCount !== undefined)); } function createSummaryCell(label, value) { const cell = document.createElement("div"); const labelNode = document.createElement("span"); labelNode.textContent = label; const valueNode = document.createElement("strong"); valueNode.textContent = value; cell.append(labelNode, valueNode); return cell; } function renderTeamListCard() { const type = normalizeTeamListType(state.teamList.type); const data = teamListBill(type) || {}; const members = Array.isArray(data.memberBillList) ? data.memberBillList : []; const summary = document.querySelector("#teamListSummary"); const content = document.querySelector("#teamListContent"); document.querySelectorAll("[data-list-tab]").forEach((button) => { const active = normalizeTeamListType(button.dataset.listTab) === type; button.classList.toggle("is-active", active); button.setAttribute("aria-selected", String(active)); }); if (summary) { const cells = type === "agency" ? [ createSummaryCell(message("agency_number"), formatCount(data.memberCount ?? members.length)), createSummaryCell(message("team_salary"), `$${formatMoney(data.totalSalary || 0)}`), createSummaryCell(message("team_recharge"), `$${formatMoney(data.totalRecharge || 0)}`) ] : [ createSummaryCell(message("bd_number"), formatCount(data.memberCount ?? members.length)), createSummaryCell(message("team_salary"), `$${formatMoney(data.totalSalary || 0)}`) ]; replaceChildren(summary, cells); } if (!content) return; if (state.teamList.loading) { const loading = document.createElement("div"); loading.className = "empty-state"; loading.textContent = message("loading_data"); replaceChildren(content, [loading]); return; } if (state.teamList.error) { const error = document.createElement("div"); error.className = "empty-state"; error.textContent = state.teamList.error; replaceChildren(content, [error]); return; } if (!members.length) { const empty = document.createElement("div"); empty.className = "empty-state"; empty.textContent = message("no_data"); replaceChildren(content, [empty]); return; } replaceChildren(content, members.map((item) => createMemberRow(item, { type }))); } function normalizeStatus(status) { if (status === "UNPAID" || status === "In Progress" || status === message("in_progress")) return message("in_progress"); if (status === "Pending" || status === message("pending")) return message("pending"); if (status === "SETTLED" || status === "PAY_OUT" || status === "Completed" || status === message("completed")) return message("completed"); if (status === "HANG_UP" || status === "Out of account" || status === "Out Of Account" || status === message("out_of_account")) return message("out_of_account"); return status || ""; } function statusClass(status) { const value = normalizeStatus(status); if (value === message("completed")) return "is-completed"; if (value === message("pending")) return "is-pending"; if (value === message("out_of_account")) return "is-out"; return "is-progress"; } function isCompletedStatus(status) { return normalizeStatus(status) === message("completed"); } function createHistoryCell(label, value) { const cell = document.createElement("div"); cell.className = "history-cell"; const labelNode = document.createElement("span"); labelNode.textContent = label; const valueNode = document.createElement("strong"); valueNode.textContent = value; cell.append(labelNode, valueNode); return cell; } function createHistoryRow(item) { const row = document.createElement("article"); row.className = `history-row ${statusClass(item?.statusText)}`; const status = document.createElement("div"); status.className = "status-tag"; status.textContent = normalizeStatus(item?.statusText); const title = document.createElement("div"); title.className = "history-title"; title.textContent = item?.billTitle || "-"; const grid = document.createElement("div"); grid.className = "history-grid"; grid.append( createHistoryCell(message("bd_number"), formatCount(item?.bdNumber || 0)), createHistoryCell(message("team_salary"), `$${formatMoney(item?.teamSalaryAmount || 0)}`) ); row.append(status, title, grid); if (isCompletedStatus(item?.statusText)) { const more = document.createElement("button"); more.className = "more-button"; more.type = "button"; more.textContent = message("more"); more.addEventListener("click", () => showHistoryMore(item)); row.appendChild(more); } return row; } function renderHistory() { const history = state.history || {}; const total = document.querySelector("#historyTotalIncome"); const previous = document.querySelector("#historyPreviousIncome"); const list = document.querySelector("#historyList"); if (total) total.textContent = `$${formatMoney(history.totalIncome || 0)}`; if (previous) previous.textContent = `$${formatMoney(history.previousPeriodIncome || 0)}`; if (!list) return; const items = Array.isArray(history.incomeDetails) ? history.incomeDetails : []; if (!items.length) { const empty = document.createElement("div"); empty.className = "empty-state"; empty.textContent = message("no_data"); replaceChildren(list, [empty]); return; } replaceChildren(list, items.map(createHistoryRow)); } const digitMaps = [ ["\u0660", "0"], ["\u0661", "1"], ["\u0662", "2"], ["\u0663", "3"], ["\u0664", "4"], ["\u0665", "5"], ["\u0666", "6"], ["\u0667", "7"], ["\u0668", "8"], ["\u0669", "9"], ["\u06f0", "0"], ["\u06f1", "1"], ["\u06f2", "2"], ["\u06f3", "3"], ["\u06f4", "4"], ["\u06f5", "5"], ["\u06f6", "6"], ["\u06f7", "7"], ["\u06f8", "8"], ["\u06f9", "9"], ["\u09e6", "0"], ["\u09e7", "1"], ["\u09e8", "2"], ["\u09e9", "3"], ["\u09ea", "4"], ["\u09eb", "5"], ["\u09ec", "6"], ["\u09ed", "7"], ["\u09ee", "8"], ["\u09ef", "9"] ]; const monthNames = new Map(Object.entries({ january: 1, jan: 1, "يناير": 1, "كانون الثاني": 1, ocak: 1, "জানুয়ারি": 1, february: 2, feb: 2, "فبراير": 2, "شباط": 2, subat: 2, "şubat": 2, "ফেব্রুয়ারি": 2, march: 3, mar: 3, "مارس": 3, "آذار": 3, mart: 3, "মার্চ": 3, april: 4, apr: 4, "أبريل": 4, "ابريل": 4, "نيسان": 4, nisan: 4, "এপ্রিল": 4, may: 5, "مايو": 5, "أيار": 5, mayis: 5, "mayıs": 5, "মে": 5, june: 6, jun: 6, "يونيو": 6, "حزيران": 6, haziran: 6, "জুন": 6, july: 7, jul: 7, "يوليو": 7, "تموز": 7, temmuz: 7, "জুলাই": 7, august: 8, aug: 8, "أغسطس": 8, "اغسطس": 8, "آب": 8, agustos: 8, "ağustos": 8, "আগস্ট": 8, september: 9, sep: 9, "سبتمبر": 9, "أيلول": 9, eylul: 9, "eylül": 9, "সেপ্টেম্বর": 9, october: 10, oct: 10, "أكتوبر": 10, "اكتوبر": 10, "تشرين الأول": 10, ekim: 10, "অক্টোবর": 10, november: 11, nov: 11, "نوفمبر": 11, "تشرين الثاني": 11, kasim: 11, "kasım": 11, "নভেম্বর": 11, december: 12, dec: 12, "ديسمبر": 12, "كانون الأول": 12, aralik: 12, "aralık": 12, "ডিসেম্বর": 12 })); function pad2(value) { return String(value).padStart(2, "0"); } function formatBillBelong(year, month, day, time) { const monthNumber = Number(month); const dayNumber = Number(day); if (monthNumber < 1 || monthNumber > 12 || dayNumber < 1 || dayNumber > 31) return ""; return `${year}-${pad2(monthNumber)}-${pad2(dayNumber)}T${time}`; } function prefersDayMonthOrder() { return currentLang === "ar" || currentLang === "tr" || currentLang === "id"; } function formatNumericBillBelong(year, firstPart, secondPart, time) { const first = Number(firstPart); const second = Number(secondPart); if (first > 12 && second <= 12) return formatBillBelong(year, second, first, time); if (second > 12 && first <= 12) return formatBillBelong(year, first, second, time); return prefersDayMonthOrder() ? formatBillBelong(year, second, first, time) : formatBillBelong(year, first, second, time); } function normalizeDateText(value) { return digitMaps.reduce((textValue, [from, to]) => textValue.split(from).join(to), String(value || "")) .replace(/&/gi, "&") .replace(/&(?:rlm|lrm);/gi, "") .replace(/ /gi, " ") .replace(/[\u061c\u200e\u200f]/g, "") .replace(/[،]/g, ",") .replace(/\s+/g, " ") .trim(); } function normalizeTime(value, markerText = "") { const match = String(value || "").match(/(\d{1,2}):(\d{2})(?::(\d{2}))?/); if (!match) return "00:00:00"; let hour = Number(match[1]); const marker = markerText.toLowerCase(); const isPm = /\bpm\b|مساء|(?:^|\s)م(?:\s|$)/.test(marker); const isAm = /\bam\b|صباح|(?:^|\s)ص(?:\s|$)/.test(marker); if (isPm && hour < 12) hour += 12; if (isAm && hour === 12) hour = 0; return `${pad2(hour)}:${match[2]}:${match[3] || "00"}`; } function normalizeBillBelong(value) { const textValue = normalizeDateText(value); if (!textValue) return ""; if (/^\d{6,8}$/.test(textValue)) return textValue; const isoMatch = textValue.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?/); if (isoMatch) { return formatBillBelong(isoMatch[1], isoMatch[2], isoMatch[3], `${isoMatch[4] || "00"}:${isoMatch[5] || "00"}:${isoMatch[6] || "00"}`); } const compactDateTime = textValue.match(/^(\d{4})(\d{2})(\d{2})(?:\D?(\d{2})(\d{2})(\d{2})?)?$/); if (compactDateTime) { return formatBillBelong(compactDateTime[1], compactDateTime[2], compactDateTime[3], `${compactDateTime[4] || "00"}:${compactDateTime[5] || "00"}:${compactDateTime[6] || "00"}`); } const numericDate = textValue.match(/\b(?:(\d{4})\s*[./-]\s*(\d{1,2})\s*[./-]\s*(\d{1,2})|(\d{1,2})\s*[./-]\s*(\d{1,2})\s*[./-]\s*(\d{4}))\b/); if (numericDate) { const time = normalizeTime(textValue, textValue); if (numericDate[1]) return formatBillBelong(numericDate[1], numericDate[2], numericDate[3], time); return formatNumericBillBelong(numericDate[6], numericDate[4], numericDate[5], time); } const lowerText = textValue.toLowerCase(); let month = 0; for (const [name, valueForMonth] of monthNames) { if (lowerText.includes(name)) { month = valueForMonth; break; } } const yearMatch = textValue.match(/\b(20\d{2})\b/); if (month && yearMatch) { const withoutYear = textValue.replace(yearMatch[1], ""); const dayMatch = withoutYear.match(/\b([0-3]?\d)\b/); if (dayMatch) { return formatBillBelong(yearMatch[1], month, dayMatch[1], normalizeTime(textValue, textValue)); } } return ""; } function historyMorePath(source) { const normalized = normalizeBillBelong(source?.billBelong ?? source?.period ?? source?.billTitle ?? source); return normalized ? `/team/bd/leader/history/more?billBelong=${encodeURIComponent(normalized)}` : ""; } function createDetailsCell(label, value) { const cell = document.createElement("div"); cell.className = "details-cell"; const labelNode = document.createElement("span"); labelNode.textContent = label; const valueNode = document.createElement("strong"); valueNode.textContent = value; cell.append(labelNode, valueNode); return cell; } function renderDetails() { const data = state.historyMore || {}; const title = document.querySelector("#detailsTitle"); const summary = document.querySelector("#detailsSummary"); const list = document.querySelector("#detailsList"); if (title) title.textContent = data.period || "-"; if (summary) { replaceChildren(summary, [ createDetailsCell(message("team_salary"), `$${formatMoney(data.teamSalary || 0)}`), createDetailsCell(message("bd_number"), formatCount(data.bdNumber || 0)) ]); } if (!list) return; const members = Array.isArray(data.memberDetails) ? data.memberDetails : []; if (!members.length) { const empty = document.createElement("div"); empty.className = "empty-state"; empty.textContent = message("no_data"); replaceChildren(list, [empty]); return; } replaceChildren(list, members.map((item) => { const row = createMemberRow({ agentName: item.bdUserName, agentAvatar: item.bdUserAvatar, agentAccount: item.account, teamSalaryAmount: item.salary, teamMemberCount: item.agencyCount }); row.classList.add("details-row"); return row; })); } function inviteConfig(mode = state.invite.mode) { if (mode === "bd") { return { titleKey: "invite_bd_title", list: "/team/bd/invite-bd-message-own", invite: "/team/bd/invite-bd", cancel: (id) => `/team/bd/invite-bd-cancel/${encodeURIComponent(id)}` }; } return { titleKey: "invite_agent_title", list: "/team/bd/invite-message-own", invite: "/team/bd/invite-agent", cancel: (id) => `/team/bd/invite-agent-cancel/${encodeURIComponent(id)}` }; } function normalizeInviteMode(mode) { return mode === "bd" ? "bd" : "agent"; } function inviteItemProfile(item) { return item?.userProfile || item?.profile || item?.inviteUserProfile || item || {}; } function inviteProfileId(profile) { return String(profile?.id || profile?.userId || ""); } function sameInviteProfile(left, right) { const leftId = inviteProfileId(left); const rightId = inviteProfileId(right); if (leftId && rightId) return leftId === rightId; const leftAccount = String(left?.account || pickSpecialAccount(left) || ""); const rightAccount = String(right?.account || pickSpecialAccount(right) || ""); return Boolean(leftAccount && rightAccount && leftAccount === rightAccount); } function inviteStatusType(status) { const value = String(status ?? "").toUpperCase(); if (status === 0 || value === "0" || value === "PENDING") return "pending"; if (status === 1 || value === "1" || value === "SUCCESS" || value === "ACCEPTED") return "success"; return ""; } function mergeInviteStatus(profile) { const record = state.invite.invited.find((item) => sameInviteProfile(inviteItemProfile(item), profile)); if (!record) return { userProfile: profile }; return { ...record, userProfile: { ...profile, ...inviteItemProfile(record) } }; } function setInviteStatus(text = "", type = "") { state.invite.statusText = text; state.invite.statusType = type; const status = document.querySelector("#inviteStatus"); if (!status) return; status.textContent = text; status.hidden = !text; status.classList.toggle("is-error", type === "error"); status.classList.toggle("is-success", type === "success"); } function createInviteFallback(name) { const span = document.createElement("span"); span.className = "invite-avatar-fallback"; span.textContent = String(name || "B").slice(0, 1).toUpperCase() || "B"; return span; } function createInviteAvatar(profile) { const avatar = document.createElement("div"); avatar.className = "invite-user-avatar"; const name = pickProfileName(profile); const avatarUrl = profile?.userAvatar || profile?.avatar || ""; if (!avatarUrl) { avatar.appendChild(createInviteFallback(name)); return avatar; } const image = document.createElement("img"); image.src = avatarUrl; image.alt = ""; image.addEventListener("error", () => { avatar.replaceChildren(createInviteFallback(name)); }); avatar.appendChild(image); return avatar; } function createInviteCard(item) { const profile = inviteItemProfile(item); const status = inviteStatusType(item?.status); const card = document.createElement("article"); card.className = "invite-user-card"; const main = document.createElement("div"); main.className = "invite-user-main"; const nameNode = document.createElement("div"); nameNode.className = "invite-user-name"; nameNode.textContent = pickProfileName(profile); const metaNode = document.createElement("div"); metaNode.className = "invite-user-meta"; metaNode.textContent = `${message("uid_prefix")} ${pickProfileAccount(profile)}`.trim(); main.append(nameNode, metaNode); const side = document.createElement("div"); side.className = "invite-user-side"; if (status === "pending") { const badge = document.createElement("span"); badge.className = "invite-badge pending"; badge.textContent = message("pending"); const cancel = document.createElement("button"); cancel.className = "invite-cancel"; cancel.type = "button"; cancel.textContent = message("cancel"); cancel.disabled = state.invite.locked; cancel.addEventListener("click", () => cancelInvite(item?.id)); side.append(badge, cancel); } else if (status === "success") { const badge = document.createElement("span"); badge.className = "invite-badge success"; badge.textContent = message("success"); side.appendChild(badge); } else { const invite = document.createElement("button"); invite.className = "invite-action"; invite.type = "button"; invite.textContent = state.invite.locked ? message("inviting") : message("invite"); invite.disabled = state.invite.locked || !inviteProfileId(profile); invite.addEventListener("click", () => inviteUser(profile)); side.appendChild(invite); } card.append(createInviteAvatar(profile), main, side); return card; } function renderInviteModal() { const config = inviteConfig(); const title = document.querySelector("#inviteModalTitle"); const input = document.querySelector("#inviteSearchInput"); const searchButton = document.querySelector("#inviteSearchButton"); const list = document.querySelector("#inviteList"); const empty = document.querySelector("#inviteEmpty"); const rows = state.invite.showSearch ? state.invite.searched : state.invite.invited; const hasQuery = Boolean(String(state.invite.query || "").trim()); if (title) title.textContent = message(config.titleKey); if (input && document.activeElement !== input) input.value = state.invite.query; if (searchButton) { searchButton.disabled = state.invite.locked || !hasQuery; searchButton.textContent = state.invite.searching ? message("searching") : message("search"); } setInviteStatus(state.invite.statusText, state.invite.statusType); if (list) replaceChildren(list, rows.map(createInviteCard)); if (empty) empty.hidden = rows.length > 0 || Boolean(state.invite.statusText); } async function loadInviteRecords(options = {}) { const config = inviteConfig(); if (!options.silent) setInviteStatus(message("loading_data")); renderInviteModal(); try { const records = await requestJSON(config.list); state.invite.invited = Array.isArray(records) ? records : []; if (!options.preserveStatus) setInviteStatus(""); } catch (error) { console.error("Failed to load invite records:", error); setInviteStatus(error.response?.errorMsg || error.message || message("failed_to_load_invites"), "error"); } finally { renderInviteModal(); } } function handleInviteInput(event) { const query = String(event?.target?.value || ""); state.invite.query = query; if (!query.trim()) { state.invite.showSearch = false; state.invite.searched = []; setInviteStatus(""); } renderInviteModal(); } async function searchInviteProfile(query) { let profile = null; let firstError = null; try { profile = await requestJSON(`/user/user-profile/open-search?account=${encodeURIComponent(query)}`); } catch (error) { firstError = error; } if (!profile && /^\d+$/.test(query)) { try { profile = await requestJSON(`/user/user-profile?userId=${encodeURIComponent(query)}`); firstError = null; } catch (error) { if (!firstError) firstError = error; } } const primaryProfile = Array.isArray(profile) ? profile[0] : profile; const normalizedProfile = inviteItemProfile(primaryProfile); if (normalizedProfile && (!pickProfileName(normalizedProfile) || pickProfileName(normalizedProfile) === "-" || !pickSpecialAccount(normalizedProfile) || !normalizedProfile.userAvatar)) { const userId = inviteProfileId(normalizedProfile) || (/^\d+$/.test(query) ? query : ""); if (userId) { try { const detail = await requestJSON(`/user/user-profile?userId=${encodeURIComponent(userId)}`); const mergedProfile = { ...normalizedProfile, ...inviteItemProfile(detail) }; profile = Array.isArray(profile) ? [{ ...primaryProfile, userProfile: mergedProfile }, ...profile.slice(1)] : (primaryProfile?.userProfile ? { ...primaryProfile, userProfile: mergedProfile } : mergedProfile); } catch (error) { console.debug("Invite search detail fallback skipped:", error); } } } if (!profile && firstError) throw firstError; return Array.isArray(profile) ? profile : (profile ? [profile] : []); } async function searchInviteUser(event) { if (event) event.preventDefault(); if (state.invite.locked) return; const input = document.querySelector("#inviteSearchInput"); const query = String(input?.value || "").trim(); state.invite.query = query; state.invite.showSearch = Boolean(query); state.invite.searched = []; if (!query) { setInviteStatus(""); renderInviteModal(); return; } state.invite.locked = true; state.invite.searching = true; setInviteStatus(message("searching")); renderInviteModal(); try { const profiles = await searchInviteProfile(query); state.invite.searched = profiles .map((profile) => inviteItemProfile(profile)) .filter((profile) => inviteProfileId(profile) || profile?.account) .map(mergeInviteStatus); setInviteStatus(""); } catch (error) { console.error("Invite search failed:", error); setInviteStatus(error.response?.errorMsg || error.message || message("something_went_wrong"), "error"); } finally { state.invite.locked = false; state.invite.searching = false; renderInviteModal(); } } async function inviteUser(profile) { const userId = inviteProfileId(profile); if (state.invite.locked || !userId) return; const config = inviteConfig(); state.invite.locked = true; renderInviteModal(); try { await requestJSON(config.invite, { method: "POST", body: { inviteUserId: userId } }); showToast(message("invitation_submitted")); state.invite.query = ""; state.invite.showSearch = false; state.invite.searched = []; const input = document.querySelector("#inviteSearchInput"); if (input) input.value = ""; await loadInviteRecords({ silent: true }); } catch (error) { console.error("Invite request failed:", error); setInviteStatus(error.response?.errorMsg || error.message || message("something_went_wrong"), "error"); } finally { state.invite.locked = false; renderInviteModal(); } } async function cancelInvite(id) { if (state.invite.locked || !id) return; const config = inviteConfig(); state.invite.locked = true; renderInviteModal(); try { await requestJSON(config.cancel(id), { method: "POST" }); showToast(message("application_cancelled")); state.invite.query = ""; state.invite.showSearch = false; state.invite.searched = []; const input = document.querySelector("#inviteSearchInput"); if (input) input.value = ""; await loadInviteRecords({ silent: true }); } catch (error) { console.error("Cancel invite failed:", error); const text = error.response?.errorMsg?.replace(/^.*\]\s*/, "") || message("cancellation_failed_try_again"); setInviteStatus(text, "error"); } finally { state.invite.locked = false; renderInviteModal(); } } async function showInviteModal(mode) { state.invite.mode = normalizeInviteMode(mode); state.invite.invited = []; state.invite.searched = []; state.invite.showSearch = false; state.invite.query = ""; state.invite.locked = false; state.invite.searching = false; setInviteStatus(""); const input = document.querySelector("#inviteSearchInput"); if (input) input.value = ""; renderInviteModal(); setModalOpen("#inviteModal", true); window.setTimeout(() => document.querySelector("#inviteSearchInput")?.focus(), 80); await loadInviteRecords(); } function closeInviteModal() { setModalOpen("#inviteModal", false); } async function fetchProfile() { state.profile = await requestJSON("/team/member/profile"); renderProfile(); } async function fetchIdentity() { state.identity = await requestJSON("/app/h5/identity"); } async function fetchTeamEntry() { state.teamEntry = await requestJSON("/team/entry"); } async function fetchBalanceTotal() { const salaryTypes = ["BD_SALARY", "BD_LEADER_SALARY"]; const results = await Promise.allSettled( salaryTypes.map((salaryType) => requestJSON(`/wallet/salary-account/balance?salaryType=${encodeURIComponent(salaryType)}`)) ); const balances = {}; results.forEach((result, index) => { const salaryType = salaryTypes[index]; if (result.status === "fulfilled") { balances[salaryType] = result.value || {}; } else { balances[salaryType] = {}; console.warn(`Failed to load ${salaryType} balance:`, result.reason); } }); state.balanceTotal = { ...balances, availableBalance: salaryTypes.reduce((total, salaryType) => total + balanceAmount(balances[salaryType]), 0) }; renderBalance(); } async function fetchBDLeaderBill() { state.bill = await requestJSON("/team/bd/member-bill/list?type=BDLEADER") || {}; renderBill(); if (normalizeTeamListType(state.teamList.type) === "bd") renderTeamListCard(); } async function fetchAgencyBill() { state.agencyBill = await requestJSON("/team/bd/member-bill/list?type=BD") || {}; if (normalizeTeamListType(state.teamList.type) === "agency") renderTeamListCard(); } async function ensureTeamListData(type) { const normalizedType = normalizeTeamListType(type); if (hasTeamListData(teamListBill(normalizedType))) return; if (normalizedType === "agency") { await fetchAgencyBill(); return; } await fetchBDLeaderBill(); } async function switchTeamList(type) { state.teamList.type = normalizeTeamListType(type); state.teamList.error = ""; state.teamList.loading = !hasTeamListData(teamListBill(state.teamList.type)); renderTeamListCard(); if (!state.teamList.loading) return; try { await ensureTeamListData(state.teamList.type); } catch (error) { console.error("Failed to load team list:", error); state.teamList.error = error.response?.errorMsg || error.message || message("failed_to_load"); } finally { state.teamList.loading = false; renderTeamListCard(); } } async function fetchHistory() { const history = await requestJSON("/team/bd/leader/history") || {}; const currentHistory = state.bill?.bdHistoryCO; const details = Array.isArray(history.incomeDetails) ? [...history.incomeDetails] : []; if (currentHistory) details.unshift(currentHistory); state.history = { ...history, incomeDetails: details }; renderHistory(); } async function showHistoryModal() { try { setModalOpen("#historyModal", true); const list = document.querySelector("#historyList"); if (list) { const loading = document.createElement("div"); loading.className = "empty-state"; loading.textContent = message("loading_data"); replaceChildren(list, [loading]); } await fetchHistory(); } catch (error) { console.error("Failed to load BD leader history:", error); showToast(error.response?.errorMsg || error.message || message("failed_to_load_history")); renderHistory(); } } async function showHistoryMore(billBelong) { const path = historyMorePath(billBelong); if (!path) return; try { state.historyMore = await requestJSON(path) || {}; renderDetails(); setModalOpen("#detailsModal", true); } catch (error) { console.error("Failed to load BD leader history more:", error); showToast(error.response?.errorMsg || error.message || message("failed_to_load_history")); } } function buildRouteURL(route) { const url = new URL(route, window.location.origin); const token = readRawParam("token"); if (token) url.searchParams.set("token", token); if (currentLang) url.searchParams.set("lang", currentLang); return `${url.pathname}${url.search}${url.hash}`; } function navigateTo(route) { window.location.href = buildRouteURL(route); } function syncWalletActionLink() { const nextParams = new URLSearchParams(); const token = readRawParam("token"); if (token) nextParams.set("token", token); if (currentLang) nextParams.set("lang", currentLang); nextParams.set("identity", "BD_LEADER"); nextParams.set("salaryType", "BD_LEADER_SALARY"); nextParams.set("mode", "withdraw"); const query = nextParams.toString(); document.querySelectorAll("[data-wallet-action-link]").forEach((link) => { link.setAttribute("href", `../withdraw-exchange/index.html${query ? `?${query}` : ""}`); }); } function closePage() { try { if (window.app && typeof window.app.closePage === "function") { window.app.closePage(); return; } if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") { window.FlutterPageControl.postMessage("close_page"); return; } if (window.history.length > 1) window.history.back(); } catch (error) { console.error("close BD leader center page failed:", error); if (window.history.length > 1) window.history.back(); } } function setLanguageMenuOpen(open) { const button = document.querySelector(".language-button"); const menu = document.querySelector(".language-menu"); if (!button || !menu) return; menu.hidden = !open; button.setAttribute("aria-expanded", String(open)); } function renderLanguageControls() { const button = document.querySelector(".language-button"); if (button) { button.textContent = languageLabels[currentLang]; button.setAttribute("aria-label", message("language_button_aria", "Change language")); } document.querySelectorAll(".language-menu [data-lang]").forEach((node) => { const active = node.dataset.lang === currentLang; node.classList.toggle("is-active", active); node.setAttribute("aria-pressed", String(active)); }); } async function applyLanguage(lang) { currentLang = normalizeLanguage(lang); currentMessages = fallbackMessages[currentLang] || fallbackMessages.en; document.documentElement.lang = currentLang; document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr"; document.title = message("document_title", "BD Leader Center"); renderMessages(currentMessages); renderLanguageControls(); renderProfile(); renderBalance(); renderBill(); renderHistory(); renderDetails(); renderTeamListCard(); renderInviteModal(); syncWalletActionLink(); } function clearPayee() { try { sessionStorage.removeItem("payee"); localStorage.removeItem("payee"); } catch (error) { console.debug("payee cache clear skipped:", error); } } function setIdentity() { try { localStorage.setItem("identity", JSON.stringify({ identity: "BD_LEADER" })); } catch (error) { console.debug("identity cache write skipped:", error); } } function bindEvents() { document.querySelector(".back-button")?.addEventListener("click", closePage); document.querySelector(".language-button")?.addEventListener("click", (event) => { event.stopPropagation(); const menu = document.querySelector(".language-menu"); setLanguageMenuOpen(Boolean(menu?.hidden)); }); document.querySelectorAll(".language-menu [data-lang]").forEach((button) => { button.addEventListener("click", (event) => { event.stopPropagation(); setLanguageMenuOpen(false); applyLanguage(button.dataset.lang); }); }); document.addEventListener("click", () => setLanguageMenuOpen(false)); document.querySelectorAll("[data-route]").forEach((node) => { node.addEventListener("click", () => navigateTo(node.dataset.route)); }); document.querySelector("[data-action='history']")?.addEventListener("click", showHistoryModal); document.querySelectorAll("[data-list-tab]").forEach((button) => { button.addEventListener("click", () => switchTeamList(button.dataset.listTab)); }); document.querySelector("[data-action='invite-agent']")?.addEventListener("click", () => showInviteModal("agent")); document.querySelector("[data-action='invite-bd']")?.addEventListener("click", () => showInviteModal("bd")); document.querySelector("#inviteSearchInput")?.addEventListener("input", handleInviteInput); document.querySelector("#inviteSearchForm")?.addEventListener("submit", searchInviteUser); document.querySelectorAll("[data-close-history]").forEach((node) => { node.addEventListener("click", () => setModalOpen("#historyModal", false)); }); document.querySelectorAll("[data-close-details]").forEach((node) => { node.addEventListener("click", () => setModalOpen("#detailsModal", false)); }); document.querySelectorAll("[data-close-invite]").forEach((node) => { node.addEventListener("click", closeInviteModal); }); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { setModalOpen("#historyModal", false); setModalOpen("#detailsModal", false); closeInviteModal(); } }); } async function initializePage() { setLoading(true); clearPayee(); setIdentity(); bindEvents(); syncWalletActionLink(); renderProfile(); renderBalance(); try { await applyLanguage(currentLang); const tasks = [ fetchProfile(), fetchIdentity(), fetchTeamEntry(), fetchBalanceTotal(), fetchBDLeaderBill() ]; const results = await Promise.allSettled(tasks); const rejected = results.find((item) => item.status === "rejected"); if (rejected) { console.warn("BD leader center partial load failed:", rejected.reason); showToast(rejected.reason?.response?.errorMsg || rejected.reason?.message || message("failed_to_load")); } } catch (error) { console.error("Failed to initialize BD leader center:", error); showToast(error.response?.errorMsg || error.message || message("failed_to_load")); } finally { setLoading(false); } } initializePage(); })();