File size: 5,139 Bytes
452f691
 
 
 
 
 
 
 
 
 
 
 
 
 
dd7ffbd
 
452f691
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd7ffbd
452f691
dd7ffbd
 
 
 
452f691
 
 
dd7ffbd
 
 
 
 
452f691
dd7ffbd
452f691
 
 
 
 
dd7ffbd
 
452f691
dd7ffbd
452f691
 
dd7ffbd
452f691
 
dd7ffbd
452f691
dd7ffbd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452f691
 
 
 
 
 
dd7ffbd
452f691
 
 
 
 
 
 
 
 
dd7ffbd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452f691
 
 
dd7ffbd
 
 
 
 
452f691
dd7ffbd
452f691
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import apiClient from './apiClient.js';
import { formatCurrency, formatPercent, renderMessage, createSkeletonRows } from './uiUtils.js';

class OverviewView {
    constructor(section) {
        this.section = section;
        this.statsContainer = section.querySelector('[data-overview-stats]');
        this.topCoinsBody = section.querySelector('[data-top-coins-body]');
        this.sentimentCanvas = section.querySelector('#sentiment-chart');
        this.sentimentChart = null;
    }

    async init() {
        this.renderStatSkeletons();
        this.topCoinsBody.innerHTML = createSkeletonRows(6, 6);
        await Promise.all([this.loadStats(), this.loadTopCoins(), this.loadSentiment()]);
    }

    renderStatSkeletons() {
        if (!this.statsContainer) return;
        this.statsContainer.innerHTML = Array.from({ length: 4 })
            .map(() => '<div class="glass-card stat-card skeleton" style="height: 140px;"></div>')
            .join('');
    }

    async loadStats() {
        if (!this.statsContainer) return;
        const result = await apiClient.getMarketStats();
        if (!result.ok) {
            renderMessage(this.statsContainer, {
                state: 'error',
                title: 'Unable to load market stats',
                body: result.error || 'Unknown error',
            });
            return;
        }
        const stats = result.data || {};
        const cards = [
            { label: 'Total Market Cap', value: formatCurrency(stats.total_market_cap) },
            { label: '24h Volume', value: formatCurrency(stats.total_volume_24h) },
            { label: 'BTC Dominance', value: formatPercent(stats.btc_dominance) },
            { label: 'ETH Dominance', value: formatPercent(stats.eth_dominance) },
        ];
        this.statsContainer.innerHTML = cards
            .map(
                (card) => `
                <div class="glass-card stat-card">
                    <h3>${card.label}</h3>
                    <div class="stat-value">${card.value}</div>
                    <div class="stat-trend">Updated ${new Date().toLocaleTimeString()}</div>
                </div>
            `,
            )
            .join('');
    }

    async loadTopCoins() {
        const result = await apiClient.getTopCoins(10);
        if (!result.ok) {
            this.topCoinsBody.innerHTML = `
                <tr><td colspan="7">
                    <div class="inline-message inline-error">
                        <strong>Failed to load coins</strong>
                        <p>${result.error}</p>
                    </div>
                </td></tr>`;
            return;
        }
        const rows = (result.data || []).map(
            (coin, index) => `
            <tr>
                <td>${index + 1}</td>
                <td>${coin.symbol || coin.ticker || '—'}</td>
                <td>${coin.name || 'Unknown'}</td>
                <td>${formatCurrency(coin.price)}</td>
                <td class="${coin.change_24h >= 0 ? 'text-success' : 'text-danger'}">
                    ${formatPercent(coin.change_24h)}
                </td>
                <td>${formatCurrency(coin.volume_24h)}</td>
                <td>${formatCurrency(coin.market_cap)}</td>
            </tr>
        `);
        this.topCoinsBody.innerHTML = rows.join('');
    }

    async loadSentiment() {
        if (!this.sentimentCanvas) return;
        const result = await apiClient.runQuery({ query: 'global crypto sentiment breakdown' });
        if (!result.ok) {
            this.sentimentCanvas.replaceWith(this.buildSentimentFallback(result.error));
            return;
        }
        const payload = result.data || {};
        const sentiment = payload.sentiment || payload.data || {};
        const data = {
            bullish: sentiment.bullish ?? 40,
            neutral: sentiment.neutral ?? 35,
            bearish: sentiment.bearish ?? 25,
        };
        if (this.sentimentChart) {
            this.sentimentChart.destroy();
        }
        this.sentimentChart = new Chart(this.sentimentCanvas, {
            type: 'doughnut',
            data: {
                labels: ['Bullish', 'Neutral', 'Bearish'],
                datasets: [
                    {
                        data: [data.bullish, data.neutral, data.bearish],
                        backgroundColor: ['#22c55e', '#38bdf8', '#ef4444'],
                        borderWidth: 0,
                    },
                ],
            },
            options: {
                cutout: '65%',
                plugins: {
                    legend: {
                        labels: { color: 'var(--text-primary)', usePointStyle: true },
                    },
                },
            },
        });
    }

    buildSentimentFallback(message) {
        const wrapper = document.createElement('div');
        wrapper.className = 'inline-message inline-info';
        wrapper.innerHTML = `
            <strong>Sentiment insight unavailable</strong>
            <p>${message || 'AI sentiment endpoint did not respond in time.'}</p>
        `;
        return wrapper;
    }
}

export default OverviewView;