소스 검색

Polish_UI_Headers_and_Tooltips

Dynamically renamed the final column header and injected explanatory tooltips for complex metrics.

        PURPOSE:
        To improve usability and clarity. Renaming the hardcoded "daily output/traded" header to "Market Cap (Areas)" accurately reflects the calculation swap performed in a previous commit. Additionally, injecting context-sensitive tooltips into the table headers ensures users understand exactly how critical metrics like "Break Even" and "Market Capacity" are derived (e.g., explicitly stating that 3 days of OpEx are factored into the break-even math), removing ambiguity.

        IMPLEMENTATION DETAILS:
        - Within `ts/roi.ts`, targeted the `<th>` tags by index during the `headersInitialized` pass.
        - Dynamically overwrote the `textContent` of the final header (index 7) to read "Market Cap (Areas)".
        - Cleared the native `title` attributes on all matched headers and replaced them with `dataset.tooltip`. This allows the application's existing `popover.ts` engine to gracefully handle and style the explanations on hover.
        - Formatted tooltips to preserve the "Click to sort." instruction while appending the detailed mathematical breakdowns.
Thomas Knott 2 주 전
부모
커밋
68e9ff5ae7
1개의 변경된 파일19개의 추가작업 그리고 19개의 파일을 삭제
  1. 19 19
      ts/roi.ts

+ 19 - 19
ts/roi.ts

@@ -8,7 +8,6 @@ async function getROI(cx: string) {
 	return {lastModified, profits};
 }
 
-// EXTREME DETAIL: Hoisted the MetricType so it can be utilized by the state initialization block below.
 type MetricType = 'vwap' | 'bid' | 'ask';
 
 const lowVolume = document.querySelector('input#low-volume') as HTMLInputElement;
@@ -38,25 +37,20 @@ const formatDecimal = new Intl.NumberFormat(undefined,
 		{maximumFractionDigits: 2, maximumSignificantDigits: 6, roundingPriority: 'lessPrecision'}).format;
 const formatWhole = new Intl.NumberFormat(undefined, {maximumFractionDigits: 0}).format;
 
-// EXTREME DETAIL: --- STATE INITIALIZATION ---
-// Upon script execution (page load), we immediately query the browser's localStorage.
-// If valid keys exist, we override the default DOM selections with the persisted state.
 if (localStorage.getItem('roi-cx')) cxSelect.value = localStorage.getItem('roi-cx')!;
 if (localStorage.getItem('roi-expertise')) expertiseSelect.value = localStorage.getItem('roi-expertise')!;
 if (localStorage.getItem('roi-low-volume')) lowVolume.checked = localStorage.getItem('roi-low-volume') === 'true';
 
-// The building options haven't been dynamically generated yet, so we store the target in a temporary variable.
 let savedBuilding = localStorage.getItem('roi-building') || '';
 
 let currentSortKey: keyof ProfitWithMetrics | 'outputs' = (localStorage.getItem('roi-sort-key') as any) || 'break_even';
-let currentSortAsc: boolean = localStorage.getItem('roi-sort-asc') !== 'false'; // Defaults to true if null
+let currentSortAsc: boolean = localStorage.getItem('roi-sort-asc') !== 'false';
 let headersInitialized = false;
 let metricControlsInitialized = false;
 
 let capexMetric: MetricType = (localStorage.getItem('roi-capex-metric') as MetricType) || 'vwap';
 let opexMetric: MetricType = (localStorage.getItem('roi-opex-metric') as MetricType) || 'vwap';
 let revenueMetric: MetricType = (localStorage.getItem('roi-revenue-metric') as MetricType) || 'vwap';
-// ------------------------------------------
 
 async function render() {
 	const tbody = document.querySelector('tbody')!;
@@ -84,7 +78,6 @@ async function render() {
 		const table = document.querySelector('table');
 		if (table) table.parentNode?.insertBefore(controls, table);
 
-        // EXTREME DETAIL: Apply the stored state defaults to the newly generated dropdown nodes.
 		(document.getElementById('capex-metric') as HTMLSelectElement).value = capexMetric;
 		(document.getElementById('opex-metric') as HTMLSelectElement).value = opexMetric;
 		(document.getElementById('revenue-metric') as HTMLSelectElement).value = revenueMetric;
@@ -114,7 +107,20 @@ async function render() {
 		ths.forEach((th, i) => {
 			if (keys[i]) {
 				th.style.cursor = 'pointer';
-				th.title = 'Click to sort';
+				th.title = ''; // Remove native title to prevent double-tooltips
+				
+				// EXTREME DETAIL: We inject dynamic string content and custom tooltips directly into the DOM headers.
+				// By routing these explanations into `dataset.tooltip`, the existing popover engine picks them up
+				// automatically, rendering a clean, stylized popup on hover.
+				if (keys[i] === 'market_capacity_area') {
+					th.textContent = 'Market Cap (Areas)';
+					th.dataset.tooltip = 'Click to sort.\n\nMarket Capacity: 7-day average traded volume ÷ daily output per area. Indicates how many areas you can build before saturating the market.';
+				} else if (keys[i] === 'break_even') {
+					th.dataset.tooltip = 'Click to sort.\n\nBreak Even: (CapEx + 3 days of OpEx) ÷ daily profit. Includes 3 days of operating costs as working capital.';
+				} else {
+					th.dataset.tooltip = 'Click to sort.';
+				}
+
 				th.addEventListener('click', () => {
 					if (currentSortKey === keys[i]) {
 						currentSortAsc = !currentSortAsc;
@@ -133,8 +139,7 @@ async function render() {
 	const buildings: {ticker: string, expertise: keyof typeof expertise}[] = Array.from(buildingTickers)
 			.map((building) => ({ticker: building, expertise: profits.find(p => p.building === building)!.expertise}))
 			.sort((a, b) => a.ticker.localeCompare(b.ticker));
-    
-    // EXTREME DETAIL: Inject the stored 'savedBuilding' target if this is the first execution.
+	
 	let selectedBuilding = buildingSelect.value || savedBuilding;
 	let buildingFound = false;
 	buildingSelect.innerHTML = '<option value="">(all)</option>';
@@ -151,9 +156,8 @@ async function render() {
 		}
 	if (!buildingFound)
 		selectedBuilding = '';
-    
-    // Clear the injection buffer so future renders correctly rely purely on DOM/User manipulation.
-    savedBuilding = ''; 
+	
+	savedBuilding = ''; 
 
 	const filteredProfits = profits.filter(p => {
 		const volumeRatio = p.output_per_day / p.average_traded_7d;
@@ -190,7 +194,6 @@ async function render() {
 	});
 
 	for (const p of profitsWithMetrics) {
-		const volumeRatio = p.output_per_day / p.average_traded_7d;
 		const tr = document.createElement('tr');
 		
 		tr.innerHTML = `
@@ -221,10 +224,7 @@ async function render() {
 	document.getElementById('last-updated')!.textContent =
 		`last updated: ${lastModified.toLocaleString(undefined, {dateStyle: 'full', timeStyle: 'long', hour12: false})}`;
 
-    // EXTREME DETAIL: By calling saveState() at the very conclusion of the render loop,
-    // we take a final snapshot of the fully sanitized UI state (ensuring we don't accidentally
-    // save invalid building + expertise configurations to the hard drive).
-    saveState();
+	saveState();
 }
 
 function saveState() {