1212 lines
41 KiB
JavaScript
1212 lines
41 KiB
JavaScript
(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 Center",
|
||
page_label: "BD Center",
|
||
back: "Back",
|
||
title: "BD Center",
|
||
language_button_aria: "Change language",
|
||
role: "BD",
|
||
uid_prefix: "UID:",
|
||
my_available_salary: "Salary",
|
||
details: "Withdraw/Exchange",
|
||
bd_number: "BD number",
|
||
team_salary: "Team salary",
|
||
agency_list_link: "Agency List",
|
||
month_label: "Month",
|
||
invite_become_agent: "Invite Agent",
|
||
invite_agent_title: "Invite Agent",
|
||
enter_user_id: "Enter User ID",
|
||
search: "Search",
|
||
searching: "Searching...",
|
||
invite: "Invite",
|
||
inviting: "Inviting...",
|
||
cancel: "Cancel",
|
||
success: "Success",
|
||
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"
|
||
},
|
||
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: "راتب الفريق",
|
||
agency_list_link: "قائمة الوكالات",
|
||
month_label: "الشهر",
|
||
invite_become_agent: "دعوة وكيل",
|
||
invite_agent_title: "دعوة وكيل",
|
||
enter_user_id: "أدخل معرف المستخدم",
|
||
search: "بحث",
|
||
searching: "جار البحث...",
|
||
invite: "دعوة",
|
||
inviting: "جار الدعوة...",
|
||
cancel: "إلغاء",
|
||
success: "نجاح",
|
||
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: "فشل تحميل البيانات"
|
||
},
|
||
tr: {
|
||
document_title: "BD Merkezi",
|
||
page_label: "BD Merkezi",
|
||
back: "Geri",
|
||
title: "BD Merkezi",
|
||
language_button_aria: "Dili değiştir",
|
||
role: "BD",
|
||
uid_prefix: "UID:",
|
||
my_available_salary: "Maaş",
|
||
details: "Withdraw/Exchange",
|
||
bd_number: "BD sayısı",
|
||
team_salary: "Takım maaşı",
|
||
agency_list_link: "Ajans listesi",
|
||
month_label: "Ay",
|
||
invite_become_agent: "Ajans Davet Et",
|
||
invite_agent_title: "Ajans 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ı",
|
||
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"
|
||
},
|
||
id: {
|
||
document_title: "Pusat BD",
|
||
page_label: "Pusat BD",
|
||
back: "Kembali",
|
||
title: "Pusat BD",
|
||
language_button_aria: "Ubah bahasa",
|
||
role: "BD",
|
||
uid_prefix: "UID:",
|
||
my_available_salary: "Gaji",
|
||
details: "Withdraw/Exchange",
|
||
bd_number: "Jumlah BD",
|
||
team_salary: "Gaji tim",
|
||
agency_list_link: "Daftar agensi",
|
||
month_label: "Bulan",
|
||
invite_become_agent: "Invite Agent",
|
||
invite_agent_title: "Invite Agent",
|
||
enter_user_id: "Masukkan ID Pengguna",
|
||
search: "Cari",
|
||
searching: "Mencari...",
|
||
invite: "Undang",
|
||
inviting: "Mengundang...",
|
||
cancel: "Batal",
|
||
success: "Berhasil",
|
||
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"
|
||
}
|
||
};
|
||
|
||
const params = readURLParams();
|
||
const bdCenterEndpoints = {
|
||
profile: "/team/member/profile",
|
||
identity: "/app/h5/identity",
|
||
balance: "/wallet/salary-account/balance?salaryType=BD_SALARY",
|
||
agencyBill: (billBelong) => `/team/bd/member-bill/list?type=BD&billBelong=${encodeURIComponent(billBelong)}`,
|
||
inviteList: "/team/bd/invite-message-own",
|
||
inviteSearch: (account) => `/user/user-profile/open-search?account=${encodeURIComponent(account)}`,
|
||
inviteAgent: "/team/bd/invite-agent",
|
||
inviteCancel: (id) => `/team/bd/invite-agent-cancel/${encodeURIComponent(id)}`
|
||
};
|
||
let currentLang = normalizeLanguage(params.get("lang"));
|
||
let currentMessages = fallbackMessages[currentLang] || fallbackMessages.en;
|
||
const state = {
|
||
profile: null,
|
||
identity: null,
|
||
balanceTotal: {},
|
||
bill: {},
|
||
agencyBill: null,
|
||
billMonth: normalizeMonthValue(params.get("month") || params.get("billBelong")) || getCurrentMonthValue(),
|
||
teamList: {
|
||
type: "agency",
|
||
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 getCurrentMonthValue() {
|
||
const now = new Date();
|
||
const year = now.getFullYear();
|
||
const month = String(now.getMonth() + 1).padStart(2, "0");
|
||
return `${year}-${month}`;
|
||
}
|
||
|
||
function normalizeMonthValue(value) {
|
||
const raw = String(value || "").trim();
|
||
let year = "";
|
||
let month = "";
|
||
|
||
if (/^\d{4}-\d{2}$/.test(raw)) {
|
||
[year, month] = raw.split("-");
|
||
} else if (/^\d{6}$/.test(raw)) {
|
||
year = raw.slice(0, 4);
|
||
month = raw.slice(4, 6);
|
||
} else if (/^\d{8}$/.test(raw)) {
|
||
year = raw.slice(0, 4);
|
||
month = raw.slice(4, 6);
|
||
}
|
||
|
||
const numericYear = Number(year);
|
||
const numericMonth = Number(month);
|
||
if (!Number.isInteger(numericYear) || !Number.isInteger(numericMonth)) return "";
|
||
if (numericYear < 2000 || numericYear > 2099 || numericMonth < 1 || numericMonth > 12) return "";
|
||
return `${year}-${String(numericMonth).padStart(2, "0")}`;
|
||
}
|
||
|
||
function monthValueToBillBelong(value) {
|
||
return normalizeMonthValue(value).replace("-", "");
|
||
}
|
||
|
||
function syncMonthControl() {
|
||
const input = document.querySelector("#billMonthInput");
|
||
if (!input) return;
|
||
input.max = getCurrentMonthValue();
|
||
input.value = state.billMonth;
|
||
}
|
||
|
||
function renderBillRange(data = teamListBill()) {
|
||
const range = document.querySelector("#billRange");
|
||
if (!range) return;
|
||
range.textContent = data?.billTitle || state.billMonth.replace("-", ".");
|
||
}
|
||
|
||
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-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(".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 new Intl.NumberFormat("en-US", {
|
||
minimumFractionDigits: 2,
|
||
maximumFractionDigits: 2
|
||
}).format(number);
|
||
}
|
||
|
||
function formatCount(value) {
|
||
if (value === null || value === undefined || value === "") return "0";
|
||
const number = Number(value);
|
||
if (Number.isFinite(number)) {
|
||
return new Intl.NumberFormat("en-US", {
|
||
maximumFractionDigits: 2
|
||
}).format(number);
|
||
}
|
||
return String(value);
|
||
}
|
||
|
||
function firstPresentValue(values) {
|
||
return values.find((value) => value !== null && value !== undefined && value !== "");
|
||
}
|
||
|
||
function firstPositiveCount(values) {
|
||
return values.find((value) => Number(value) > 0) ?? firstPresentValue(values);
|
||
}
|
||
|
||
function agencyCount(data, members = []) {
|
||
return firstPositiveCount([
|
||
data?.agencyNumber,
|
||
data?.bdHistoryCO?.agencyNumber,
|
||
data?.bdHistory?.agencyNumber,
|
||
data?.memberCount,
|
||
members.length
|
||
]);
|
||
}
|
||
|
||
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.agencyBill || 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(agencyCount(bill, members));
|
||
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 "agency";
|
||
}
|
||
|
||
function teamListBill() {
|
||
return state.agencyBill || state.bill;
|
||
}
|
||
|
||
function hasTeamListData(data) {
|
||
return Boolean(data && (
|
||
Array.isArray(data.memberBillList) ||
|
||
data.memberCount !== undefined ||
|
||
data.agencyNumber !== undefined ||
|
||
data?.bdHistoryCO?.agencyNumber !== undefined ||
|
||
data?.bdHistory?.agencyNumber !== 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");
|
||
syncMonthControl();
|
||
renderBillRange(data);
|
||
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(agencyCount(data, members))),
|
||
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 inviteConfig(mode = state.invite.mode) {
|
||
return {
|
||
titleKey: "invite_agent_title",
|
||
list: bdCenterEndpoints.inviteList,
|
||
invite: bdCenterEndpoints.inviteAgent,
|
||
cancel: bdCenterEndpoints.inviteCancel
|
||
};
|
||
}
|
||
|
||
function normalizeInviteMode(mode) {
|
||
return "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) {
|
||
const profile = await requestJSON(bdCenterEndpoints.inviteSearch(query));
|
||
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(bdCenterEndpoints.profile);
|
||
renderProfile();
|
||
}
|
||
|
||
async function fetchIdentity() {
|
||
state.identity = await requestJSON(bdCenterEndpoints.identity);
|
||
}
|
||
|
||
async function fetchBalanceTotal() {
|
||
state.balanceTotal = await requestJSON(bdCenterEndpoints.balance) || {};
|
||
renderBalance();
|
||
}
|
||
|
||
async function fetchAgencyBill() {
|
||
state.agencyBill = await requestJSON(bdCenterEndpoints.agencyBill(monthValueToBillBelong(state.billMonth))) || {};
|
||
renderBill();
|
||
renderTeamListCard();
|
||
}
|
||
|
||
async function handleBillMonthChange(event) {
|
||
const nextMonth = normalizeMonthValue(event.target.value);
|
||
if (!nextMonth) {
|
||
syncMonthControl();
|
||
return;
|
||
}
|
||
if (nextMonth === state.billMonth) return;
|
||
|
||
state.billMonth = nextMonth;
|
||
state.agencyBill = null;
|
||
state.teamList.error = "";
|
||
state.teamList.loading = true;
|
||
renderTeamListCard();
|
||
|
||
try {
|
||
await fetchAgencyBill();
|
||
} catch (error) {
|
||
console.error("Failed to load selected bill month:", error);
|
||
state.teamList.error = error.response?.errorMsg || error.message || message("failed_to_load");
|
||
showToast(state.teamList.error);
|
||
} finally {
|
||
state.teamList.loading = false;
|
||
renderTeamListCard();
|
||
}
|
||
}
|
||
|
||
async function ensureTeamListData(type) {
|
||
const normalizedType = normalizeTeamListType(type);
|
||
if (hasTeamListData(teamListBill(normalizedType))) return;
|
||
await fetchAgencyBill();
|
||
}
|
||
|
||
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();
|
||
}
|
||
}
|
||
|
||
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");
|
||
nextParams.set("salaryType", "BD_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 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 Center");
|
||
renderMessages(currentMessages);
|
||
renderLanguageControls();
|
||
renderProfile();
|
||
renderBalance();
|
||
renderBill();
|
||
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" }));
|
||
} 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.querySelectorAll("[data-list-tab]").forEach((button) => {
|
||
button.addEventListener("click", () => switchTeamList(button.dataset.listTab));
|
||
});
|
||
document.querySelector("#billMonthInput")?.addEventListener("change", handleBillMonthChange);
|
||
document.querySelector("[data-action='invite-agent']")?.addEventListener("click", () => showInviteModal("agent"));
|
||
document.querySelector("#inviteSearchInput")?.addEventListener("input", handleInviteInput);
|
||
document.querySelector("#inviteSearchForm")?.addEventListener("submit", searchInviteUser);
|
||
document.querySelectorAll("[data-close-invite]").forEach((node) => {
|
||
node.addEventListener("click", closeInviteModal);
|
||
});
|
||
document.addEventListener("keydown", (event) => {
|
||
if (event.key === "Escape") {
|
||
closeInviteModal();
|
||
}
|
||
});
|
||
}
|
||
|
||
async function initializePage() {
|
||
setLoading(true);
|
||
clearPayee();
|
||
setIdentity();
|
||
bindEvents();
|
||
syncWalletActionLink();
|
||
renderProfile();
|
||
renderBalance();
|
||
|
||
try {
|
||
await applyLanguage(currentLang);
|
||
const tasks = [
|
||
fetchProfile(),
|
||
fetchIdentity(),
|
||
fetchBalanceTotal(),
|
||
fetchAgencyBill()
|
||
];
|
||
const results = await Promise.allSettled(tasks);
|
||
const rejected = results.find((item) => item.status === "rejected");
|
||
if (rejected) {
|
||
console.warn("BD 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 center:", error);
|
||
showToast(error.response?.errorMsg || error.message || message("failed_to_load"));
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
|
||
initializePage();
|
||
})();
|