2026-04-29 00:14:49 +08:00

941 lines
31 KiB
JavaScript

(function () {
const API_BASE_URL = "https://jvapi.haiyihy.com";
const supportedLanguages = ["en", "ar", "tr", "id"];
const languageLabels = {
en: "EN",
ar: "AR",
tr: "TR",
id: "ID"
};
const translations = {
en: {
document_title: "Salary Bills",
page_label: "Salary Bills",
back: "Back",
title: "Salary Bills",
language_button_aria: "Change language",
history_team_income: "History team income",
bill_summary: "Bill summary",
total_bills: "Total bills",
completed_bills: "Completed",
work_report: "Work report",
host_salary: "Host salary",
agent_salary: "Agent salary",
total: "Total",
more: "More",
team_member: "Team member",
enter_host_id: "Enter host ID",
search: "Search",
cancel: "Cancel",
close: "Close",
help: "Help",
open_help: "Open help",
work_report_description: "Work report shows each team bill cycle and salary summary.",
status_description: "Status describes the current settlement state.",
status_completed: "Completed: salary has been settled.",
status_in_progress: "In progress: the bill is still running.",
status_out_of_account: "Out of account: the bill is suspended or unavailable.",
completed: "Completed",
in_progress: "In progress",
out_of_account: "Out of account",
pending: "Pending",
target: "Target",
days: "Days",
uid_prefix: "UID:",
gift_points: "Gift points",
loading: "Loading",
no_bills: "No bills",
no_members: "No hosts",
no_search_result: "No matching hosts",
missing_token: "Missing token",
missing_token_text: "Open this page from the app or add a token in the URL.",
missing_team: "Missing team information",
failed_to_load_data: "Failed to load data",
unknown_user: "Unknown user"
},
ar: {
document_title: "Salary Bills",
page_label: "Salary Bills",
back: "رجوع",
title: "Salary Bills",
language_button_aria: "تغيير اللغة",
history_team_income: "دخل الفريق السابق",
bill_summary: "ملخص الفواتير",
total_bills: "إجمالي الفواتير",
completed_bills: "مكتمل",
work_report: "تقرير العمل",
host_salary: "راتب المضيف",
agent_salary: "راتب الوكالة",
total: "الإجمالي",
more: "المزيد",
team_member: "عضو الفريق",
enter_host_id: "أدخل معرف المضيف",
search: "بحث",
cancel: "إلغاء",
close: "إغلاق",
help: "مساعدة",
open_help: "فتح المساعدة",
work_report_description: "يعرض تقرير العمل كل دورة فاتورة للفريق وملخص الراتب.",
status_description: "توضح الحالة وضع التسوية الحالي.",
status_completed: "مكتمل: تم تسوية الراتب.",
status_in_progress: "قيد التنفيذ: لا تزال الفاتورة قيد التشغيل.",
status_out_of_account: "خارج الحساب: الفاتورة موقوفة أو غير متاحة.",
completed: "مكتمل",
in_progress: "قيد التنفيذ",
out_of_account: "خارج الحساب",
pending: "قيد الانتظار",
target: "الهدف",
days: "الأيام",
uid_prefix: "UID:",
gift_points: "نقاط الهدايا",
loading: "جار التحميل",
no_bills: "لا توجد فواتير",
no_members: "لا يوجد مضيفون",
no_search_result: "لا توجد نتائج مطابقة",
missing_token: "رمز الدخول مفقود",
missing_token_text: "افتح هذه الصفحة من التطبيق أو أضف token في الرابط.",
missing_team: "معلومات الفريق مفقودة",
failed_to_load_data: "فشل تحميل البيانات",
unknown_user: "مستخدم غير معروف"
},
tr: {
document_title: "Salary Bills",
page_label: "Salary Bills",
back: "Geri",
title: "Salary Bills",
language_button_aria: "Dili degistir",
history_team_income: "Gecmis takim geliri",
bill_summary: "Fatura ozeti",
total_bills: "Toplam fatura",
completed_bills: "Tamamlandi",
work_report: "Calisma raporu",
host_salary: "Host maasi",
agent_salary: "Ajans maasi",
total: "Toplam",
more: "Daha fazla",
team_member: "Takim uyesi",
enter_host_id: "Host ID girin",
search: "Ara",
cancel: "Iptal",
close: "Kapat",
help: "Yardim",
open_help: "Yardimi ac",
work_report_description: "Calisma raporu her takim fatura dongusunu ve maas ozetini gosterir.",
status_description: "Durum mevcut odeme surecini aciklar.",
status_completed: "Tamamlandi: maas odendi.",
status_in_progress: "Devam ediyor: fatura hala calisiyor.",
status_out_of_account: "Hesap disi: fatura askida veya kullanilamiyor.",
completed: "Tamamlandi",
in_progress: "Devam ediyor",
out_of_account: "Hesap disi",
pending: "Beklemede",
target: "Hedef",
days: "Gun",
uid_prefix: "UID:",
gift_points: "Hediye puani",
loading: "Yukleniyor",
no_bills: "Fatura yok",
no_members: "Host yok",
no_search_result: "Eslesen host yok",
missing_token: "Token eksik",
missing_token_text: "Bu sayfayi uygulamadan acin veya URL'ye token ekleyin.",
missing_team: "Takim bilgisi eksik",
failed_to_load_data: "Veri yuklenemedi",
unknown_user: "Bilinmeyen kullanici"
},
id: {
document_title: "Salary Bills",
page_label: "Salary Bills",
back: "Kembali",
title: "Salary Bills",
language_button_aria: "Ubah bahasa",
history_team_income: "Riwayat pendapatan tim",
bill_summary: "Ringkasan tagihan",
total_bills: "Total tagihan",
completed_bills: "Selesai",
work_report: "Laporan kerja",
host_salary: "Gaji host",
agent_salary: "Gaji agensi",
total: "Total",
more: "Lainnya",
team_member: "Anggota tim",
enter_host_id: "Masukkan ID host",
search: "Cari",
cancel: "Batal",
close: "Tutup",
help: "Bantuan",
open_help: "Buka bantuan",
work_report_description: "Laporan kerja menampilkan setiap siklus tagihan tim dan ringkasan gaji.",
status_description: "Status menjelaskan kondisi penyelesaian saat ini.",
status_completed: "Selesai: gaji telah diselesaikan.",
status_in_progress: "Berjalan: tagihan masih berjalan.",
status_out_of_account: "Di luar akun: tagihan ditangguhkan atau tidak tersedia.",
completed: "Selesai",
in_progress: "Berjalan",
out_of_account: "Di luar akun",
pending: "Tertunda",
target: "Target",
days: "Hari",
uid_prefix: "UID:",
gift_points: "Poin hadiah",
loading: "Memuat",
no_bills: "Tidak ada tagihan",
no_members: "Tidak ada host",
no_search_result: "Tidak ada host yang cocok",
missing_token: "Token tidak ditemukan",
missing_token_text: "Buka halaman ini dari aplikasi atau tambahkan token di URL.",
missing_team: "Informasi tim tidak ditemukan",
failed_to_load_data: "Gagal memuat data",
unknown_user: "Pengguna tidak dikenal"
}
};
const params = readURLParams();
let currentLang = normalizeLanguage(params.get("lang"));
let currentMessages = translations[currentLang] || translations.en;
let currentTeamId = params.get("teamId") || params.get("id") || "";
let bills = [];
let currentPolicy = [];
let selectedBill = null;
let selectedMembers = [];
let displayMembers = [];
let memberSearchActive = false;
let detailLoading = false;
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();
return supportedLanguages.includes(value) ? value : "en";
}
function t(key, fallback) {
return currentMessages[key] || translations.en[key] || fallback || key;
}
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(t("missing_token", "Missing token"));
const headers = authHeadersFromToken(token);
const method = options.method || "GET";
const hasBody = options.body !== undefined;
if (hasBody) headers["Content-Type"] = "application/json";
const response = await fetch(new URL(path, `${apiBaseURL()}/`).toString(), {
method,
cache: "no-store",
headers,
body: hasBody ? JSON.stringify(options.body) : undefined
});
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 buildPageURL(target) {
const url = new URL(target, window.location.href);
const nextParams = new URLSearchParams(url.search);
params.forEach((value, key) => {
if (!nextParams.has(key)) nextParams.set(key, value);
});
const token = readRawParam("token");
if (token) nextParams.set("token", token);
if (currentLang) nextParams.set("lang", currentLang);
if (currentTeamId) nextParams.set("teamId", currentTeamId);
url.search = nextParams.toString();
return url.toString();
}
function setLoading(isLoading) {
const root = document.querySelector(".hosts-bill-page");
const loadingMask = document.querySelector("#loadingMask");
if (root) root.dataset.loading = isLoading ? "true" : "false";
if (loadingMask) {
loadingMask.hidden = !isLoading;
loadingMask.setAttribute("aria-busy", String(isLoading));
}
}
function renderLanguageControls() {
const languageButton = document.querySelector(".language-button");
if (languageButton) {
languageButton.textContent = languageLabels[currentLang];
languageButton.setAttribute("aria-label", t("language_button_aria"));
}
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
const isActive = button.dataset.lang === currentLang;
button.classList.toggle("is-active", isActive);
button.setAttribute("aria-pressed", String(isActive));
});
}
function renderMessages() {
document.querySelectorAll("[data-i18n]").forEach((node) => {
const value = currentMessages[node.dataset.i18n];
if (value) node.textContent = value;
});
document.querySelectorAll("[data-i18n-aria]").forEach((node) => {
const value = currentMessages[node.dataset.i18nAria];
if (value) node.setAttribute("aria-label", value);
});
document.querySelectorAll("[data-i18n-placeholder]").forEach((node) => {
const value = currentMessages[node.dataset.i18nPlaceholder];
if (value) node.setAttribute("placeholder", value);
});
}
function applyLanguage(lang) {
currentLang = normalizeLanguage(lang);
currentMessages = translations[currentLang] || translations.en;
document.documentElement.lang = currentLang;
document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr";
document.title = t("document_title");
renderMessages();
renderLanguageControls();
renderSummary();
renderBills();
renderBillDetail();
}
function setLanguageMenuOpen(open) {
const languageButton = document.querySelector(".language-button");
const languageMenu = document.querySelector(".language-menu");
if (!languageButton || !languageMenu) return;
languageMenu.hidden = !open;
languageButton.setAttribute("aria-expanded", String(open));
}
function pickTeamInfo(body) {
return body?.teamProfile || body || null;
}
function pickTeamId(teamInfo) {
return teamInfo?.id || teamInfo?.teamId || teamInfo?.teamProfile?.id || "";
}
function pickRecords(body) {
if (Array.isArray(body)) return body;
if (Array.isArray(body?.records)) return body.records;
if (Array.isArray(body?.list)) return body.list;
if (Array.isArray(body?.items)) return body.items;
return [];
}
function pickPolicyRecords(body) {
if (Array.isArray(body)) return body;
if (Array.isArray(body?.policy)) return body.policy;
if (Array.isArray(body?.policies)) return body.policies;
return pickRecords(body);
}
function formatMoney(value) {
const number = Number(value);
return Number.isFinite(number) ? `$${number.toFixed(2)}` : "$0.00";
}
function formatPlainMoney(value) {
const number = Number(value);
return Number.isFinite(number) ? number.toFixed(2) : "0.00";
}
function formatCount(value) {
if (value === null || value === undefined || value === "") return "0";
const number = Number(value);
if (!Number.isFinite(number)) return String(value);
return Math.trunc(number).toLocaleString("en-US");
}
function formatGift(value) {
if (value === null || value === undefined || value === "") return "0";
const number = Number(value);
if (!Number.isFinite(number)) return String(value);
return Math.trunc(number).toLocaleString("en-US");
}
function normalizeStatus(status) {
switch (String(status || "").toUpperCase()) {
case "SETTLED":
case "PAY_OUT":
case "COMPLETED":
return { key: "completed", className: "status-completed" };
case "HANG_UP":
case "OUT_OF_ACCOUNT":
case "OUT OF ACCOUNT":
return { key: "out_of_account", className: "status-out-of-account" };
case "PENDING":
return { key: "pending", className: "status-pending" };
case "UNPAID":
case "IN_PROGRESS":
case "IN PROGRESS":
default:
return { key: "in_progress", className: "status-in-progress" };
}
}
function statusText(status) {
const info = normalizeStatus(status);
return t(info.key, status || "-");
}
function setStatusBadge(node, status) {
if (!node) return;
const info = normalizeStatus(status);
node.className = `bill-status-badge ${info.className}`;
node.textContent = t(info.key);
}
function billTitle(bill) {
return bill?.billTitle || bill?.billDayTitle || String(bill?.billBelong || "-");
}
function billTotal(bill) {
return Number(bill?.settleResult?.totalSalary || 0);
}
function billMemberSalary(bill) {
return Number(bill?.settleResult?.memberSalary || 0);
}
function billAgentSalary(bill) {
return Number(bill?.settleResult?.ownSalary || 0);
}
function isCompletedBill(bill) {
const status = String(bill?.status || "").toUpperCase();
return status === "SETTLED" || status === "PAY_OUT" || status === "COMPLETED";
}
function calculateHistoryIncome() {
return bills.reduce((sum, bill) => {
if (!isCompletedBill(bill)) return sum;
return sum + billTotal(bill);
}, 0);
}
function replaceNodeChildren(node, children) {
while (node.firstChild) node.removeChild(node.firstChild);
children.forEach((child) => node.appendChild(child));
}
function createSvg(paths) {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("aria-hidden", "true");
paths.forEach((pathValue) => {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", pathValue);
svg.appendChild(path);
});
return svg;
}
function renderSummary() {
const historyIncome = document.querySelector("#historyIncome");
const totalBills = document.querySelector("#totalBills");
const completedBills = document.querySelector("#completedBills");
if (historyIncome) historyIncome.textContent = formatMoney(calculateHistoryIncome());
if (totalBills) totalBills.textContent = formatCount(bills.length);
if (completedBills) completedBills.textContent = formatCount(bills.filter(isCompletedBill).length);
}
function createMoneyItem(label, value) {
const item = document.createElement("div");
item.className = "bill-money-item";
const labelNode = document.createElement("span");
labelNode.textContent = label;
const valueNode = document.createElement("strong");
valueNode.textContent = formatMoney(value);
item.appendChild(labelNode);
item.appendChild(valueNode);
return item;
}
function createBillCard(bill) {
const card = document.createElement("article");
card.className = "bill-card";
const top = document.createElement("div");
top.className = "bill-card-top";
const date = document.createElement("div");
date.className = "bill-date";
date.textContent = billTitle(bill);
const badge = document.createElement("span");
setStatusBadge(badge, bill?.status);
top.appendChild(date);
top.appendChild(badge);
const moneyGrid = document.createElement("div");
moneyGrid.className = "bill-money-grid";
moneyGrid.appendChild(createMoneyItem(t("host_salary"), billMemberSalary(bill)));
moneyGrid.appendChild(createMoneyItem(t("agent_salary"), billAgentSalary(bill)));
moneyGrid.appendChild(createMoneyItem(t("total"), billTotal(bill)));
const more = document.createElement("button");
more.type = "button";
more.className = "bill-more-button";
more.textContent = t("more");
more.addEventListener("click", () => {
openBillDetail(bill);
});
card.appendChild(top);
card.appendChild(moneyGrid);
card.appendChild(more);
return card;
}
function setListStatus(message) {
const status = document.querySelector("#listStatus");
if (status) status.textContent = message || "";
}
function renderBills() {
renderSummary();
const list = document.querySelector("#billList");
if (!list) return;
replaceNodeChildren(list, bills.map(createBillCard));
setListStatus(bills.length ? "" : t("no_bills"));
}
function pickMemberProfile(member) {
return member?.memberProfile || member?.userProfile || member?.profile || member?.user || {};
}
function pickMemberTarget(member) {
return member?.target || member?.memberTarget || {};
}
function pickMemberName(profile, member) {
return profile?.userNickname || profile?.name || member?.userNickname || member?.name || t("unknown_user");
}
function pickMemberAccount(profile, member) {
return profile?.ownSpecialId?.account || profile?.account || member?.account || member?.userAccount || profile?.id || member?.userId || "-";
}
function memberIdentifiers(member) {
const profile = pickMemberProfile(member);
return [
profile?.id,
profile?.account,
profile?.ownSpecialId?.account,
member?.userId,
member?.account,
member?.userAccount
]
.filter((value) => value !== null && value !== undefined && value !== "")
.map((value) => String(value));
}
function calculateTargetLevel(acceptGiftValue) {
if (!Array.isArray(currentPolicy) || !currentPolicy.length) return "N/A";
const giftValue = Number(acceptGiftValue);
if (!Number.isFinite(giftValue)) return "N/A";
const sortedPolicy = currentPolicy
.slice()
.filter((item) => item && item.target !== null && item.target !== undefined)
.sort((left, right) => Number(right.target) - Number(left.target));
for (const policy of sortedPolicy) {
if (giftValue >= Number(policy.target)) return policy.level ?? "N/A";
}
const levels = sortedPolicy
.map((policy) => Number(policy.level))
.filter((level) => Number.isFinite(level));
return levels.length ? Math.min(...levels) : "N/A";
}
function createMemberAvatar(name, avatarURL) {
const avatar = document.createElement("div");
avatar.className = "bill-member-avatar";
const fallback = document.createElement("span");
fallback.textContent = (name || t("unknown_user")).slice(0, 1).toUpperCase();
avatar.appendChild(fallback);
if (avatarURL) {
const image = document.createElement("img");
image.src = avatarURL;
image.alt = name;
image.addEventListener("error", () => {
image.remove();
});
avatar.appendChild(image);
}
return avatar;
}
function createMemberCard(member) {
const profile = pickMemberProfile(member);
const target = pickMemberTarget(member);
const name = pickMemberName(profile, member);
const account = pickMemberAccount(profile, member);
const giftValue = target?.acceptGiftValue || 0;
const effectiveDay = target?.effectiveDay || 0;
const card = document.createElement("article");
card.className = "bill-member-card";
const user = document.createElement("div");
user.className = "bill-member-user";
user.appendChild(createMemberAvatar(name, profile?.userAvatar || member?.userAvatar || member?.avatar || ""));
const copy = document.createElement("div");
copy.className = "bill-member-copy";
const nameNode = document.createElement("div");
nameNode.className = "bill-member-name";
nameNode.textContent = name;
const accountNode = document.createElement("div");
accountNode.className = "bill-member-account";
accountNode.textContent = `${t("uid_prefix")} ${account}`.trim();
copy.appendChild(nameNode);
copy.appendChild(accountNode);
user.appendChild(copy);
const side = document.createElement("div");
side.className = "bill-member-side";
const level = document.createElement("span");
level.className = "bill-member-target";
level.textContent = `${t("target")}: ${calculateTargetLevel(giftValue)}`;
const gift = document.createElement("div");
gift.className = "bill-member-gift";
gift.textContent = formatGift(giftValue);
gift.title = `${t("gift_points")}: ${formatGift(giftValue)}`;
const days = document.createElement("div");
days.className = "bill-member-days";
days.textContent = `${t("days")}: ${effectiveDay}`;
side.appendChild(level);
side.appendChild(gift);
side.appendChild(days);
card.appendChild(user);
card.appendChild(side);
return card;
}
function setMemberStatus(message) {
const status = document.querySelector("#memberStatus");
if (status) status.textContent = message || "";
}
function renderSearchState() {
const cancelButton = document.querySelector("#memberCancelButton");
if (cancelButton) cancelButton.hidden = !memberSearchActive;
}
function renderBillDetail() {
const date = document.querySelector("#detailBillDate");
const total = document.querySelector("#detailBillTotal");
const status = document.querySelector("#detailBillStatus");
const list = document.querySelector("#billMemberList");
if (date) date.textContent = selectedBill ? billTitle(selectedBill) : "-";
if (total) total.textContent = selectedBill ? formatMoney(billTotal(selectedBill)) : "$0.00";
if (status && selectedBill) setStatusBadge(status, selectedBill.status);
if (list) replaceNodeChildren(list, displayMembers.map(createMemberCard));
renderSearchState();
if (detailLoading) {
setMemberStatus(`${t("loading")}...`);
return;
}
if (!displayMembers.length) {
setMemberStatus(memberSearchActive ? t("no_search_result") : t("no_members"));
return;
}
setMemberStatus("");
}
async function ensureTeamId() {
if (currentTeamId) return currentTeamId;
const body = await requestJSON("/team/entry");
currentTeamId = pickTeamId(pickTeamInfo(body));
return currentTeamId;
}
async function fetchBills() {
const query = new URLSearchParams({ id: String(currentTeamId) });
const body = await requestJSON(`/team/bill?${query.toString()}`);
bills = pickRecords(body);
renderBills();
}
async function fetchPolicy() {
const query = new URLSearchParams({ id: String(currentTeamId) });
const body = await requestJSON(`/team/release/policy?${query.toString()}`);
currentPolicy = pickPolicyRecords(body);
}
async function fetchBillMembers(billId) {
const query = new URLSearchParams({ id: String(billId) });
const body = await requestJSON(`/team/bill/work-members?${query.toString()}`);
return pickRecords(body);
}
async function openBillDetail(bill) {
selectedBill = bill;
selectedMembers = [];
displayMembers = [];
memberSearchActive = false;
detailLoading = true;
const input = document.querySelector("#memberSearchInput");
if (input) input.value = "";
const modal = document.querySelector("#billDetailModal");
if (modal) modal.hidden = false;
document.body.classList.add("daily-modal-open");
renderBillDetail();
try {
selectedMembers = await fetchBillMembers(bill.id);
displayMembers = selectedMembers.slice();
} catch (error) {
console.error("Failed to load bill members:", error);
showToast(error.message || t("failed_to_load_data"));
selectedMembers = [];
displayMembers = [];
} finally {
detailLoading = false;
renderBillDetail();
}
}
function setBillDetailOpen(open) {
const modal = document.querySelector("#billDetailModal");
if (!modal) return;
modal.hidden = !open;
document.body.classList.toggle("daily-modal-open", open || isHelpOpen());
if (!open) {
selectedBill = null;
selectedMembers = [];
displayMembers = [];
memberSearchActive = false;
detailLoading = false;
}
}
function isHelpOpen() {
return !document.querySelector("#helpModal")?.hidden;
}
function setHelpOpen(open) {
const modal = document.querySelector("#helpModal");
if (!modal) return;
modal.hidden = !open;
document.body.classList.toggle("daily-modal-open", open || !document.querySelector("#billDetailModal")?.hidden);
}
function searchMembers() {
const input = document.querySelector("#memberSearchInput");
const query = String(input?.value || "").trim();
if (!query) {
cancelMemberSearch();
return;
}
memberSearchActive = true;
displayMembers = selectedMembers.filter((member) => memberIdentifiers(member).some((id) => id === query));
renderBillDetail();
}
function cancelMemberSearch() {
const input = document.querySelector("#memberSearchInput");
if (input) input.value = "";
memberSearchActive = false;
displayMembers = selectedMembers.slice();
renderBillDetail();
}
function showToast(message) {
const toast = document.querySelector("#toast");
if (!toast) return;
toast.textContent = message;
toast.hidden = false;
window.clearTimeout(showToast.timer);
showToast.timer = window.setTimeout(() => {
toast.hidden = true;
}, 2200);
}
function goBack() {
if (window.history.length > 1) {
window.history.back();
return;
}
window.location.href = buildPageURL("../agency-center/index.html");
}
function bindEvents() {
document.querySelector(".back-button")?.addEventListener("click", goBack);
document.querySelector(".language-button")?.addEventListener("click", (event) => {
event.stopPropagation();
const languageMenu = document.querySelector(".language-menu");
setLanguageMenuOpen(Boolean(languageMenu?.hidden));
});
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
button.addEventListener("click", (event) => {
event.stopPropagation();
setLanguageMenuOpen(false);
applyLanguage(button.dataset.lang);
});
});
document.querySelector(".bill-help-button")?.addEventListener("click", () => {
setHelpOpen(true);
});
document.querySelector("#memberSearchForm")?.addEventListener("submit", (event) => {
event.preventDefault();
searchMembers();
});
document.querySelector("#memberCancelButton")?.addEventListener("click", cancelMemberSearch);
document.querySelectorAll("[data-close-detail]").forEach((node) => {
node.addEventListener("click", () => setBillDetailOpen(false));
});
document.querySelectorAll("[data-close-help]").forEach((node) => {
node.addEventListener("click", () => setHelpOpen(false));
});
document.addEventListener("click", () => {
setLanguageMenuOpen(false);
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
setLanguageMenuOpen(false);
setBillDetailOpen(false);
setHelpOpen(false);
}
});
}
async function initializePage() {
setLoading(true);
applyLanguage(currentLang);
bindEvents();
if (!readRawParam("token")) {
setLoading(false);
setListStatus(t("missing_token_text"));
return;
}
try {
await ensureTeamId();
if (!currentTeamId) throw new Error(t("missing_team"));
const results = await Promise.allSettled([
fetchBills(),
fetchPolicy()
]);
results.forEach((result) => {
if (result.status === "rejected") console.warn("Hosts bill request failed:", result.reason);
});
} catch (error) {
console.error("Failed to initialize hosts bill:", error);
showToast(error.message || t("failed_to_load_data"));
setListStatus(error.message || t("failed_to_load_data"));
} finally {
setLoading(false);
}
}
initializePage();
})();