109 lines
5.9 KiB
JavaScript
109 lines
5.9 KiB
JavaScript
import { apiClient } from '/static/portal/common/api-client.js?v=20260913';
|
|
import { requireCustomer } from '/static/portal/common/auth.js?v=20260913';
|
|
import { mountShell } from '/static/portal/common/layout/app-shell.js';
|
|
import { applyPermissionGuard } from '/static/portal/common/permission-guard.js';
|
|
import { renderError, renderLoading } from '/static/portal/common/state-view.js?v=20260912';
|
|
import * as accountSummary from '/static/portal/customer/dashboard/modules/account-summary.js';
|
|
import * as holdingsTable from '/static/portal/customer/dashboard/modules/holdings-table.js';
|
|
import * as portfolioSummary from '/static/portal/customer/dashboard/modules/portfolio-summary.js';
|
|
import * as quickActions from '/static/portal/customer/dashboard/modules/quick-actions.js';
|
|
import { updateRefreshIndicator } from '/static/portal/customer/dashboard/modules/refresh-indicator.js';
|
|
|
|
if (requireCustomer()) {
|
|
mountShell({ active: 'dashboard', mode: 'customer' });
|
|
const state = { data: null, fetchedAt: 0, controller: null };
|
|
const containers = {
|
|
account: document.querySelector('[data-account-summary]'),
|
|
portfolio: document.querySelector('[data-portfolio-summary]'),
|
|
holdings: document.querySelector('[data-holdings-table]'),
|
|
};
|
|
const refreshButton = document.querySelector('[data-refresh]');
|
|
const refreshTime = document.querySelector('[data-refresh-time]');
|
|
const alert = document.querySelector('[data-dashboard-alert]');
|
|
const dialog = document.querySelector('[data-order-dialog]');
|
|
const quickActionsContainer = document.querySelector('[data-quick-actions]');
|
|
|
|
function showDashboardSections() {
|
|
Object.values(containers).forEach((container) => { container.hidden = false; });
|
|
quickActionsContainer.hidden = false;
|
|
}
|
|
function showLoading() {
|
|
showDashboardSections();
|
|
Object.values(containers).forEach((container) => renderLoading(container, 1));
|
|
}
|
|
function renderPageError(error) {
|
|
containers.account.hidden = false;
|
|
containers.portfolio.hidden = true;
|
|
containers.holdings.hidden = true;
|
|
quickActionsContainer.hidden = true;
|
|
renderError(containers.account, error, () => load({ force: true }));
|
|
}
|
|
function renderData(data, cached = false) {
|
|
showDashboardSections();
|
|
accountSummary.render(containers.account, data.account);
|
|
portfolioSummary.render(containers.portfolio, data);
|
|
holdingsTable.render(containers.holdings, data.holdings || []);
|
|
quickActions.render(quickActionsContainer);
|
|
updateRefreshIndicator(refreshTime, data.as_of, cached);
|
|
applyPermissionGuard();
|
|
document.querySelector('[data-open-order]')?.addEventListener('click', () => dialog.showModal());
|
|
const productCode = new URLSearchParams(window.location.search).get('product_code');
|
|
const target = productCode ? document.querySelector(`[data-product-code="${CSS.escape(productCode)}"]`) : null;
|
|
if (target) { target.classList.add('dashboard-holdings__highlight'); target.scrollIntoView({ block: 'center' }); window.setTimeout(() => target.classList.remove('dashboard-holdings__highlight'), 1500); }
|
|
if (new URLSearchParams(window.location.search).get('action') === 'trade') {
|
|
const orderForm = document.querySelector('[data-order-form]');
|
|
const productInput = orderForm?.querySelector('[name="product_code"]');
|
|
if (productInput && productCode) productInput.value = productCode;
|
|
if (dialog?.showModal && productCode && !dialog.open) dialog.showModal();
|
|
}
|
|
}
|
|
async function load({ force = false } = {}) {
|
|
if (!force && state.data && Date.now() - state.fetchedAt < 60000) { renderData(state.data, true); return; }
|
|
state.controller?.abort();
|
|
state.controller = new AbortController();
|
|
showLoading();
|
|
refreshButton.disabled = true;
|
|
const startedAt = performance.now();
|
|
try {
|
|
const response = await apiClient.get('T001', { signal: state.controller.signal });
|
|
state.data = response.data;
|
|
state.fetchedAt = Date.now();
|
|
renderData(response.data);
|
|
apiClient.track('dashboard_refresh', { source: force ? 'manual' : 'load', duration_ms: Math.round(performance.now() - startedAt) });
|
|
} catch (error) {
|
|
apiClient.reportError(error);
|
|
renderPageError(error);
|
|
} finally { refreshButton.disabled = false; }
|
|
}
|
|
refreshButton.addEventListener('click', () => load());
|
|
document.querySelector('[data-order-close]').addEventListener('click', () => dialog.close());
|
|
document.querySelector('[data-order-form]').addEventListener('submit', async (event) => {
|
|
event.preventDefault();
|
|
const form = event.currentTarget;
|
|
const formData = new FormData(form);
|
|
const orderAlert = document.querySelector('[data-order-alert]');
|
|
const submit = form.querySelector('[type="submit"]');
|
|
orderAlert.classList.remove('form-alert--visible');
|
|
submit.disabled = true;
|
|
const body = { product_code: String(formData.get('product_code') || '').trim(), order_side: String(formData.get('order_side')), quantity: String(formData.get('quantity')), price_type: 'market' };
|
|
apiClient.track('order_submit_attempt', { product_code: body.product_code, side: body.order_side, quantity: body.quantity });
|
|
try {
|
|
const response = await apiClient.post('T002', body);
|
|
apiClient.track('order_submit_result', { order_no: response.data.order_no, status: response.data.status });
|
|
alert.textContent = `委托 ${response.data.order_no} 已${response.data.status}`;
|
|
alert.classList.add('dashboard-alert--visible');
|
|
dialog.close();
|
|
form.reset();
|
|
await load({ force: true });
|
|
} catch (error) {
|
|
apiClient.reportError(error);
|
|
apiClient.track('order_submit_result', { status: 'failed', error_code: error.code });
|
|
orderAlert.textContent = error.message;
|
|
orderAlert.classList.add('form-alert--visible');
|
|
} finally { submit.disabled = false; }
|
|
});
|
|
window.addEventListener('pagehide', () => state.controller?.abort());
|
|
apiClient.track('page_view', { page: 'dashboard', portal: 'customer' });
|
|
load();
|
|
}
|