const API_BASE = '/api/v1'; let chart = null; let currentSeriesData = []; let currentPage = 1; const itemsPerPage = 20; const popularSeries = ['GNPCA', 'GDP', 'UNRATE', 'CPIAUCSL', 'FEDFUNDS', 'M2SL', 'SP500', 'EXUSEU']; function init() { updateTime(); setInterval(updateTime, 1000); loadPopularSeries(); loadCategories(); loadReleases(); initChart(); } function updateTime() { const now = new Date(); document.getElementById('current-time').textContent = now.toLocaleString(); } function initChart() { const ctx = document.getElementById('dataChart').getContext('2d'); chart = new Chart(ctx, { type: 'line', data: { labels: [], datasets: [{ label: 'Value', data: [], borderColor: '#1a73e8', backgroundColor: 'rgba(26, 115, 232, 0.1)', fill: true, tension: 0.3, pointRadius: 3, pointHoverRadius: 6 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'top' }, tooltip: { mode: 'index', intersect: false } }, scales: { x: { ticks: { maxRotation: 45, minRotation: 45 } }, y: { beginAtZero: false } }, interaction: { mode: 'nearest', axis: 'x', intersect: false } } }); } async function searchSeries() { const searchText = document.getElementById('search-input').value.trim(); if (!searchText) { alert('Please enter a search term'); return; } try { const response = await axios.get(`${API_BASE}/series/search`, { params: { search_text: searchText, limit: 20 } }); if (response.data && response.data.seriess && response.data.seriess.length > 0) { displaySearchResults(response.data.seriess); } else { alert('No results found'); } } catch (error) { console.error('Search error:', error); alert('Search failed: ' + (error.response?.data?.error_message || error.message)); } } function displaySearchResults(results) { const select = document.getElementById('series-select'); select.innerHTML = ''; results.forEach(series => { const option = document.createElement('option'); option.value = series.id; option.textContent = `${series.id} - ${series.title}`; select.appendChild(option); }); if (results.length > 0) { select.value = results[0].id; loadSeriesData(); } } async function loadPopularSeries() { const select = document.getElementById('series-select'); select.innerHTML = ''; popularSeries.forEach(seriesId => { const option = document.createElement('option'); option.value = seriesId; option.textContent = seriesId; select.appendChild(option); }); if (popularSeries.length > 0) { select.value = popularSeries[0]; loadSeriesData(); } } async function loadSeriesData() { const seriesId = document.getElementById('series-select').value; if (!seriesId) { document.getElementById('chart-placeholder').style.display = 'block'; document.getElementById('dataChart').style.display = 'none'; return; } try { const [seriesRes, obsRes] = await Promise.all([ axios.get(`${API_BASE}/series`, { params: { series_id: seriesId } }), axios.get(`${API_BASE}/series/observations`, { params: { series_id: seriesId, limit: 1000 } }) ]); const series = seriesRes.data.series; const observations = obsRes.data.observations || []; displaySeriesInfo(series); displayChart(observations, seriesId); currentSeriesData = observations; renderTable(1); updatePagination(); document.getElementById('chart-placeholder').style.display = 'none'; document.getElementById('dataChart').style.display = 'block'; } catch (error) { console.error('Load series error:', error); alert('Failed to load series: ' + (error.response?.data?.error_message || error.message)); } } function displaySeriesInfo(series) { const info = document.getElementById('series-info'); info.innerHTML = `

Series ID: ${series.id}

Title: ${series.title}

Frequency: ${series.frequency}

Units: ${series.units}

Seasonal Adjustment: ${series.seasonal_adjustment}

Last Updated: ${series.last_updated}

${series.notes ? `

Notes: ${series.notes}

` : ''} `; document.getElementById('chart-title').textContent = series.title || series.id; } function displayChart(observations, seriesId) { const labels = observations.map(o => o.date).reverse(); const data = observations.map(o => parseFloat(o.value) || null).reverse(); chart.data.labels = labels; chart.data.datasets[0].data = data; chart.data.datasets[0].label = seriesId; chart.update(); } function renderTable(page) { currentPage = page; const start = (page - 1) * itemsPerPage; const end = start + itemsPerPage; const pageData = currentSeriesData.slice(start, end); const tbody = document.getElementById('data-table-body'); if (pageData.length === 0) { tbody.innerHTML = 'No data available'; return; } tbody.innerHTML = pageData.map(obs => ` ${obs.date} ${obs.value || 'N/A'} `).join(''); } function updatePagination() { const totalPages = Math.ceil(currentSeriesData.length / itemsPerPage); const pagination = document.getElementById('pagination'); if (totalPages <= 1) { pagination.innerHTML = ''; return; } let html = ''; if (currentPage > 1) { html += `
  • Prev
  • `; } for (let i = 1; i <= totalPages; i++) { if (i === currentPage) { html += `
  • ${i}
  • `; } else if (i === 1 || i === totalPages || (i >= currentPage - 2 && i <= currentPage + 2)) { html += `
  • ${i}
  • `; } else if (i === currentPage - 3 || i === currentPage + 3) { html += `
  • ...
  • `; } } if (currentPage < totalPages) { html += `
  • Next
  • `; } pagination.innerHTML = html; } async function loadCategories() { try { const response = await axios.get(`${API_BASE}/category/children`, { params: { category_id: 0 } }); const list = document.getElementById('categories-list'); if (response.data && response.data.categories) { list.innerHTML = response.data.categories.map(cat => `
    ${cat.name}
    `).join(''); } } catch (error) { console.error('Load categories error:', error); } } async function loadCategorySeries(categoryId, categoryName) { try { const response = await axios.get(`${API_BASE}/category/series`, { params: { category_id: categoryId, limit: 10 } }); const select = document.getElementById('series-select'); select.innerHTML = ``; if (response.data && response.data.seriess) { response.data.seriess.forEach(series => { const option = document.createElement('option'); option.value = series.id; option.textContent = `${series.id} - ${series.title}`; select.appendChild(option); }); if (response.data.seriess.length > 0) { select.value = response.data.seriess[0].id; loadSeriesData(); } } } catch (error) { console.error('Load category series error:', error); } } async function loadReleases() { try { const response = await axios.get(`${API_BASE}/releases/dates`, { params: { limit: 10, start_date: getDateDaysAgo(30) } }); const list = document.getElementById('releases-list'); if (response.data && response.data.release_dates) { list.innerHTML = response.data.release_dates.map(rel => `
    ${rel.release_name}
    ${rel.date}
    `).join(''); } } catch (error) { console.error('Load releases error:', error); } } async function loadReleaseSeries(releaseId, releaseName) { try { const response = await axios.get(`${API_BASE}/release/series`, { params: { release_id: releaseId, limit: 10 } }); const select = document.getElementById('series-select'); select.innerHTML = ``; if (response.data && response.data.seriess) { response.data.seriess.forEach(series => { const option = document.createElement('option'); option.value = series.id; option.textContent = `${series.id} - ${series.title}`; select.appendChild(option); }); if (response.data.seriess.length > 0) { select.value = response.data.seriess[0].id; loadSeriesData(); } } } catch (error) { console.error('Load release series error:', error); } } function showSeriesModal() { const modal = new bootstrap.Modal(document.getElementById('addSeriesModal')); modal.show(); } function addSeriesFromModal() { const seriesId = document.getElementById('modal-series-id').value.trim(); if (!seriesId) { alert('Please enter a series ID'); return; } document.getElementById('series-select').value = seriesId; bootstrap.Modal.getInstance(document.getElementById('addSeriesModal')).hide(); loadSeriesData(); } function exportData() { if (!currentSeriesData.length) { alert('No data to export'); return; } let csv = 'Date,Value\n'; currentSeriesData.forEach(obs => { csv += `${obs.date},${obs.value}\n`; }); const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `fred_data_${new Date().toISOString().split('T')[0]}.csv`; a.click(); URL.revokeObjectURL(url); } function getDateDaysAgo(days) { const date = new Date(); date.setDate(date.getDate() - days); return date.toISOString().split('T')[0]; } document.addEventListener('DOMContentLoaded', init); document.getElementById('search-input').addEventListener('keypress', function(e) { if (e.key === 'Enter') { searchSeries(); } });