(function () { var ui = window.FamiCenterUI; var fallbackAvatar = '../../products/fami/centers/assets/fami/agency-avatar.png'; var state = { loading: true, profile: null, owner: null, balance: 0, stats: null, hosts: [], dailyHosts: {}, activeAsset: 'diamond', activeDate: '', view: 'summary', inviteProfile: null, inviting: false, statsRequestSequence: 0, dailyRequestSequence: 0, }; var dates = []; var contactController = null; var summaryDefinitions = [ { key: 'total_earnings', label: 'fami.total_earnings', fallback: 'Total Earnings', }, { key: 'gift_income', label: 'fami.gift_income', fallback: 'Gift Income', }, { key: 'share_income', label: 'fami.share_income', fallback: 'Share Income', }, { key: 'total_hosts', label: 'fami.total_hosts', fallback: 'Total Hosts', }, { key: 'gifted_host_count', label: 'fami.gifted_hosts', fallback: 'Gifted Hosts', }, ]; var mockHosts = Array.from({ length: 5 }, function (_, index) { return { membership_id: 'membership-' + index, host_user_id: 'host-' + index, display_user_id: '12345' + (index + 10), username: 'host ' + (index + 1), avatar: fallbackAvatar, diamond_earnings: 123214, }; }); var mock = { user: { user_id: 'agency-owner-1', display_user_id: '123456789', username: 'konghuimingc', avatar: fallbackAvatar, contact_info: '+65 1234 5678', }, overview: { agency: { agency_id: '123456789', name: 'konghuimingc', avatar: fallbackAvatar, created_at_ms: Date.UTC(2099, 8, 9), }, }, coins: { balances: [{ asset_type: 'COIN', available_amount: 1000000000 }], }, stats: { summary: { total_earnings: 999999, gift_income: 999999, share_income: 999999, total_hosts: 999999, gifted_host_count: 999999, }, hosts: mockHosts, }, }; function profileForOverview(payload, owner) { var data = ui.unwrap(payload) || {}; var agency = data.agency || {}; return { id: String(agency.agency_id || ''), displayId: (owner && owner.displayId) || '', name: String(agency.name || 'Agency'), avatar: String(agency.avatar || ''), createdAt: agency.created_at_ms || 0, }; } function normalizeHost(item) { item = item || {}; return { id: String(item.host_user_id || ''), membershipId: String(item.membership_id || ''), displayId: String(item.display_user_id || ''), name: String(item.username || 'Host'), avatar: String(item.avatar || ''), diamondEarnings: ui.number(item.diamond_earnings), }; } function summaryValues() { var summary = (state.stats && state.stats.summary) || {}; return summaryDefinitions.map(function (definition) { return ui.number(summary[definition.key]); }); } function applyStats(payload, date) { var stats = ui.unwrap(payload) || { summary: {}, hosts: [] }; var hosts = Array.isArray(stats.hosts) ? stats.hosts.map(normalizeHost) : []; state.stats = stats; state.hosts = hosts; if (date) state.dailyHosts[date] = hosts; } function hostValue(host, index) { var date = dates[index] && dates[index].value; var daily = state.dailyHosts[date] || []; var item = daily.find(function (candidate) { return candidate.id === host.id; }); return item ? item.diamondEarnings : 0; } function renderProfile() { var profile = state.profile || {}; var owner = state.owner || {}; ui.setAvatar(ui.$('agencyAvatar'), profile.avatar, fallbackAvatar); ui.$('agencyName').textContent = profile.name || '-'; ui.$('agencyUserId').textContent = ui .t('fami.user_id', 'User ID:{id}') .replace('{id}', profile.displayId || '-'); var welcomeDate = profile.createdAt ? new Date(profile.createdAt) : null; var welcome = ''; if (welcomeDate && !Number.isNaN(welcomeDate.getTime())) { // Figma 固定使用点分隔日期;字段值仍来自 agency.created_at_ms,不由客户端推断公会成立时间。 welcome = [ welcomeDate.getFullYear(), String(welcomeDate.getMonth() + 1).padStart(2, '0'), String(welcomeDate.getDate()).padStart(2, '0'), ].join('.'); } if (ui.isMock()) welcome = '2099.99.99'; ui.$('agencyWelcome').textContent = ui .t('fami.welcome_date', 'Welcome {date}') .replace('{date}', welcome || '-'); ui.$('contactName').textContent = owner.name || profile.name || '-'; ui.$('contactUserId').textContent = ui .t('fami.user_id', 'User ID:{id}') .replace('{id}', owner.displayId || profile.displayId || '-'); if (contactController) contactController.setContact(owner.contact || ''); } function renderDates() { dates = ui.dateWindow(9); if (!state.activeDate && dates.length) state.activeDate = dates[0].value; ui.renderDateStrip( ui.$('dateStrip'), dates.slice(0, 7), state.activeDate, selectDate ); } function renderMetrics() { var grid = ui.$('metricGrid'); grid.replaceChildren(); summaryValues().forEach(function (value, index) { var card = document.createElement('div'); card.className = 'agency-metric'; var valueRow = document.createElement('div'); valueRow.className = 'agency-metric__value'; if (index < 3) { var icon = document.createElement('img'); icon.src = '../../products/fami/centers/assets/fami/diamond.png'; icon.alt = ''; valueRow.appendChild(icon); } var number = document.createElement('strong'); number.textContent = ui.formatNumber(value).replace(/,/g, ''); valueRow.appendChild(number); var definition = summaryDefinitions[index]; var label = document.createElement('span'); label.textContent = ui.isMock() ? ui.t('fami.total_return', 'Total Return') : ui.t(definition.label, definition.fallback); card.appendChild(valueRow); card.appendChild(label); grid.appendChild(card); }); } function renderTabs() { document.querySelectorAll('.agency-tab').forEach(function (tab) { var active = tab.getAttribute('data-type') === state.activeAsset; tab.classList.toggle('is-active', active); tab.setAttribute('aria-selected', String(active)); }); } function renderHostTable() { var hostTable = ui.$('hostTable'); // mock 模式固定复现 Figma 已输入的搜索态,但不能让占位 ID 把设计稿中的示例行过滤掉;真实入口仍按用户提交值筛选。 var query = ui.isMock() ? '' : String(ui.$('hostSearchInput').value || '') .trim() .toLowerCase(); var hosts = state.hosts.filter(function (host) { if (!query) return true; return ( host.displayId.toLowerCase().includes(query) || host.name.toLowerCase().includes(query) ); }); var grid = document.createElement('div'); grid.className = 'agency-host-table__grid'; var header = document.createElement('div'); header.className = 'agency-host-table__header'; dates.slice(0, 9).forEach(function (date) { var head = document.createElement('span'); head.className = 'agency-host-table__head'; head.textContent = ui.isMock() ? ui.t('fami.today', 'Today') : date.label; head.setAttribute('aria-label', date.label + ' ' + date.value); header.appendChild(head); }); var rows = document.createElement('div'); rows.className = 'agency-host-table__rows'; hosts.forEach(function (host) { var avatar = document.createElement('img'); avatar.className = 'agency-host-table__avatar'; avatar.alt = ''; ui.setAvatar(avatar, host.avatar, fallbackAvatar); rows.appendChild(avatar); dates.slice(0, 8).forEach(function (_, index) { var value = document.createElement('span'); value.className = 'agency-host-table__value'; value.textContent = ui .formatNumber(hostValue(host, index)) .replace(/,/g, ''); rows.appendChild(value); }); }); grid.appendChild(header); grid.appendChild(rows); hostTable.replaceChildren(grid); } function renderView() { ui.$('summaryView').hidden = state.view !== 'summary'; ui.$('hostView').hidden = state.view !== 'hosts'; if (state.view === 'hosts') renderHostTable(); } function render() { renderProfile(); ui.$('goldBalance').textContent = ui.formatNumber(state.balance); renderTabs(); renderDates(); renderMetrics(); renderView(); } function applyPayloads( userPayload, overviewPayload, coinPayload, statsPayload ) { state.owner = ui.profileFrom(userPayload, 'Agency'); state.profile = profileForOverview(overviewPayload, state.owner); state.balance = ui.walletAvailable(coinPayload, 'COIN'); applyStats(statsPayload, state.activeDate); state.loading = false; render(); } function selectDate(value) { state.activeDate = value; renderDates(); if (ui.isMock()) { applyStats(mock.stats, value); renderMetrics(); return; } var api = window.HyAppAPI && window.HyAppAPI.agencyCenter; if (!api || !api.stats) return; var sequence = ++state.statsRequestSequence; // 汇总卡必须与当前选中 UTC 自然日一致;迟到响应只写入其日期缓存,不覆盖后来选择。 api.stats({ start_date: value, end_date: value }) .then(function (payload) { if (sequence !== state.statsRequestSequence) return; applyStats(payload, value); renderMetrics(); }) .catch(function (error) { if (sequence !== state.statsRequestSequence) return; ui.toast( (error && error.message) || ui.t( 'agency_center.load_failed', 'Load failed. Try again later.' ) ); }); } function loadDailyMatrix() { if (ui.isMock()) { dates.slice(0, 8).forEach(function (date) { state.dailyHosts[date.value] = mockHosts.map(normalizeHost); }); renderHostTable(); return; } var api = window.HyAppAPI && window.HyAppAPI.agencyCenter; if (!api || !api.stats) return; var sequence = ++state.dailyRequestSequence; // 矩阵每列是一个自然日,现有 stats 契约按区间聚合;并行读取 8 个单日且独立容错,绝不把某日聚合值复制到其他日期。 Promise.all( dates.slice(0, 8).map(function (date) { return ui.safe('agency-stats-' + date.value, function () { return api.stats({ start_date: date.value, end_date: date.value, }); }); }) ).then(function (payloads) { if (sequence !== state.dailyRequestSequence) return; payloads.forEach(function (payload, index) { var day = dates[index] && dates[index].value; var data = ui.unwrap(payload) || {}; state.dailyHosts[day] = Array.isArray(data.hosts) ? data.hosts.map(normalizeHost) : []; }); renderHostTable(); }); } function load() { dates = ui.dateWindow(9); if (!state.activeDate && dates.length) state.activeDate = dates[0].value; if (ui.isMock()) { applyPayloads(mock.user, mock.overview, mock.coins, mock.stats); return; } var api = window.HyAppAPI; if (!api || !api.agencyCenter || !api.agencyCenter.stats) { state.loading = false; render(); ui.toast( ui.t('agency_center.api_not_ready', 'API module is not ready.') ); return; } // 公会资料、负责人、COIN 余额和日统计来自明确接口;旧 salary 字段只代表 USD 工资,不能继续冒充 Gold Coins。 Promise.all([ ui.safe('agency-owner', function () { return api.user.me(); }), ui.safe('agency-overview', function () { return api.agencyCenter.overview(); }), ui.safe('agency-coins', function () { return api.wallet.balances('COIN'); }), ui.safe('agency-stats', function () { return api.agencyCenter.stats({ start_date: state.activeDate, end_date: state.activeDate, }); }), ]).then(function (results) { applyPayloads(results[0], results[1], results[2], results[3]); if (!results.some(Boolean)) { ui.toast( ui.t( 'agency_center.load_failed', 'Load failed. Try again later.' ) ); } }); } function openWithdraw() { // 资金页必须由产品上下文构造 URL,保证无 app_code 参数的旧客户端仍使用 Fami 租户。 window.location.href = window.HyGuild.product.buildURL('../wallet/', { identity: 'AGENCY', }); } function setInviteStatus(message, success) { var node = ui.$('inviteStatus'); node.textContent = message || ''; node.hidden = !message; node.classList.toggle('is-success', !!success); } function normalizeInviteProfile(payload, fallbackId) { var profile = ui.profileFrom(payload, 'Host'); if (!profile.id) return null; if (!profile.displayId) profile.displayId = fallbackId; return profile; } function renderInviteResult() { var root = ui.$('inviteResult'); root.replaceChildren(); if (!state.inviteProfile) return; var row = document.createElement('div'); row.className = 'fami-search-result'; var avatar = document.createElement('img'); avatar.alt = ''; ui.setAvatar(avatar, state.inviteProfile.avatar, fallbackAvatar); var copy = document.createElement('div'); copy.className = 'fami-search-result__copy'; var name = document.createElement('strong'); name.textContent = state.inviteProfile.name || 'Host'; var id = document.createElement('span'); id.textContent = 'ID: ' + state.inviteProfile.displayId; var action = document.createElement('button'); action.className = 'fami-search-result__action'; action.type = 'button'; action.disabled = state.inviting; action.textContent = state.inviting ? ui.t('agency_center.pending', 'Pending') : ui.t('agency_center.invite', 'Invite'); action.addEventListener('click', inviteHost); copy.appendChild(name); copy.appendChild(id); row.appendChild(avatar); row.appendChild(copy); row.appendChild(action); root.appendChild(row); } function searchInvite(event) { event.preventDefault(); var value = String(ui.$('inviteSearchInput').value || '').trim(); state.inviteProfile = null; renderInviteResult(); if (!value) return; if (ui.isMock()) { state.inviteProfile = ui.profileFrom(mock.user, 'Host'); state.inviteProfile.id = 'mock-host-' + value; state.inviteProfile.displayId = value; renderInviteResult(); return; } var api = window.HyAppAPI && window.HyAppAPI.user; if (!api || !api.resolveDisplayUserID) { setInviteStatus( ui.t('agency_center.api_not_ready', 'API module is not ready.') ); return; } ui.$('inviteSearchButton').disabled = true; setInviteStatus(ui.t('agency_center.searching', 'Searching...')); api.resolveDisplayUserID(value) .then(function (payload) { state.inviteProfile = normalizeInviteProfile(payload, value); setInviteStatus( state.inviteProfile ? '' : ui.t('agency_center.no_users_found', 'No users found') ); renderInviteResult(); }) .catch(function (error) { setInviteStatus( (error && error.message) || ui.t( 'agency_center.search_failed', 'Search failed. Try again later.' ) ); }) .finally(function () { ui.$('inviteSearchButton').disabled = false; }); } function inviteHost() { if (!state.inviteProfile || state.inviting) return; if (ui.isMock()) { setInviteStatus( ui.t( 'agency_center.invite_sent', 'Invite sent. Waiting for confirmation.' ), true ); return; } var api = window.HyAppAPI && window.HyAppAPI.agencyCenter; if (!api || !api.inviteHost) return; state.inviting = true; renderInviteResult(); // 邀请使用唯一 command_id 交给服务端幂等;结果未确认前禁用按钮,避免 WebView 连点产生多份待确认邀请。 api.inviteHost({ command_id: ui.commandId('agency-invite-host'), target_user_id: state.inviteProfile.id, }) .then(function () { setInviteStatus( ui.t( 'agency_center.invite_sent', 'Invite sent. Waiting for confirmation.' ), true ); state.inviteProfile = null; renderInviteResult(); }) .catch(function (error) { setInviteStatus( (error && error.message) || ui.t( 'agency_center.invite_failed', 'Invite failed. Try again later.' ) ); }) .finally(function () { state.inviting = false; renderInviteResult(); }); } function openInvite() { ui.$('hostSearchInput').value = ui.isMock() ? '1235456' : ''; state.view = 'hosts'; renderView(); loadDailyMatrix(); ui.openModal('inviteModal'); ui.$('inviteSearchInput').value = ''; state.inviteProfile = null; setInviteStatus(''); renderInviteResult(); } function init() { contactController = ui.bindContact(); ui.$('backButton').addEventListener('click', function () { if (state.view === 'hosts') { state.view = 'summary'; renderView(); return; } ui.goBack(); }); ui.$('withdrawButton').addEventListener('click', openWithdraw); ui.$('addHostButton').addEventListener('click', openInvite); ui.$('inviteSearchForm').addEventListener('submit', searchInvite); ui.$('hostSearchForm').addEventListener('submit', function (event) { event.preventDefault(); renderHostTable(); }); document .querySelectorAll('[data-invite-close]') .forEach(function (node) { node.addEventListener('click', function () { ui.closeModal('inviteModal'); }); }); document .querySelectorAll('.agency-tab:not([disabled])') .forEach(function (tab) { tab.addEventListener('click', function () { state.activeAsset = tab.getAttribute('data-type'); renderTabs(); }); }); window.addEventListener('hyapp:i18n-ready', render); render(); load(); if (window.HyAppBridge) { window.HyAppBridge.ready({ page: 'agency-center', app_code: 'fami', mock: ui.isMock(), }); } } document.addEventListener('DOMContentLoaded', init); })();