2026-06-01 21:00:03 +08:00

1212 lines
41 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(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();
})();