| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> |
| <title>Stock Ownership Analytics Tool</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| <link href="https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.css" rel="stylesheet" type="text/css" /> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"/> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); |
| body { |
| font-family: 'Inter', sans-serif; |
| } |
| .chart-container { |
| position: relative; |
| height: 350px; |
| width: 100%; |
| } |
| .loading-spinner { |
| border-top-color: #3498db; |
| animation: spinner 1.2s linear infinite; |
| } |
| @keyframes spinner { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| .database-item { |
| transition: all 0.2s; |
| } |
| .database-item:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 10px 20px rgba(0,0,0,0.1); |
| } |
| </style> |
| </head> |
| <body class="bg-gradient-to-br from-slate-50 to-slate-100 min-h-screen"> |
|
|
| |
| <nav class="bg-white shadow-lg sticky top-0 z-50 border-b"> |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> |
| <div class="flex justify-between items-center h-16"> |
| <div class="flex items-center"> |
| <div class="text-2xl font-bold text-indigo-600 flex items-center gap-2"> |
| <i class="fas fa-chart-line"></i> |
| <span>StockOwnership Insights</span> |
| </div> |
| </div> |
| <div class="hidden md:flex space-x-8"> |
| <a href="#dashboard" class="text-gray-700 hover:text-indigo-600 font-medium">Dashboard</a> |
| <a href="#uploader" class="text-gray-700 hover:text-indigo-600 font-medium">Upload Data</a> |
| <a href="#database" class="text-gray-700 hover:text-indigo-600 font-medium">Database</a> |
| <a href="#download" class="text-gray-700 hover:text-indigo-600 font-medium">Export</a> |
| </div> |
| </div> |
| </div> |
| </nav> |
|
|
| |
| <section class="py-16 px-6 text-center bg-gradient-to-r from-indigo-600 to-purple-600 text-white"> |
| <div class="max-w-4xl mx-auto"> |
| <h1 class="text-4xl md:text-5xl font-bold mb-6 leading-tight"> |
| Analisis Kepemilikan Saham |
| </h1> |
| <p class="text-lg md:text-xl opacity-90 mb-10"> |
| Pantau pertumbuhan kepemilikan saham oleh investor lokal, asing, dan institusi secara real-time. |
| Ekstrak data dari KSEI dan analisis dalam dashboard interaktif. |
| </p> |
| <button onclick="scrollToSection('uploader')" class="bg-white text-indigo-700 font-semibold px-8 py-3 rounded-full shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 flex items-center gap-2 mx-auto"> |
| <i class="fas fa-file-import"></i> Mulai Analisis |
| </button> |
| </div> |
| </section> |
|
|
| |
| <section id="uploader" class="py-16 px-6 bg-white"> |
| <div class="max-w-4xl mx-auto"> |
| <div class="text-center mb-10"> |
| <h2 class="text-3xl font-bold text-gray-800 mb-4">Unggah Data KSEI</h2> |
| <p class="text-gray-600"> |
| Unggah file CSV yang diunduh dari <a href="https://web.ksei.co.id/Download/" target="_blank" class="text-indigo-600 hover:underline">KSEI</a>. |
| Format yang didukung: CSV dengan pembatas <strong>|</strong> dan header <strong>Ticker|Nama Saham|Lokal|Asing|Institusi|Tanggal</strong>. |
| </p> |
| </div> |
|
|
| <div id="upload-box" class="border-2 border-dashed border-gray-300 rounded-xl p-10 text-center hover:border-indigo-400 transition-colors duration-200 cursor-pointer relative overflow-hidden group"> |
| <input type="file" id="csv-upload" accept=".csv" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" onchange="handleFileSelect(event)" /> |
| <div class="flex flex-col items-center"> |
| <i class="fas fa-cloud-upload-alt text-5xl text-gray-400 group-hover:text-indigo-500 transition-colors duration-200"></i> |
| <p class="mt-4 text-gray-600 font-medium">Seret dan lepas file CSV, atau klik untuk memilih</p> |
| <p class="text-sm text-gray-500 mt-2">Format: CSV dengan delimiter |, maks 10MB</p> |
| </div> |
| </div> |
|
|
| <div id="upload-meta" class="mt-6 bg-gray-50 p-6 rounded-xl hidden"> |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Informasi Data</h3> |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-2">Bulan</label> |
| <select id="upload-month" class="w-full border border-gray-300 rounded-lg px-3 py-2"> |
| <option value="1">Januari</option> |
| <option value="2">Februari</option> |
| <option value="3">Maret</option> |
| <option value="4">April</option> |
| <option value="5">Mei</option> |
| <option value="6">Juni</option> |
| <option value="7">Juli</option> |
| <option value="8">Agustus</option> |
| <option value="9">September</option> |
| <option value="10">Oktober</option> |
| <option value="11">November</option> |
| <option value="12">Desember</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-2">Tahun</label> |
| <input type="number" id="upload-year" class="w-full border border-gray-300 rounded-lg px-3 py-2" |
| value="2023" min="2010" max="2030" /> |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-2">Nomor Laporan</label> |
| <input type="number" id="upload-number" class="w-full border border-gray-300 rounded-lg px-3 py-2" |
| value="1" min="1" /> |
| </div> |
| </div> |
| <div class="mt-4 flex justify-end"> |
| <button onclick="saveToDatabase()" class="bg-indigo-600 text-white px-6 py-2 rounded-lg hover:bg-indigo-700 transition"> |
| Simpan ke Database |
| </button> |
| </div> |
| </div> |
|
|
| <div id="loading" class="flex flex-col items-center justify-center mt-8 hidden"> |
| <div class="w-16 h-16 border-4 border-gray-200 border-t-indigo-500 rounded-full loading-spinner"></div> |
| <p class="mt-4 text-gray-600">Memproses data...</p> |
| </div> |
|
|
| <div id="preview" class="mt-8 hidden"> |
| <h3 class="text-xl font-semibold text-gray-800 mb-4">Pratinjau Data</h3> |
| <div class="overflow-x-auto bg-gray-50 rounded-lg border"> |
| <table id="data-preview-table" class="min-w-full text-sm"></table> |
| </div> |
| <div class="mt-4 flex justify-end"> |
| <button onclick="processData()" class="bg-indigo-600 text-white px-6 py-2 rounded-lg hover:bg-indigo-700 transition"> |
| Proses Data |
| </button> |
| </div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section id="database" class="py-16 px-6 bg-gray-50"> |
| <div class="max-w-6xl mx-auto"> |
| <div class="text-center mb-10"> |
| <h2 class="text-3xl font-bold text-gray-800 mb-4">Database Laporan Kepemilikan</h2> |
| <p class="text-gray-600">Kelola dan akses laporan bulanan kepemilikan saham</p> |
| </div> |
|
|
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-10" id="database-container"> |
| |
| <div class="text-center py-10 text-gray-500"> |
| <i class="fas fa-database text-5xl mb-4 opacity-30"></i> |
| <p>Belum ada data dalam database</p> |
| </div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section id="dashboard" class="py-16 px-6 bg-gray-50"> |
| <div class="max-w-7xl mx-auto"> |
| <div class="text-center mb-10"> |
| <h2 class="text-3xl font-bold text-gray-800 mb-4">Dashboard Analitik</h2> |
| <p class="text-gray-600">Analisa perubahan kepemilikan saham secara mendalam</p> |
| </div> |
|
|
| |
| <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-10"> |
| <div class="bg-white p-6 rounded-xl shadow-md border"> |
| <div class="flex items-center"> |
| <div class="p-3 rounded-full bg-blue-100 text-blue-600"> |
| <i class="fas fa-user-friends"></i> |
| </div> |
| <div class="ml-4"> |
| <p class="text-sm font-medium text-gray-600">Total Saham</p> |
| <p id="total-stocks" class="text-2xl font-bold text-gray-800">-</p> |
| </div> |
| </div> |
| </div> |
| <div class="bg-white p-6 rounded-xl shadow-md border"> |
| <div class="flex items-center"> |
| <div class="p-3 rounded-full bg-green-100 text-green-600"> |
| <i class="fas fa-arrow-up"></i> |
| </div> |
| <div class="ml-4"> |
| <p class="text-sm font-medium text-gray-600">Saham Asing Naik</p> |
| <p id="foreign-up" class="text-2xl font-bold text-gray-800">-</p> |
| </div> |
| </div> |
| </div> |
| <div class="bg-white p-6 rounded-xl shadow-md border"> |
| <div class="flex items-center"> |
| <div class="p-3 rounded-full bg-purple-100 text-purple-600"> |
| <i class="fas fa-building"></i> |
| </div> |
| <div class="ml-4"> |
| <p class="text-sm font-medium text-gray-600">Saham Institusi</p> |
| <p id="institutional-total" class="text-2xl font-bold text-gray-800">-</p> |
| </div> |
| </div> |
| </div> |
| <div class="bg-white p-6 rounded-xl shadow-md border"> |
| <div class="flex items-center"> |
| <div class="p-3 rounded-full bg-indigo-100 text-indigo-600"> |
| <i class="fas fa-trending-up"></i> |
| </div> |
| <div class="ml-4"> |
| <p class="text-sm font-medium text-gray-600">Rata-Rata Kenaikan Asing</p> |
| <p id="foreign-growth" class="text-2xl font-bold text-gray-800">-</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-10"> |
| |
| <div class="bg-white p-6 rounded-xl shadow-md border"> |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Distribusi Kepemilikan (Rata-rata)</h3> |
| <div class="chart-container"> |
| <canvas id="ownershipChart"></canvas> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-white p-6 rounded-xl shadow-md border"> |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Trend Pertumbuhan Asing</h3> |
| <div class="chart-container"> |
| <canvas id="growthChart"></canvas> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-white p-6 rounded-xl shadow-md border mb-10"> |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Saham dengan Kenaikan Kepemilikan Asing Tertinggi</h3> |
| <div class="overflow-x-auto"> |
| <table id="top-movers-table" class="min-w-full text-sm"> |
| <thead> |
| <tr class="border-b"> |
| <th class="py-2 text-left">Ticker</th> |
| <th class="py-2 text-left">Nama</th> |
| <th class="py-2 text-right">Kenaikan (%)</th> |
| </tr> |
| </thead> |
| <tbody></tbody> |
| </table> |
| </div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section id="download" class="py-16 px-6 bg-white"> |
| <div class="max-w-4xl mx-auto text-center"> |
| <h2 class="text-3xl font-bold text-gray-800 mb-4">Ekspor Hasil Analisis</h2> |
| <p class="text-gray-600 mb-8"> |
| Ekspor data dan hasil analisis ke format CSV untuk disimpan atau digunakan di aplikasi lain. |
| </p> |
| <button onclick="exportAnalysis()" class="bg-green-600 hover:bg-green-700 text-white font-semibold px-8 py-3 rounded-full shadow-lg flex items-center gap-2 mx-auto transition"> |
| <i class="fas fa-file-export"></i> Ekspor ke CSV |
| </button> |
| </div> |
| </section> |
|
|
| |
| <footer class="py-8 bg-gray-800 text-white text-center"> |
| <div class="max-w-4xl mx-auto px-6"> |
| <p>StockOwnership Insights Tool © 2023 | Data bersumber dari <a href="https://web.ksei.co.id" class="text-indigo-400 hover:underline">KSEI</a></p> |
| <p class="text-sm text-gray-400 mt-2">Alat analisis ini tidak berafiliasi dengan KSEI. Gunakan dengan tanggung jawab Anda sendiri.</p> |
| </div> |
| </footer> |
|
|
| <script> |
| |
| let rawData = []; |
| let processedData = []; |
| let ownershipChart = null; |
| let growthChart = null; |
| let database = JSON.parse(localStorage.getItem('ownershipDatabase')) || []; |
| |
| |
| document.addEventListener('DOMContentLoaded', function() { |
| renderDatabase(); |
| }); |
| |
| |
| function scrollToSection(id) { |
| document.getElementById(id).scrollIntoView({ behavior: 'smooth' }); |
| } |
| |
| |
| function handleFileSelect(event) { |
| const file = event.target.files[0]; |
| if (!file) return; |
| |
| document.getElementById('loading').classList.remove('hidden'); |
| document.getElementById('preview').classList.add('hidden'); |
| document.getElementById('upload-meta').classList.add('hidden'); |
| |
| const reader = new FileReader(); |
| reader.onload = function(e) { |
| const csv = e.target.result; |
| Papa.parse(csv, { |
| header: true, |
| skipEmptyLines: true, |
| delimiter: "|", |
| complete: function(results) { |
| rawData = results.data; |
| previewData(rawData.slice(0, 10)); |
| document.getElementById('loading').classList.add('hidden'); |
| document.getElementById('preview').classList.remove('hidden'); |
| document.getElementById('upload-meta').classList.remove('hidden'); |
| }, |
| error: function(error) { |
| alert("Error parsing CSV: " + error); |
| document.getElementById('loading').classList.add('hidden'); |
| } |
| }); |
| }; |
| reader.readAsText(file); |
| } |
| |
| |
| function previewData(data) { |
| const table = document.getElementById('data-preview-table'); |
| table.innerHTML = ''; |
| |
| |
| const thead = document.createElement('thead'); |
| thead.innerHTML = '<tr class="border-b"><th class="py-2 text-left">Ticker</th><th class="py-2 text-left">Nama</th><th class="py-2 text-right">Lokal</th><th class="py-2 text-right">Asing</th><th class="py-2 text-right">Institusi</th><th class="py-2 text-right">Tanggal</th></tr>'; |
| table.appendChild(thead); |
| |
| |
| const tbody = document.createElement('tbody'); |
| data.forEach(row => { |
| const tr = document.createElement('tr'); |
| tr.className = 'border-b hover:bg-gray-50'; |
| tr.innerHTML = ` |
| <td class="py-2 font-mono">${row.Ticker || '-'}</td> |
| <td class="py-2">${row['Nama Saham'] || '-'}</td> |
| <td class="py-2 text-right">${formatNumber(row.Lokal)}</td> |
| <td class="py-2 text-right">${formatNumber(row.Asing)}</td> |
| <td class="py-2 text-right">${formatNumber(row.Institusi)}</td> |
| <td class="py-2 text-right">${row.Tanggal || '-'}</td> |
| `; |
| tbody.appendChild(tr); |
| }); |
| table.appendChild(tbody); |
| } |
| |
| |
| function processData() { |
| |
| processedData = rawData.map(row => ({ |
| ...row, |
| Lokal: parseFloat(row.Lokal) || 0, |
| Asing: parseFloat(row.Asing) || 0, |
| Institusi: parseFloat(row.Institusi) || 0 |
| })).filter(row => row.Ticker && !isNaN(row.Asing)); |
| |
| |
| updateMetrics(); |
| |
| |
| renderCharts(); |
| |
| |
| renderTopMovers(); |
| |
| |
| scrollToSection('dashboard'); |
| } |
| |
| |
| function formatNumber(num) { |
| if (typeof num !== 'number') num = parseFloat(num); |
| if (isNaN(num)) return '-'; |
| return num.toLocaleString('id-ID', { maximumFractionDigits: 2 }); |
| } |
| |
| |
| function updateMetrics() { |
| const totalStocks = processedData.length; |
| const foreignUp = processedData.filter(row => row.Asing > 0).length; |
| const institutionalTotal = processedData.reduce((sum, row) => sum + row.Institusi, 0); |
| const avgForeignGrowth = processedData.length > 0 |
| ? processedData.reduce((sum, row) => sum + row.Asing, 0) / processedData.length |
| : 0; |
| |
| document.getElementById('total-stocks').textContent = totalStocks; |
| document.getElementById('foreign-up').textContent = foreignUp; |
| document.getElementById('institutional-total').textContent = formatNumber(institutionalTotal); |
| document.getElementById('foreign-growth').textContent = formatNumber(avgForeignGrowth) + '%'; |
| } |
| |
| |
| function renderCharts() { |
| |
| const avgLocal = processedData.reduce((sum, row) => sum + row.Lokal, 0) / processedData.length; |
| const avgForeign = processedData.reduce((sum, row) => sum + row.Asing, 0) / processedData.length; |
| const avgInstitutional = processedData.reduce((sum, row) => sum + row.Institusi, 0) / processedData.length; |
| |
| const ctx1 = document.getElementById('ownershipChart').getContext('2d'); |
| if (ownershipChart) ownershipChart.destroy(); |
| ownershipChart = new Chart(ctx1, { |
| type: 'pie', |
| data: { |
| labels: ['Investor Lokal', 'Investor Asing', 'Institusi'], |
| datasets: [{ |
| data: [avgLocal, avgForeign, avgInstitutional], |
| backgroundColor: ['#6366F1', '#10B981', '#8B5CF6'], |
| borderWidth: 2, |
| borderColor: '#FFFFFF', |
| hoverOffset: 10 |
| }] |
| }, |
| options: { |
| responsive: true, |
| plugins: { |
| legend: { |
| position: 'bottom' |
| } |
| } |
| } |
| }); |
| |
| |
| |
| const groupedByDate = {}; |
| processedData.forEach(row => { |
| const date = row.Tanggal; |
| if (!groupedByDate[date]) { |
| groupedByDate[date] = { sum: 0, count: 0 }; |
| } |
| groupedByDate[date].sum += parseFloat(row.Asing) || 0; |
| groupedByDate[date].count++; |
| }); |
| |
| const dates = Object.keys(groupedByDate).sort(); |
| const avgGrowth = dates.map(date => groupedByDate[date].sum / groupedByDate[date].count); |
| |
| const ctx2 = document.getElementById('growthChart').getContext('2d'); |
| if (growthChart) growthChart.destroy(); |
| growthChart = new Chart(ctx2, { |
| type: 'line', |
| data: { |
| labels: dates, |
| datasets: [{ |
| label: 'Rata-rata Kepemilikan Asing (%)', |
| data: avgGrowth, |
| borderColor: '#EC4899', |
| backgroundColor: 'rgba(236, 72, 153, 0.1)', |
| fill: true, |
| tension: 0.4, |
| pointBackgroundColor: '#EC4899' |
| }] |
| }, |
| options: { |
| responsive: true, |
| plugins: { |
| legend: { |
| display: false |
| } |
| }, |
| scales: { |
| y: { |
| beginAtZero: true, |
| title: { |
| display: true, |
| text: 'Pertumbuhan (%)' |
| } |
| }, |
| x: { |
| title: { |
| display: true, |
| text: 'Tanggal' |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| |
| function renderTopMovers() { |
| const sorted = [...processedData] |
| .sort((a, b) => (parseFloat(b.Asing) || 0) - (parseFloat(a.Asing) || 0)) |
| .slice(0, 10); |
| |
| const table = document.getElementById('top-movers-table').getElementsByTagName('tbody')[0]; |
| table.innerHTML = ''; |
| |
| sorted.forEach(row => { |
| const tr = document.createElement('tr'); |
| tr.className = 'border-b hover:bg-gray-50'; |
| tr.innerHTML = ` |
| <td class="py-2 font-mono font-semibold">${row.Ticker}</td> |
| <td class="py-2">${row['Nama Saham'] || '-'}</td> |
| <td class="py-2 text-right text-green-600 font-semibold">${formatNumber(row.Asing)}%</td> |
| `; |
| table.appendChild(tr); |
| }); |
| } |
| |
| |
| function saveToDatabase() { |
| if (rawData.length === 0) { |
| alert('Belum ada data untuk disimpan.'); |
| return; |
| } |
| |
| const month = document.getElementById('upload-month').value; |
| const year = document.getElementById('upload-year').value; |
| const number = document.getElementById('upload-number').value; |
| const monthNames = ["Januari", "Februari", "Maret", "April", "Mei", "Juni", |
| "Juli", "Agustus", "September", "Oktober", "November", "Desember"]; |
| |
| const entry = { |
| id: Date.now(), |
| month: parseInt(month), |
| year: parseInt(year), |
| number: parseInt(number), |
| monthName: monthNames[month - 1], |
| date: new Date().toISOString(), |
| recordCount: rawData.length |
| }; |
| |
| database.push(entry); |
| database.sort((a, b) => new Date(b.year, b.month) - new Date(a.year, a.month)); |
| |
| localStorage.setItem('ownershipDatabase', JSON.stringify(database)); |
| renderDatabase(); |
| |
| alert(`Data berhasil disimpan ke database!`); |
| } |
| |
| |
| function renderDatabase() { |
| const container = document.getElementById('database-container'); |
| container.innerHTML = ''; |
| |
| if (database.length === 0) { |
| container.innerHTML = ` |
| <div class="text-center py-10 text-gray-500 col-span-2"> |
| <i class="fas fa-database text-5xl mb-4 opacity-30"></i> |
| <p>Belum ada data dalam database</p> |
| </div> |
| `; |
| return; |
| } |
| |
| database.forEach(item => { |
| const monthNames = ["Januari", "Februari", "Maret", "April", "Mei", "Juni", |
| "Juli", "Agustus", "September", "Oktober", "November", "Desember"]; |
| |
| const itemDiv = document.createElement('div'); |
| itemDiv.className = 'bg-white p-6 rounded-xl shadow-md border database-item'; |
| itemDiv.innerHTML = ` |
| <div class="flex justify-between items-start"> |
| <div> |
| <span class="inline-block bg-indigo-100 text-indigo-800 text-xs px-3 py-1 rounded-full font-medium"> |
| No. ${item.number} |
| </span> |
| <h3 class="text-xl font-bold text-gray-800 mt-2">${item.monthName} ${item.year}</h3> |
| <p class="text-gray-600">Diunggah: ${new Date(item.date).toLocaleDateString('id-ID')}</p> |
| </div> |
| <div class="text-right"> |
| <span class="inline-block bg-gray-100 text-gray-800 text-sm px-3 py-1 rounded-full"> |
| ${item.recordCount} saham |
| </span> |
| <div class="mt-2 space-x-2"> |
| <button onclick="loadFromDatabase(${item.id})" class="text-blue-600 hover:text-blue-800 text-sm"> |
| <i class="fas fa-chart-bar"></i> Lihat |
| </button> |
| <button onclick="deleteFromDatabase(${item.id})" class="text-red-600 hover:text-red-800 text-sm"> |
| <i class="fas fa-trash"></i> Hapus |
| </button> |
| </div> |
| </div> |
| </div> |
| `; |
| container.appendChild(itemDiv); |
| }); |
| } |
| |
| |
| function loadFromDatabase(id) { |
| const item = database.find(d => d.id === id); |
| if (!item) return; |
| |
| alert(`Fitur lengkap akan diintegrasikan. Saat ini, fungsi ini akan memuat data laporan ${item.monthName} ${item.year}`); |
| |
| |
| |
| } |
| |
| |
| function deleteFromDatabase(id) { |
| if (confirm('Apakah Anda yakin ingin menghapus laporan ini dari database?')) { |
| database = database.filter(d => d.id !== id); |
| localStorage.setItem('ownershipDatabase', JSON.stringify(database)); |
| renderDatabase(); |
| } |
| } |
| |
| |
| function exportAnalysis() { |
| if (processedData.length === 0) { |
| alert('Tidak ada data untuk diekspor. Silakan unggah dan proses data terlebih dahulu.'); |
| return; |
| } |
| |
| |
| const exportData = processedData.map(row => ({ |
| 'Ticker': row.Ticker, |
| 'Nama Saham': row['Nama Saham'], |
| 'Kepemilikan Lokal (%)': formatNumber(row.Lokal), |
| 'Kepemilikan Asing (%)': formatNumber(row.Asing), |
| 'Kepemilikan Institusi (%)': formatNumber(row.Institusi), |
| 'Tanggal': row.Tanggal, |
| 'Kategori': row.Asing > 0 ? 'Net Buy' : 'Net Sell' |
| })); |
| |
| |
| const csv = Papa.unparse(exportData); |
| |
| |
| const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); |
| const url = URL.createObjectURL(blob); |
| const link = document.createElement('a'); |
| link.setAttribute('href', url); |
| link.setAttribute('download', 'analisis-kepemilikan-saham.csv'); |
| link.style.visibility = 'hidden'; |
| document.body.appendChild(link); |
| link.click(); |
| document.body.removeChild(link); |
| } |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-qwensite.hf.space/logo.svg" alt="qwensite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-qwensite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >QwenSite</a> - 🧬 <a href="https://enzostvs-qwensite.hf.space?remix=alterzick/ksei" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |