diff --git a/h5/hyapp/bd-center/index.html b/h5/hyapp/bd-center/index.html new file mode 100644 index 0000000..e81ae30 --- /dev/null +++ b/h5/hyapp/bd-center/index.html @@ -0,0 +1,131 @@ + + + + + + BD Center + + + +
+ + + + + + +
+
+
+ + B +
+
+
BD
+
-
+
UID: -
+
+
+ +
+
+
+
Salary
+
+ +
+
+ + 0.00 +
+
+ + + +
+
+ +
+
+
+
Loading...
+
+
+
+ + + + +
+ +
loading....
+
+ + +
+ + + diff --git a/h5/hyapp/bd-center/script.js b/h5/hyapp/bd-center/script.js new file mode 100644 index 0000000..196a76e --- /dev/null +++ b/h5/hyapp/bd-center/script.js @@ -0,0 +1,1114 @@ +(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", + 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: "قائمة الوكالات", + 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", + 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", + 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(); + let currentLang = normalizeLanguage(params.get("lang")); + let currentMessages = fallbackMessages[currentLang] || fallbackMessages.en; + const state = { + profile: null, + identity: null, + teamEntry: null, + balanceTotal: {}, + bill: {}, + agencyBill: null, + 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 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 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.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(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 "agency"; + } + + function teamListBill() { + return state.agencyBill || state.bill; + } + + function hasTeamListData(data) { + return Boolean(data && (Array.isArray(data.memberBillList) || 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 inviteConfig(mode = state.invite.mode) { + 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 "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() { + state.balanceTotal = await requestJSON("/wallet/salary-account/balance?salaryType=BD_SALARY") || {}; + renderBalance(); + } + + async function fetchAgencyBill() { + state.agencyBill = await requestJSON("/team/bd/member-bill/list?type=BD") || {}; + renderBill(); + 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 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(); + } + + 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("[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(); + renderProfile(); + renderBalance(); + + try { + await applyLanguage(currentLang); + const tasks = [ + fetchProfile(), + fetchIdentity(), + fetchTeamEntry(), + 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(); +})(); diff --git a/h5/hyapp/bd-center/style.css b/h5/hyapp/bd-center/style.css new file mode 100644 index 0000000..776c64c --- /dev/null +++ b/h5/hyapp/bd-center/style.css @@ -0,0 +1,1269 @@ +:root { + --page-bg: #f6f7f7; + --card-bg: rgba(255, 255, 255, 0.96); + --text: #25282e; + --muted: #8f9196; + --line: #eef0f2; + --mint: #43e7d8; + --green: #25d878; + --shadow: 0 8px 22px rgba(36, 46, 60, 0.055); +} + +* { + box-sizing: border-box; +} + +html, +body { + width: 100%; + min-height: 100%; + margin: 0; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; +} + +body { + display: flex; + justify-content: center; + background: #111; + color: var(--text); + font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; +} + +button { + border: 0; + padding: 0; + background: transparent; + color: inherit; + font: inherit; +} + +[hidden] { + display: none !important; +} + +.bd-center { + position: relative; + width: 100%; + max-width: 430px; + min-height: 100vh; + overflow: hidden; + background: var(--page-bg); +} + +.bd-center[data-loading="true"] .title-bar, +.bd-center[data-loading="true"] .content, +.bd-center[data-loading="true"] .home-indicator { + visibility: hidden; +} + +.hero-bg { + position: absolute; + inset: 0 0 auto; + height: 215px; + background: + radial-gradient(circle at 72% 4%, rgba(209, 255, 248, 0.98) 0, rgba(209, 255, 248, 0.88) 23%, rgba(209, 255, 248, 0) 48%), + radial-gradient(circle at 14% 20%, rgba(185, 250, 244, 0.78) 0, rgba(185, 250, 244, 0) 35%), + linear-gradient(151deg, #ccf7ef 0%, #ebfffa 58%, rgba(242, 255, 252, 0.7) 100%); + clip-path: polygon(0 0, 100% 0, 100% 76%, 70% 47%, 40% 58%, 0 78%); +} + +.status-bar, +.title-bar, +.content, +.home-indicator { + position: relative; + z-index: 1; +} + +.status-bar { + display: flex; + align-items: center; + justify-content: space-between; + height: 47px; + padding: 18px 29px 0 38px; + color: #11161c; + direction: ltr; + font-size: 15px; + font-weight: 800; +} + +.status-icons { + display: flex; + align-items: center; + gap: 6px; +} + +.cellular { + display: flex; + align-items: flex-end; + gap: 2px; + width: 18px; + height: 12px; +} + +.cellular i { + width: 3px; + border-radius: 2px; + background: #11161c; +} + +.cellular i:nth-child(1) { height: 4px; } +.cellular i:nth-child(2) { height: 6px; } +.cellular i:nth-child(3) { height: 9px; } +.cellular i:nth-child(4) { height: 12px; } + +.wifi { + position: relative; + display: inline-flex; + width: 16px; + height: 12px; +} + +.wifi i { + position: absolute; + left: 50%; + bottom: 0; + border: 2px solid transparent; + border-top-color: #11161c; + border-radius: 50%; + transform: translateX(-50%); +} + +.wifi i:nth-child(1) { width: 16px; height: 16px; } +.wifi i:nth-child(2) { width: 11px; height: 11px; bottom: 2px; } +.wifi i:nth-child(3) { width: 5px; height: 5px; bottom: 4px; background: #11161c; border: 0; } + +.battery { + position: relative; + width: 24px; + height: 12px; + border: 2px solid #11161c; + border-radius: 2px; +} + +.battery::after { + content: ""; + position: absolute; + right: -4px; + top: 3px; + width: 2px; + height: 4px; + border-radius: 0 2px 2px 0; + background: #11161c; +} + +.battery i { + display: block; + width: 16px; + height: 100%; + background: #11161c; +} + +.title-bar { + display: grid; + grid-template-columns: 44px minmax(0, 1fr) 56px; + align-items: center; + z-index: 12; + min-height: 56px; + padding: 8px 14px 6px; +} + +.back-button, +.language-button { + display: flex; + align-items: center; + justify-content: center; + width: 38px; + height: 38px; + border-radius: 50%; + color: #2c3036; +} + +.back-button svg { + width: 30px; + height: 30px; + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2.4; +} + +.title-bar h1 { + min-width: 0; + margin: 0; + overflow: hidden; + color: #24282e; + font-size: 19px; + font-weight: 900; + line-height: 1.2; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; +} + +.language-switcher { + position: relative; + display: flex; + justify-content: flex-end; +} + +.language-button { + width: auto; + min-width: 42px; + height: 32px; + padding: 0 10px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.55); + font-size: 12px; + font-weight: 900; +} + +.language-menu { + position: absolute; + top: 38px; + right: 0; + z-index: 30; + display: grid; + gap: 4px; + min-width: 74px; + padding: 8px; + border: 1px solid rgba(34, 196, 180, 0.18); + border-radius: 8px; + background: #fff; + box-shadow: 0 12px 24px rgba(22, 31, 42, 0.12); +} + +.language-menu button { + min-height: 30px; + border-radius: 6px; + font-size: 12px; + font-weight: 850; +} + +.language-menu button.is-active { + background: #e7fbf8; + color: #0aa397; +} + +.content { + display: grid; + gap: 12px; + padding: 8px 14px 32px; +} + +.card { + border-radius: 8px; + background: var(--card-bg); + box-shadow: var(--shadow); +} + +.profile-card { + display: flex; + align-items: center; + min-height: 104px; + padding: 16px; +} + +.avatar-shell, +.member-avatar { + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + border-radius: 50%; + background: linear-gradient(145deg, #d9fff5, #43e7d8); +} + +.avatar-shell { + width: 70px; + height: 70px; + flex: 0 0 70px; +} + +.avatar-image, +.member-avatar img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} + +.avatar-fallback, +.member-fallback { + color: #0b5f56; + font-size: 26px; + font-weight: 950; +} + +.profile-copy { + min-width: 0; + flex: 1; + margin-left: 16px; +} + +.role-pill { + display: inline-flex; + align-items: center; + min-height: 22px; + padding: 0 9px; + border-radius: 999px; + background: #e7fbf8; + color: #0b958a; + font-size: 11px; + font-weight: 900; +} + +.name { + margin-top: 7px; + overflow: hidden; + color: #25282e; + font-size: 20px; + font-weight: 900; + line-height: 1.15; + text-overflow: ellipsis; + white-space: nowrap; +} + +.meta { + margin-top: 6px; + overflow: hidden; + color: var(--muted); + font-size: 13px; + font-weight: 750; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.salary-card { + min-height: 113px; + padding: 18px 20px; +} + +.salary-head, +.section-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; +} + +.salary-label, +.section-title { + color: #24282e; + font-size: 16px; + font-weight: 900; + line-height: 1.2; +} + +.section-subtitle { + margin-top: 6px; + color: #93959a; + font-size: 12px; + font-weight: 800; + line-height: 1.2; +} + +.text-link { + flex: 0 0 auto; + color: #10bcb0; + font-size: 13px; + font-weight: 900; +} + +.salary-value { + display: flex; + align-items: baseline; + gap: 6px; + margin-top: 16px; + color: #1f242b; +} + +.salary-value span, +.metric-money span { + color: #21b66a; + font-weight: 950; +} + +.salary-value strong { + min-width: 0; + overflow-wrap: anywhere; + font-size: 31px; + font-weight: 950; + line-height: 1.08; +} + +.menu-card { + overflow: hidden; +} + +.menu-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + width: 100%; + min-height: 58px; + padding: 0 16px; + border-bottom: 1px solid var(--line); +} + +.menu-row:last-child { + border-bottom: 0; +} + +.menu-left { + display: flex; + align-items: center; + min-width: 0; + gap: 14px; +} + +.menu-icon { + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + flex: 0 0 30px; + border-radius: 8px; + background: #e9fbf4; + color: #1f9d66; +} + +.menu-icon svg { + width: 22px; + height: 22px; + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2.1; +} + +.menu-left span:last-child { + min-width: 0; + overflow: hidden; + color: #464951; + font-size: 15px; + font-weight: 850; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.chevron { + color: #8b8d92; + font-size: 24px; + line-height: 1; +} + +.team-list-card { + display: grid; + gap: 12px; + padding: 14px; +} + +.team-list-tabs { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; + padding: 4px; + border-radius: 8px; + background: #f2f6f6; +} + +.team-list-tabs.is-single { + grid-template-columns: minmax(0, 1fr); +} + +.team-list-tab { + min-width: 0; + min-height: 38px; + padding: 0 10px; + border-radius: 7px; + color: #697078; + font-size: 13px; + font-weight: 950; + line-height: 1.1; +} + +.team-list-tab.is-active { + background: linear-gradient(135deg, #30dfb4, #15bda9); + color: #fff; + box-shadow: 0 8px 18px rgba(21, 189, 169, 0.24); +} + +.team-list-summary { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); + gap: 8px; +} + +.team-list-summary > div { + min-width: 0; + padding: 10px; + border-radius: 8px; + background: #f7fbfa; +} + +.team-list-summary span { + display: block; + overflow: hidden; + color: #8b8d92; + font-size: 11px; + font-weight: 850; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.team-list-summary strong { + display: block; + margin-top: 4px; + overflow-wrap: anywhere; + color: #24282e; + font-size: 15px; + font-weight: 950; + line-height: 1.15; +} + +.team-list-content { + display: grid; + gap: 10px; + max-height: 360px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +.member-card { + padding: 16px; +} + +.member-list { + display: grid; + gap: 10px; + margin-top: 14px; +} + +.member-row, +.history-row, +.details-row { + display: grid; + gap: 10px; + padding: 12px; + border: 1px solid #eef0f2; + border-radius: 8px; + background: #fbfcfc; +} + +.member-row { + grid-template-columns: 44px minmax(0, 1fr) auto; + align-items: center; +} + +.member-avatar { + width: 44px; + height: 44px; +} + +.member-fallback { + font-size: 18px; +} + +.member-main { + min-width: 0; +} + +.member-name { + overflow: hidden; + color: #25282e; + font-size: 15px; + font-weight: 900; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.member-meta { + margin-top: 4px; + overflow: hidden; + color: #8b8d92; + font-size: 12px; + font-weight: 800; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.member-side { + display: grid; + gap: 3px; + color: #555b64; + font-size: 12px; + font-weight: 850; + line-height: 1.2; + text-align: right; +} + +.empty-state { + display: flex; + align-items: center; + justify-content: center; + min-height: 80px; + border: 1px dashed #e5e8ea; + border-radius: 8px; + color: #8b8d92; + font-size: 14px; + font-weight: 850; + text-align: center; +} + +.history-modal[hidden], +.details-modal[hidden], +.invite-modal[hidden] { + display: none; +} + +.history-modal, +.details-modal, +.invite-modal { + position: fixed; + z-index: 24; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 18px; +} + +.history-backdrop, +.invite-backdrop { + position: absolute; + inset: 0; + background: rgba(15, 21, 28, 0.38); +} + +.history-dialog, +.invite-dialog { + position: relative; + display: flex; + flex-direction: column; + width: min(394px, 100%); + max-height: 78vh; + max-height: 78dvh; + overflow: hidden; + border-radius: 14px; + background: #fff; + box-shadow: 0 18px 44px rgba(23, 31, 42, 0.2); +} + +.history-head, +.invite-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 58px; + padding: 16px 18px 12px; + border-bottom: 1px solid #eef0f2; +} + +.history-head h2, +.invite-head h2 { + min-width: 0; + margin: 0; + overflow: hidden; + color: #24282e; + font-size: 18px; + font-weight: 950; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.history-close, +.invite-close { + display: flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 50%; + background: #f4f6f7; + color: #50545b; + font-size: 25px; + font-weight: 500; + line-height: 1; +} + +.invite-dialog { + max-height: 80vh; + max-height: 80dvh; +} + +.invite-body { + display: flex; + min-height: 0; + flex: 1 1 auto; + flex-direction: column; + gap: 12px; + overflow-y: auto; + padding: 14px 18px max(18px, env(safe-area-inset-bottom)); + -webkit-overflow-scrolling: touch; +} + +.invite-search-form { + margin: 0; +} + +.invite-field { + display: grid; + gap: 8px; +} + +.invite-field > span { + color: #555b64; + font-size: 13px; + font-weight: 900; + line-height: 1.2; +} + +.invite-search-control { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px; +} + +.invite-search-control input { + width: 100%; + min-width: 0; + height: 42px; + padding: 0 12px; + border: 1px solid #e4e8eb; + border-radius: 8px; + outline: none; + background: #fbfcfc; + color: #25282e; + font: inherit; + font-size: 14px; + font-weight: 800; +} + +.invite-search-control input:focus { + border-color: rgba(16, 188, 176, 0.72); + box-shadow: 0 0 0 3px rgba(67, 231, 216, 0.16); +} + +.invite-search-control button, +.invite-action, +.invite-cancel { + min-height: 34px; + border-radius: 8px; + font-size: 12px; + font-weight: 950; +} + +.invite-search-control button { + min-width: 76px; + height: 42px; + padding: 0 12px; + background: linear-gradient(135deg, #30dfb4, #15bda9); + color: #fff; +} + +.invite-search-control button:disabled, +.invite-action:disabled, +.invite-cancel:disabled { + opacity: 0.55; +} + +.invite-status { + padding: 10px 12px; + border-radius: 8px; + background: #eafbf7; + color: #078d80; + font-size: 12px; + font-weight: 850; + line-height: 1.35; +} + +.invite-status.is-error { + background: #fff0f0; + color: #c43f3f; +} + +.invite-status.is-success { + background: #ebfbf2; + color: #128946; +} + +.invite-list { + display: grid; + gap: 10px; +} + +.invite-empty { + display: flex; + align-items: center; + justify-content: center; + min-height: 86px; + border: 1px dashed #e5e8ea; + border-radius: 8px; + color: #8b8d92; + font-size: 14px; + font-weight: 850; + text-align: center; +} + +.invite-user-card { + display: grid; + grid-template-columns: 44px minmax(0, 1fr) auto; + align-items: center; + gap: 10px; + padding: 12px; + border: 1px solid #eef0f2; + border-radius: 8px; + background: #fbfcfc; +} + +.invite-user-card.is-selected { + border-color: rgba(16, 188, 176, 0.7); + box-shadow: 0 0 0 3px rgba(67, 231, 216, 0.14); +} + +.invite-user-avatar { + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + overflow: hidden; + border-radius: 50%; + background: linear-gradient(145deg, #d9fff5, #43e7d8); +} + +.invite-user-avatar img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} + +.invite-avatar-fallback { + color: #0b5f56; + font-size: 18px; + font-weight: 950; +} + +.invite-user-main { + min-width: 0; +} + +.invite-user-name { + overflow: hidden; + color: #25282e; + font-size: 15px; + font-weight: 900; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.invite-user-meta { + margin-top: 4px; + overflow: hidden; + color: #8b8d92; + font-size: 12px; + font-weight: 800; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.invite-user-side { + display: grid; + justify-items: end; + gap: 6px; +} + +.invite-action { + min-width: 68px; + padding: 0 12px; + background: #1fcf7a; + color: #fff; +} + +.invite-cancel { + min-width: 68px; + padding: 0 12px; + border: 1px solid #ffd6d6; + background: #fff2f2; + color: #c74444; +} + +.invite-badge { + min-width: 72px; + padding: 5px 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 950; + line-height: 1; + text-align: center; +} + +.invite-badge.pending { + background: #fff4dc; + color: #bd7a10; +} + +.invite-badge.success { + background: #e9fbf1; + color: #15914f; +} + +.history-summary { + display: grid; + gap: 8px; + padding: 13px 18px; + border-bottom: 1px solid #f0f2f3; +} + +.history-summary.compact { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.history-summary span { + display: block; + color: #8b8d92; + font-size: 12px; + font-weight: 800; + line-height: 1.2; +} + +.history-summary strong { + display: block; + margin-top: 4px; + overflow-wrap: anywhere; + color: #24282e; + font-size: 18px; + font-weight: 950; + line-height: 1.2; +} + +.history-list { + display: grid; + flex: 1 1 auto; + gap: 10px; + min-height: 0; + overflow-y: auto; + padding: 14px 18px max(18px, env(safe-area-inset-bottom)); + -webkit-overflow-scrolling: touch; +} + +.history-row { + position: relative; + padding-top: 35px; +} + +.status-tag { + position: absolute; + top: 0; + left: 0; + min-width: 92px; + padding: 4px 9px; + border-radius: 8px 0 8px 0; + background: #3d73ff; + color: #fff; + font-size: 12px; + font-weight: 900; + text-align: center; +} + +.history-row.is-completed .status-tag { + background: #25c05c; +} + +.history-row.is-pending .status-tag { + background: #f0a92e; +} + +.history-row.is-out .status-tag { + background: #9ea1a7; +} + +.history-title { + color: #24282e; + font-size: 15px; + font-weight: 950; + line-height: 1.2; +} + +.history-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.history-cell span, +.details-cell span { + display: block; + color: #96989d; + font-size: 11px; + font-weight: 800; + line-height: 1.2; +} + +.history-cell strong, +.details-cell strong { + display: block; + margin-top: 4px; + overflow-wrap: anywhere; + color: #292d33; + font-size: 14px; + font-weight: 950; + line-height: 1.2; +} + +.more-button { + justify-self: end; + color: #10bcb0; + font-size: 13px; + font-weight: 900; +} + +.details-row { + grid-template-columns: 44px minmax(0, 1fr); + align-items: center; +} + +.loading-mask { + position: absolute; + z-index: 30; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: 18px; + min-height: 100vh; + background: + radial-gradient(circle at 50% 35%, rgba(230, 255, 243, 0.98), rgba(246, 247, 247, 0.98) 44%, rgba(246, 247, 247, 0.96) 100%); +} + +.runner-stick { + position: relative; + width: 92px; + height: 82px; + color: #1ecf70; + filter: drop-shadow(0 8px 14px rgba(30, 207, 112, 0.22)); + animation: runner-bob 0.42s ease-in-out infinite; +} + +.runner-head, +.runner-body, +.runner-arm, +.runner-leg, +.runner-ground { + position: absolute; + display: block; +} + +.runner-head { + top: 5px; + left: 39px; + width: 18px; + height: 18px; + border: 4px solid currentColor; + border-radius: 50%; +} + +.runner-body { + top: 26px; + left: 47px; + width: 5px; + height: 28px; + border-radius: 999px; + background: currentColor; + transform-origin: 50% 3px; + animation: runner-body 0.42s ease-in-out infinite; +} + +.runner-arm, +.runner-leg { + left: 47px; + width: 5px; + border-radius: 999px; + background: currentColor; + transform-origin: 50% 2px; +} + +.runner-arm { + top: 31px; + height: 27px; +} + +.runner-arm-front { animation: runner-arm-front 0.42s ease-in-out infinite; } +.runner-arm-back { animation: runner-arm-back 0.42s ease-in-out infinite; } + +.runner-leg { + top: 51px; + height: 33px; +} + +.runner-leg-front { animation: runner-leg-front 0.42s ease-in-out infinite; } +.runner-leg-back { animation: runner-leg-back 0.42s ease-in-out infinite; } + +.runner-ground { + right: 6px; + bottom: 2px; + width: 70px; + height: 4px; + overflow: hidden; + border-radius: 999px; + background: rgba(30, 207, 112, 0.18); +} + +.runner-ground::before, +.runner-ground::after { + content: ""; + position: absolute; + top: 0; + width: 24px; + height: 4px; + border-radius: 999px; + background: currentColor; + animation: runner-ground 0.6s linear infinite; +} + +.runner-ground::after { + animation-delay: 0.3s; +} + +.loading-copy { + color: #139a55; + font-size: 15px; + font-weight: 900; + letter-spacing: 0; +} + +.toast { + position: fixed; + left: 50%; + bottom: calc(28px + env(safe-area-inset-bottom)); + z-index: 50; + max-width: 86vw; + transform: translateX(-50%); + padding: 10px 14px; + border-radius: 8px; + color: #fff; + background: rgba(0, 0, 0, 0.75); + font-size: 13px; + font-weight: 700; + text-align: center; +} + +.home-indicator { + position: absolute; + left: 50%; + bottom: 8px; + width: 154px; + height: 5px; + border-radius: 999px; + background: #050506; + transform: translateX(-50%); +} + +.modal-open { + overflow: hidden; +} + +[dir="rtl"] .content { + direction: rtl; +} + +[dir="rtl"] .profile-copy { + margin-right: 16px; + margin-left: 0; + text-align: right; +} + +[dir="rtl"] .member-side { + text-align: left; +} + +[dir="rtl"] .status-tag { + right: 0; + left: auto; + border-radius: 0 8px 0 8px; +} + +[dir="rtl"] .language-menu { + right: auto; + left: 0; +} + +@keyframes runner-bob { + 0%, + 100% { transform: translateY(0); } + 50% { transform: translateY(-5px); } +} + +@keyframes runner-body { + 0%, + 100% { transform: rotate(-7deg); } + 50% { transform: rotate(7deg); } +} + +@keyframes runner-arm-front { + 0%, + 100% { transform: rotate(62deg); } + 50% { transform: rotate(-58deg); } +} + +@keyframes runner-arm-back { + 0%, + 100% { transform: rotate(-58deg); } + 50% { transform: rotate(62deg); } +} + +@keyframes runner-leg-front { + 0%, + 100% { transform: rotate(-58deg); } + 50% { transform: rotate(54deg); } +} + +@keyframes runner-leg-back { + 0%, + 100% { transform: rotate(54deg); } + 50% { transform: rotate(-58deg); } +} + +@keyframes runner-ground { + 0% { transform: translateX(78px); } + 100% { transform: translateX(-30px); } +} + +@media (max-width: 360px) { + .status-bar { + padding-right: 22px; + padding-left: 30px; + } + + .content { + padding-right: 12px; + padding-left: 12px; + } + + .member-row { + grid-template-columns: 40px minmax(0, 1fr); + } + + .member-side { + grid-column: 2; + text-align: left; + } +} diff --git a/h5/hyapp/bd-leader-center/index.html b/h5/hyapp/bd-leader-center/index.html index 8052167..1f674dc 100644 --- a/h5/hyapp/bd-leader-center/index.html +++ b/h5/hyapp/bd-leader-center/index.html @@ -4,7 +4,7 @@ BD Leader Center - +
@@ -54,7 +54,6 @@
Salary
-
-
@@ -97,7 +96,7 @@
- +
@@ -179,6 +178,6 @@
- + diff --git a/h5/hyapp/bd-leader-center/script.js b/h5/hyapp/bd-leader-center/script.js index 8d118a3..dff0ca4 100644 --- a/h5/hyapp/bd-leader-center/script.js +++ b/h5/hyapp/bd-leader-center/script.js @@ -22,7 +22,7 @@ bd_number: "BD number", team_salary: "Team salary", bd_leader_history: "Salary History", - agency_list_link: "Agency list", + agency_list_link: "Agency List", bd_list_link: "BD List", invite_become_agent: "Invite Agent", invite_become_bd: "Invite BD", @@ -465,10 +465,8 @@ function renderBill() { const bill = state.bill || {}; const members = Array.isArray(bill.memberBillList) ? bill.memberBillList : []; - const billTitle = document.querySelector("#billTitle"); const bdNumber = document.querySelector("#bdNumber"); const teamSalary = document.querySelector("#teamSalary"); - if (billTitle) billTitle.textContent = bill.billTitle || "-"; if (bdNumber) bdNumber.textContent = formatCount(bill.memberCount ?? bill.bdNumber ?? members.length); if (teamSalary) teamSalary.textContent = formatMoney(bill.totalSalary ?? bill.teamSalary ?? 0); renderMembers(members);