662 lines
24 KiB
JavaScript
662 lines
24 KiB
JavaScript
export const API_BASE_URL = "https://jvapi.haiyihy.com";
|
||
|
||
const text = {
|
||
en: {
|
||
bd_leader_center: "BD Leader Center",
|
||
available_salaries: "Available salaries",
|
||
bd: "BD",
|
||
bd_leader: "BD Leader",
|
||
my_bd_salary: "My BD salary",
|
||
my_bd_leader_salary: "My BD leader salary",
|
||
exchange: "Exchange",
|
||
transfer: "Transfer",
|
||
cash_out: "Cash out",
|
||
details: "Details",
|
||
please_select_amount: "Please select amount",
|
||
please_select_coin_amount: "Please select coin amount",
|
||
please_select_payee: "Please select payee",
|
||
transfer_dollar_success: "Transfer succeeded",
|
||
transfer_dollar_failed: "Transfer failed",
|
||
transfer_error: "Transfer error",
|
||
exchange_success: "Exchange succeeded",
|
||
exchange_error_occurred: "Exchange failed",
|
||
amount: "Amount",
|
||
coins: "Coins",
|
||
dollar: "Dollar",
|
||
minimum_cash_out: "Minimum cash out is $50",
|
||
bd_policy_link: "BD policy",
|
||
bd_leader_policy: "BD leader policy",
|
||
bd_policy: "BD policy",
|
||
agency_list_link: "Agency list",
|
||
bd_list_link: "BD List",
|
||
invite_become_agent: "Invite to become agent",
|
||
invite_become_BD: "Invite to become BD",
|
||
bank_cards_link: "Bank cards",
|
||
bank_card: "Bank Card",
|
||
kyc: "KYC",
|
||
category: "Category",
|
||
card_number_label: "Card number",
|
||
card_number: "Card number",
|
||
payee: "Payee",
|
||
more: "More",
|
||
add_payment_method: "Add payment method",
|
||
complete_information: "Complete information",
|
||
passport_id_card_title: "Passport/ID card",
|
||
contact_number: "Contact number",
|
||
other_description: "Other description",
|
||
review_passed: "Review passed",
|
||
review_failed: "Review failed",
|
||
awaiting_review: "Awaiting review",
|
||
currently_in_use: "Currently in use",
|
||
all_bank_cards: "All bank cards",
|
||
no_bank_cards_in_use: "No bank cards in use",
|
||
no_bank_cards_added: "No bank cards added",
|
||
add_new_payment_method: "Add new payment method",
|
||
withdrawal_method: "Withdrawal method",
|
||
choose_withdrawal_method: "Choose withdrawal method",
|
||
card_name: "Card name",
|
||
enter_card_name: "Enter card name",
|
||
enter_card_number: "Enter card number",
|
||
enter_payee: "Enter payee",
|
||
delete: "Delete",
|
||
use: "Use",
|
||
using: "Using",
|
||
confirm: "Confirm",
|
||
submit: "Submit",
|
||
change_kyc: "Change KYC",
|
||
enter_contact_number: "Enter contact number",
|
||
enter_other_description: "Enter other description",
|
||
current_status: "Current status",
|
||
tips: "Tips",
|
||
confirm_modify_kyc: "Confirm modify KYC?",
|
||
cancel: "Cancel",
|
||
application_submitted: "Application submitted",
|
||
agency_number: "Agency number",
|
||
number_of_bd: "BD number",
|
||
bd_number: "BD number",
|
||
team_salary: "Team salary",
|
||
team_recharge: "Team recharge",
|
||
salary: "Salary",
|
||
recharge: "Recharge",
|
||
host: "Host",
|
||
agencies: "Agencies",
|
||
user_id_prefix: "ID: ",
|
||
revenue_details: "Revenue details",
|
||
bd_leader_history: "BD Leader history",
|
||
total_income: "Total income",
|
||
previous_period_income: "Previous period income",
|
||
income_details_label: "Income details",
|
||
team_top_up: "Team top up",
|
||
bd_income_salary: "BD income salary",
|
||
bd_income_top_up: "BD income top up",
|
||
total_team_salary: "Total team salary",
|
||
num_teams: "Num teams",
|
||
in_progress: "In Progress",
|
||
pending: "Pending",
|
||
completed: "Completed",
|
||
out_of_account: "Out of account",
|
||
information: "Information",
|
||
enter_user_id: "Enter User ID",
|
||
invite: "Invite",
|
||
no_users_found: "No users found",
|
||
success: "Success",
|
||
invitation_submitted: "Invitation submitted",
|
||
application_cancelled: "Application cancelled",
|
||
something_went_wrong: "Something went wrong",
|
||
cancellation_failed_try_again: "Cancellation failed, try again"
|
||
,
|
||
item_distribution: "Item distribution",
|
||
frames: "Frames",
|
||
vehicles: "Vehicles",
|
||
vip: "VIP",
|
||
send: "Send",
|
||
validity: "Validity",
|
||
day: " day",
|
||
enter_user_id_placeholder: "Enter user ID",
|
||
gift_delivery_successful: "Gift delivery successful",
|
||
available_salaries: "Available salaries",
|
||
details: "Details",
|
||
bd: "BD",
|
||
bd_leader: "BD Leader",
|
||
my_bd_salary: "My BD salary",
|
||
my_bd_leader_salary: "My BD leader salary",
|
||
exchange: "Exchange",
|
||
transfer: "Transfer",
|
||
cash_out: "Cash out",
|
||
please_select_amount: "Please select amount",
|
||
please_select_payee: "Please select payee",
|
||
transfer_dollar_success: "Transfer successful",
|
||
transfer_dollar_failed: "Transfer failed",
|
||
transfer_error: "Transfer error",
|
||
please_select_coin_amount: "Please select coin amount",
|
||
exchange_success: "Exchange successful",
|
||
exchange_error_occurred: "Exchange failed",
|
||
confirm_exchange: "Confirm exchange?",
|
||
fill_info_tips: "Please complete bank card and KYC information",
|
||
card_issue_tips: "Bank card information is required before cash out",
|
||
to_improve: "To improve",
|
||
cash_withdrawal_tips: "Cash withdrawal submitted",
|
||
enter_cash_out_amount: "Enter cash out amount",
|
||
cash_out_minimum: "Cash out minimum: $50",
|
||
change_withdrawal_method: "Change withdrawal method",
|
||
please_select_bank_card: "Please select bank card",
|
||
prompt: "Prompt",
|
||
failed_to_load_balance: "Failed to load balance"
|
||
},
|
||
ar: {},
|
||
tr: {},
|
||
bn: {}
|
||
};
|
||
|
||
["ar", "tr", "bn"].forEach((lang) => {
|
||
text[lang] = { ...text.en };
|
||
});
|
||
|
||
let headers = {
|
||
authorization: "",
|
||
"req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google",
|
||
"req-client": "Android",
|
||
"req-imei": "8fa54d728ab449e04f9292329ed44bda",
|
||
"req-lang": "id-ID",
|
||
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
|
||
"req-version": "V2",
|
||
"req-zone": "Asia/Shanghai",
|
||
"content-type": "application/json"
|
||
};
|
||
|
||
export const lang = resolveLanguage();
|
||
let appConnectionPromise = null;
|
||
|
||
export function t(key) {
|
||
return text[lang]?.[key] || text.en[key] || key;
|
||
}
|
||
|
||
export function applyLang() {
|
||
document.documentElement.lang = lang;
|
||
document.documentElement.dir = lang === "ar" ? "rtl" : "ltr";
|
||
document.querySelectorAll("[data-i18n]").forEach((node) => {
|
||
node.textContent = t(node.dataset.i18n);
|
||
});
|
||
const language = document.querySelector("[data-action='language']");
|
||
if (language) language.textContent = lang.toUpperCase();
|
||
}
|
||
|
||
function resolveLanguage() {
|
||
const params = new URLSearchParams(window.location.search);
|
||
const hash = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
|
||
const hashParams = new URLSearchParams(hash);
|
||
const value = params.get("lang") || hashParams.get("lang") || navigator.language || "en";
|
||
if (value.startsWith("ar")) return "ar";
|
||
if (value.startsWith("tr")) return "tr";
|
||
if (value.startsWith("bn")) return "bn";
|
||
return "en";
|
||
}
|
||
|
||
function splitHeaderPairs(value) {
|
||
const result = {};
|
||
if (!value) return result;
|
||
value.split(";").forEach((item) => {
|
||
const index = item.indexOf("=");
|
||
if (index > -1) result[item.slice(0, index)] = item.slice(index + 1);
|
||
});
|
||
return result;
|
||
}
|
||
|
||
function mapHeaderValue(target, key, value) {
|
||
if (value === null || value === undefined || value === "" || target[key]) return;
|
||
target[key] = String(value);
|
||
}
|
||
|
||
function normalizeHeaderCandidate(value, depth = 0) {
|
||
if (depth > 4 || value === null || value === undefined) return {};
|
||
if (typeof value === "string") {
|
||
const trimmed = value.trim();
|
||
if (!trimmed) return {};
|
||
if (trimmed[0] === "{" || trimmed[0] === "[") {
|
||
try {
|
||
return normalizeHeaderCandidate(JSON.parse(trimmed), depth + 1);
|
||
} catch {
|
||
return {};
|
||
}
|
||
}
|
||
return /^bearer\s+/i.test(trimmed) ? { authorization: trimmed } : {};
|
||
}
|
||
if (Array.isArray(value)) {
|
||
return value.reduce((result, item) => ({ ...result, ...normalizeHeaderCandidate(item, depth + 1) }), {});
|
||
}
|
||
if (typeof value !== "object") return {};
|
||
|
||
const appIntel = splitHeaderPairs(value["Req-App-Intel"] || value["req-app-intel"]);
|
||
const sysOrigin = splitHeaderPairs(value["Req-Sys-Origin"] || value["req-sys-origin"]);
|
||
const normalized = {};
|
||
|
||
mapHeaderValue(normalized, "authorization", value.Authorization || value.authorization || value.token || value.accessToken);
|
||
mapHeaderValue(normalized, "reqLang", value["Req-Lang"] || value["req-lang"] || value.reqLang || value.lang || value.language);
|
||
mapHeaderValue(normalized, "appVersion", appIntel.version || value["App-Version"] || value.appVersion);
|
||
mapHeaderValue(normalized, "buildVersion", appIntel.build || value["Build-Version"] || value.buildVersion);
|
||
mapHeaderValue(normalized, "channel", appIntel.channel || value["App-Channel"] || value.appChannel || value.channel);
|
||
mapHeaderValue(normalized, "reqImei", value["Req-Imei"] || value["req-imei"] || value.reqImei || appIntel["Req-Imei"] || appIntel.reqImei);
|
||
mapHeaderValue(normalized, "origin", sysOrigin.origin || value["Sys-Origin"] || value.sysOrigin || value.origin);
|
||
mapHeaderValue(normalized, "child", sysOrigin.originChild || sysOrigin.child || value["Sys-Origin-Child"] || value.sysOriginChild || value.child);
|
||
|
||
["headerInfo", "headers", "auth", "data", "state"].forEach((key) => {
|
||
Object.assign(normalized, normalizeHeaderCandidate(value[key], depth + 1));
|
||
});
|
||
|
||
Object.keys(value).forEach((key) => {
|
||
if (["headerInfo", "headers", "auth", "data", "state"].includes(key)) return;
|
||
if (value[key] && typeof value[key] === "object") {
|
||
Object.assign(normalized, normalizeHeaderCandidate(value[key], depth + 1));
|
||
}
|
||
});
|
||
|
||
return normalized;
|
||
}
|
||
|
||
function collectHeaderCandidate(collected, value) {
|
||
const normalized = normalizeHeaderCandidate(value);
|
||
if (Object.keys(normalized).length) collected.push(normalized);
|
||
}
|
||
|
||
function collectHeadersFromStorage(collected, storage) {
|
||
if (!storage) return;
|
||
["authorization", "Authorization", "token", "accessToken", "headerInfo", "headersFromApp", "appHeaders", "user", "userInfo"].forEach((key) => {
|
||
try {
|
||
collectHeaderCandidate(collected, storage.getItem(key));
|
||
} catch {}
|
||
});
|
||
try {
|
||
for (let index = 0; index < storage.length; index += 1) {
|
||
collectHeaderCandidate(collected, storage.getItem(storage.key(index)));
|
||
}
|
||
} catch {}
|
||
}
|
||
|
||
function queryHeaderCandidate() {
|
||
const params = new URLSearchParams(window.location.search);
|
||
return {
|
||
authorization: params.get("authorization") || params.get("Authorization") || params.get("token"),
|
||
reqLang: params.get("lang"),
|
||
appVersion: params.get("appVersion"),
|
||
buildVersion: params.get("buildVersion"),
|
||
channel: params.get("appChannel") || params.get("channel"),
|
||
reqImei: params.get("reqImei"),
|
||
origin: params.get("sysOrigin"),
|
||
child: params.get("sysOriginChild")
|
||
};
|
||
}
|
||
|
||
function discoverAppHeaders() {
|
||
const collected = [{ reqLang: lang }, queryHeaderCandidate()];
|
||
collectHeaderCandidate(collected, window.__APP_HEADERS__);
|
||
collectHeaderCandidate(collected, window.__HEADER_INFO__);
|
||
collectHeaderCandidate(collected, window.headerInfo);
|
||
collectHeadersFromStorage(collected, window.localStorage);
|
||
collectHeadersFromStorage(collected, window.sessionStorage);
|
||
return collected.reduce((result, item) => ({ ...result, ...item }), {});
|
||
}
|
||
|
||
function applyDiscoveredHeaders() {
|
||
const discovered = discoverAppHeaders();
|
||
if (headers.authorization) delete discovered.authorization;
|
||
if (Object.keys(discovered).length) setHeadersFromApp(discovered);
|
||
return discovered;
|
||
}
|
||
|
||
export function setHeadersFromApp(rawOrObject) {
|
||
const data = normalizeHeaderCandidate(rawOrObject || {});
|
||
const next = {};
|
||
if (data.authorization) next.authorization = data.authorization;
|
||
if (data.reqLang) next["req-lang"] = data.reqLang;
|
||
if (data.appVersion || data.buildVersion || data.channel) {
|
||
next["req-app-intel"] = [
|
||
data.appVersion ? `version=${data.appVersion}` : "",
|
||
data.buildVersion ? `build=${data.buildVersion}` : "",
|
||
data.channel ? `channel=${data.channel}` : ""
|
||
].filter(Boolean).join(";");
|
||
}
|
||
if (data.reqImei) next["req-imei"] = data.reqImei;
|
||
if (data.origin) next["req-sys-origin"] = [`origin=${data.origin}`, data.child ? `child=${data.child}` : ""].filter(Boolean).join(";");
|
||
headers = { ...headers, ...next };
|
||
}
|
||
|
||
function isAppEnvironment() {
|
||
return Boolean(window.app || window.webkit || window.FlutterPageControl || window.FlutterApp);
|
||
}
|
||
|
||
function requestAccessOrigin() {
|
||
return new Promise((resolve) => {
|
||
let settled = false;
|
||
let timeoutId = null;
|
||
const finish = (raw) => {
|
||
if (settled) {
|
||
if (raw) setHeadersFromApp(raw);
|
||
return;
|
||
}
|
||
settled = true;
|
||
if (timeoutId) window.clearTimeout(timeoutId);
|
||
resolve(raw || null);
|
||
};
|
||
|
||
window.renderData = finish;
|
||
window.getIosAccessOriginParam = finish;
|
||
|
||
if (!isAppEnvironment()) {
|
||
setHeadersFromApp({
|
||
reqLang: lang,
|
||
appVersion: "1.0.0",
|
||
buildVersion: "1",
|
||
channel: "Web",
|
||
reqImei: "H5-STATIC",
|
||
origin: "LIKEI",
|
||
child: "LIKEI"
|
||
});
|
||
finish(null);
|
||
return;
|
||
}
|
||
|
||
const requestFromFlutter = () => {
|
||
if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") {
|
||
window.FlutterApp.postMessage("requestAccessOrigin");
|
||
}
|
||
};
|
||
|
||
if (window.app && typeof window.app.getAccessOrigin === "function") {
|
||
finish(window.app.getAccessOrigin());
|
||
return;
|
||
}
|
||
|
||
requestFromFlutter();
|
||
timeoutId = window.setTimeout(() => finish(null), 12000);
|
||
|
||
let attempt = 0;
|
||
const poll = () => {
|
||
attempt += 1;
|
||
if (window.app && typeof window.app.getAccessOrigin === "function") {
|
||
finish(window.app.getAccessOrigin());
|
||
return;
|
||
}
|
||
if (attempt < 50) {
|
||
window.setTimeout(poll, 100);
|
||
return;
|
||
}
|
||
requestFromFlutter();
|
||
};
|
||
|
||
poll();
|
||
});
|
||
}
|
||
|
||
export async function connectToApp() {
|
||
if (!appConnectionPromise) {
|
||
applyDiscoveredHeaders();
|
||
appConnectionPromise = requestAccessOrigin()
|
||
.then((raw) => {
|
||
if (raw) setHeadersFromApp(raw);
|
||
else applyDiscoveredHeaders();
|
||
return { success: true, environment: isAppEnvironment() ? "app" : "browser", authenticated: Boolean(headers.authorization) };
|
||
})
|
||
.finally(() => {
|
||
appConnectionPromise = null;
|
||
});
|
||
}
|
||
|
||
return appConnectionPromise;
|
||
}
|
||
|
||
export async function request(method, path, body) {
|
||
applyDiscoveredHeaders();
|
||
const options = { method, headers: { ...headers } };
|
||
if (body !== undefined) options.body = JSON.stringify(body);
|
||
const response = await fetch(`${API_BASE_URL}${path}`, options);
|
||
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;
|
||
}
|
||
|
||
export const get = (path) => request("GET", path);
|
||
export const post = (path, body) => request("POST", path, body);
|
||
|
||
export function navigateTo(target) {
|
||
const url = new URL(target, window.location.origin);
|
||
const currentLang = new URLSearchParams(window.location.search).get("lang");
|
||
if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang);
|
||
window.location.href = `${url.pathname}${url.search}${url.hash}`;
|
||
}
|
||
|
||
export function closePage() {
|
||
const params = new URLSearchParams(window.location.search);
|
||
const from = params.get("from");
|
||
if (from) {
|
||
const target = new URL(from, window.location.origin);
|
||
const activeAction = params.get("activeAction");
|
||
if (activeAction) target.searchParams.set("activeAction", activeAction);
|
||
const currentLang = params.get("lang");
|
||
if (currentLang && !target.searchParams.has("lang")) target.searchParams.set("lang", currentLang);
|
||
window.location.href = `${target.pathname}${target.search}${target.hash}`;
|
||
return;
|
||
}
|
||
if (window.history.length > 1) {
|
||
window.history.back();
|
||
return;
|
||
}
|
||
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");
|
||
}
|
||
}
|
||
|
||
export function bindHeader(titleKey) {
|
||
document.body.insertAdjacentHTML("afterbegin", `
|
||
<header class="header">
|
||
<button class="icon-btn" type="button" data-action="back" aria-label="Back"><img src="../bd-static/icons/back.svg" alt=""></button>
|
||
<h1>${t(titleKey)}</h1>
|
||
<button class="lang-btn" type="button" data-action="language">EN</button>
|
||
</header>
|
||
`);
|
||
document.querySelector("[data-action='back']").addEventListener("click", closePage);
|
||
document.querySelector("[data-action='language']").addEventListener("click", () => navigateTo("/language"));
|
||
applyLang();
|
||
}
|
||
|
||
export function money(value) {
|
||
if (value === null || value === undefined || value === "") return "0";
|
||
const number = Number(value);
|
||
if (Number.isNaN(number)) return String(value);
|
||
return Number.isInteger(number) ? String(number) : number.toFixed(2);
|
||
}
|
||
|
||
const digitMaps = [
|
||
["٠", "0"], ["١", "1"], ["٢", "2"], ["٣", "3"], ["٤", "4"], ["٥", "5"], ["٦", "6"], ["٧", "7"], ["٨", "8"], ["٩", "9"],
|
||
["۰", "0"], ["۱", "1"], ["۲", "2"], ["۳", "3"], ["۴", "4"], ["۵", "5"], ["۶", "6"], ["۷", "7"], ["۸", "8"], ["۹", "9"],
|
||
["০", "0"], ["১", "1"], ["২", "2"], ["৩", "3"], ["৪", "4"], ["৫", "5"], ["৬", "6"], ["৭", "7"], ["৮", "8"], ["৯", "9"]
|
||
];
|
||
|
||
const monthNames = new Map(Object.entries({
|
||
january: 1, jan: 1, "يناير": 1, "كانون الثاني": 1, ocak: 1, "জানুয়ারি": 1,
|
||
february: 2, feb: 2, "فبراير": 2, "شباط": 2, subat: 2, "şubat": 2, "ফেব্রুয়ারি": 2,
|
||
march: 3, mar: 3, "مارس": 3, "آذار": 3, mart: 3, "মার্চ": 3,
|
||
april: 4, apr: 4, "أبريل": 4, "ابريل": 4, "نيسان": 4, nisan: 4, "এপ্রিল": 4,
|
||
may: 5, "مايو": 5, "أيار": 5, mayis: 5, "mayıs": 5, "মে": 5,
|
||
june: 6, jun: 6, "يونيو": 6, "حزيران": 6, haziran: 6, "জুন": 6,
|
||
july: 7, jul: 7, "يوليو": 7, "تموز": 7, temmuz: 7, "জুলাই": 7,
|
||
august: 8, aug: 8, "أغسطس": 8, "اغسطس": 8, "آب": 8, agustos: 8, "ağustos": 8, "আগস্ট": 8,
|
||
september: 9, sep: 9, "سبتمبر": 9, "أيلول": 9, eylul: 9, "eylül": 9, "সেপ্টেম্বর": 9,
|
||
october: 10, oct: 10, "أكتوبر": 10, "اكتوبر": 10, "تشرين الأول": 10, ekim: 10, "অক্টোবর": 10,
|
||
november: 11, nov: 11, "نوفمبر": 11, "تشرين الثاني": 11, kasim: 11, "kasım": 11, "নভেম্বর": 11,
|
||
december: 12, dec: 12, "ديسمبر": 12, "كانون الأول": 12, aralik: 12, "aralık": 12, "ডিসেম্বর": 12
|
||
}));
|
||
|
||
function pad2(value) {
|
||
return String(value).padStart(2, "0");
|
||
}
|
||
|
||
function formatBillBelong(year, month, day, time) {
|
||
const monthNumber = Number(month);
|
||
const dayNumber = Number(day);
|
||
if (monthNumber < 1 || monthNumber > 12 || dayNumber < 1 || dayNumber > 31) return "";
|
||
return `${year}-${pad2(monthNumber)}-${pad2(dayNumber)}T${time}`;
|
||
}
|
||
|
||
function normalizeDateText(value) {
|
||
return digitMaps.reduce((textValue, [from, to]) => textValue.split(from).join(to), String(value || ""))
|
||
.replace(/&(?:rlm|lrm|nbsp);/gi, " ")
|
||
.replace(/[\u061c\u200e\u200f]/g, "")
|
||
.replace(/[،]/g, ",")
|
||
.replace(/\s+/g, " ")
|
||
.trim();
|
||
}
|
||
|
||
function normalizeTime(value, markerText = "") {
|
||
const match = String(value || "").match(/(\d{1,2}):(\d{2})(?::(\d{2}))?/);
|
||
if (!match) return "00:00:00";
|
||
let hour = Number(match[1]);
|
||
const marker = markerText.toLowerCase();
|
||
const isPm = /\bpm\b|مساء|(?:^|\s)م(?:\s|$)/.test(marker);
|
||
const isAm = /\bam\b|صباح|(?:^|\s)ص(?:\s|$)/.test(marker);
|
||
if (isPm && hour < 12) hour += 12;
|
||
if (isAm && hour === 12) hour = 0;
|
||
return `${pad2(hour)}:${match[2]}:${match[3] || "00"}`;
|
||
}
|
||
|
||
export function normalizeBillBelong(value) {
|
||
const textValue = normalizeDateText(value);
|
||
if (!textValue) return "";
|
||
|
||
const isoMatch = textValue.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?/);
|
||
if (isoMatch) {
|
||
return formatBillBelong(isoMatch[1], isoMatch[2], isoMatch[3], `${isoMatch[4] || "00"}:${isoMatch[5] || "00"}:${isoMatch[6] || "00"}`);
|
||
}
|
||
|
||
const compactDateTime = textValue.match(/^(\d{4})(\d{2})(\d{2})(?:\D?(\d{2})(\d{2})(\d{2})?)?$/);
|
||
if (compactDateTime) {
|
||
return formatBillBelong(compactDateTime[1], compactDateTime[2], compactDateTime[3], `${compactDateTime[4] || "00"}:${compactDateTime[5] || "00"}:${compactDateTime[6] || "00"}`);
|
||
}
|
||
|
||
const numericDate = textValue.match(/\b(?:(\d{4})[./-](\d{1,2})[./-](\d{1,2})|(\d{1,2})[./-](\d{1,2})[./-](\d{4}))\b/);
|
||
if (numericDate) {
|
||
const time = normalizeTime(textValue, textValue);
|
||
if (numericDate[1]) return formatBillBelong(numericDate[1], numericDate[2], numericDate[3], time);
|
||
const first = Number(numericDate[4]);
|
||
const second = Number(numericDate[5]);
|
||
const month = first > 12 && second <= 12 ? second : first;
|
||
const day = second > 12 && first <= 12 ? second : first > 12 ? first : second;
|
||
return formatBillBelong(numericDate[6], month, day, time);
|
||
}
|
||
|
||
const lowerText = textValue.toLowerCase();
|
||
let month = 0;
|
||
for (const [name, valueForMonth] of monthNames) {
|
||
if (lowerText.includes(name)) {
|
||
month = valueForMonth;
|
||
break;
|
||
}
|
||
}
|
||
const yearMatch = textValue.match(/\b(20\d{2})\b/);
|
||
if (month && yearMatch) {
|
||
const withoutYear = textValue.replace(yearMatch[1], "");
|
||
const dayMatch = withoutYear.match(/\b([0-3]?\d)\b/);
|
||
if (dayMatch) {
|
||
return formatBillBelong(yearMatch[1], month, dayMatch[1], normalizeTime(textValue, textValue));
|
||
}
|
||
}
|
||
|
||
return "";
|
||
}
|
||
|
||
export function historyMoreUrl(basePath, billBelong) {
|
||
const normalized = normalizeBillBelong(billBelong);
|
||
return normalized ? `${basePath}?billBelong=${encodeURIComponent(normalized)}` : "";
|
||
}
|
||
|
||
export function safeJsonArray(value) {
|
||
try {
|
||
const result = JSON.parse(value || "[]");
|
||
return Array.isArray(result) ? result : [];
|
||
} catch {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
export function getUserId() {
|
||
const keys = ["userInfo", "user", "USER_INFO"];
|
||
for (const key of keys) {
|
||
try {
|
||
const raw = localStorage.getItem(key) || sessionStorage.getItem(key);
|
||
if (!raw) continue;
|
||
const data = JSON.parse(raw);
|
||
if (data.id || data.userId) return data.id || data.userId;
|
||
} catch {}
|
||
}
|
||
return "";
|
||
}
|
||
|
||
export function toast(message) {
|
||
let node = document.getElementById("toast");
|
||
if (!node) {
|
||
node = document.createElement("div");
|
||
node.id = "toast";
|
||
node.className = "toast";
|
||
document.body.appendChild(node);
|
||
}
|
||
node.textContent = message;
|
||
node.hidden = false;
|
||
window.clearTimeout(toast.timer);
|
||
toast.timer = window.setTimeout(() => {
|
||
node.hidden = true;
|
||
}, 2500);
|
||
}
|
||
|
||
export function createMask(content) {
|
||
const old = document.getElementById("modalMask");
|
||
if (old) old.remove();
|
||
const mask = document.createElement("div");
|
||
mask.id = "modalMask";
|
||
mask.className = "mask";
|
||
mask.addEventListener("click", () => mask.remove());
|
||
content.addEventListener("click", (event) => event.stopPropagation());
|
||
mask.appendChild(content);
|
||
document.body.appendChild(mask);
|
||
return mask;
|
||
}
|
||
|
||
export function statusClass(status) {
|
||
const normalized = statusText(status);
|
||
if (normalized === t("completed")) return "completed";
|
||
if (normalized === t("pending")) return "pending";
|
||
if (normalized === t("out_of_account")) return "out";
|
||
return "";
|
||
}
|
||
|
||
export function statusText(status) {
|
||
const map = {
|
||
SETTLED: t("completed"),
|
||
PAY_OUT: t("completed"),
|
||
UNPAID: t("in_progress"),
|
||
HANG_UP: t("out_of_account"),
|
||
"In Progress": t("in_progress"),
|
||
Pending: t("pending"),
|
||
Completed: t("completed"),
|
||
"Out of account": t("out_of_account"),
|
||
"Out Of Account": t("out_of_account")
|
||
};
|
||
return map[status] || status || "";
|
||
}
|
||
|
||
export function isCompletedStatus(status) {
|
||
return status === "SETTLED" || status === "PAY_OUT" || statusText(status) === t("completed");
|
||
}
|
||
|
||
export function avatarError(img) {
|
||
img.addEventListener("error", () => {
|
||
img.removeAttribute("src");
|
||
});
|
||
}
|