Create initial HTML file with styles#77
Open
Sazwan1996 wants to merge 1 commit into
Open
Conversation
Add HTML structure and styles for the application.tambahkan data semuanya,sync_portfolio.pyrequest.url.pathws.onmessagews.onmessagegoogle.cloudbigquery_client.pyledger_doc.existsredis.asynciofastapi.middleware.corsmain.pysgae.identityNext.jsqualityReport.innerHTMLstatsContainer.innerHTMLpageInfo.textContentfilteredData.lengthfilteredRowsLabel.textContentfullData.lengthtotalRowsLabel.textContenttableBody.innerHTMLtableHeader.innerHTMLparsed.lengthrowCountDisplay.textContentfileNameDisplay.textContentreader.onloadfileInfo.style.displayuploadPrompt.style.displayfileInput.valueuploadArea.style.borderColorobj.fileNameobj.data.lengthdarkToggle.innerHTMLChart.js<!DOCTYPE html>
<html lang="ms">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>⚡ Data Dashboard Power</title>
<!-- Bootstrap 5 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome 6 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<!-- Papa Parse (CSV) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.4.1/papaparse.min.js"></script>
<!-- SheetJS (Excel) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<style>
:root {
--bg-body: #f4f6f9;
--bg-card: #ffffff;
--text-color: #212529;
--border-color: #dee2e6;
--shadow: 0 8px 24px rgba(0,0,0,0.08);
}
[data-theme="dark"] {
--bg-body: #1a1d23;
--bg-card: #2c3038;
--text-color: #e9ecef;
--border-color: #495057;
--shadow: 0 8px 24px rgba(0,0,0,0.4);
}
body {
background-color: var(--bg-body);
color: var(--text-color);
transition: background 0.3s, color 0.3s;
padding: 1.5rem;
}
.card {
background-color: var(--bg-card);
border: 1px solid var(--border-color);
box-shadow: var(--shadow);
transition: background 0.3s, border 0.3s;
border-radius: 16px;
}
.table {
color: var(--text-color);
font-size: 0.85rem;
}
.table th {
background: #0d6efd;
color: white;
position: sticky;
top: 0;
z-index: 10;
white-space: nowrap;
}
.table td {
vertical-align: middle;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.table td .cell-full {
display: none;
white-space: normal;
word-break: break-all;
}
.table td:hover .cell-full {
display: block;
}
.table td:hover .cell-preview {
display: none;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 0.5rem;
}
.stats-item {
background: var(--bg-body);
padding: 0.5rem 0.75rem;
border-radius: 8px;
border-left: 4px solid #0d6efd;
}
.chart-container {
position: relative;
height: 250px;
width: 100%;
}
.btn-icon {
margin-right: 4px;
}
#uploadArea {
border: 2px dashed var(--border-color);
border-radius: 16px;
padding: 2.5rem;
text-align: center;
cursor: pointer;
transition: 0.3s;
}
#uploadArea:hover {
background: rgba(13, 110, 253, 0.05);
border-color: #0d6efd;
}
.dark-toggle {
cursor: pointer;
font-size: 1.5rem;
}
.badge-count {
background: #0d6efd;
color: white;
padding: 0.25rem 0.75rem;
border-radius: 20px;
}
@media (max-width: 768px) {
body { padding: 0.5rem; }
.table-wrapper { overflow-x: auto; }
}
</style>
</head>
<body>
<div class="container-fluid">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap">
<div>
<h1 class="display-6 fw-bold"><i class="fas fa-bolt text-warning"></i> Data Dashboard Power</h1>
<p class="text-muted">Muat naik, analisis, eksport — semua dalam satu HTML</p>
</div>
<div>
<span class="dark-toggle me-3" id="darkToggle" title="Tukar mod gelap/terang">
<i class="fas fa-moon"></i>
</span>
<button class="btn btn-outline-danger" id="clearStorageBtn"><i class="fas fa-trash"></i> Reset</button>
</div>
</div>
<!-- Upload Area -->
<div class="card p-4 mb-4" id="uploadArea">
<div id="uploadPrompt">
<i class="fas fa-cloud-upload-alt fa-3x text-primary mb-2"></i>
<h5>Drag & drop atau klik untuk muat naik</h5>
<p class="text-muted small">Sokongan: CSV, JSON (maks 5MB)</p>
<input type="file" id="fileInput" accept=".csv,.json" style="display:none;">
</div>
<div id="fileInfo" style="display:none;">
<i class="fas fa-file-alt fa-2x text-success me-2"></i>
<span id="fileNameDisplay" class="fw-bold"></span>
<span class="badge bg-secondary ms-2" id="rowCountDisplay">0 baris</span>
<button class="btn btn-sm btn-outline-secondary ms-3" id="changeFileBtn"><i class="fas fa-exchange-alt"></i> Tukar fail</button>
</div>
</div>
<!-- Stats & Quality Row -->
<div class="row g-4 mb-4">
<div class="col-md-8">
<div class="card p-3">
<h6><i class="fas fa-chart-simple text-primary"></i> Ringkasan Lajur</h6>
<div id="statsContainer" class="stats-grid"></div>
</div>
</div>
<div class="col-md-4">
<div class="card p-3">
<h6><i class="fas fa-clipboard-check text-success"></i> Laporan Kualiti</h6>
<div id="qualityReport">
<p class="text-muted">Tiada data.</p>
</div>
</div>
</div>
</div>
<!-- Charts -->
<div class="card p-3 mb-4">
<h6><i class="fas fa-chart-bar text-info"></i> Visualisasi Automatik</h6>
<div class="row g-3" id="chartContainer">
<div class="col-md-6"><canvas id="chart1"></canvas></div>
<div class="col-md-6"><canvas id="chart2"></canvas></div>
</div>
</div>
<!-- Table Controls -->
<div class="d-flex flex-wrap justify-content-between align-items-center mb-3 gap-2">
<div>
<span class="badge bg-primary rounded-pill me-2" id="totalRowsLabel">0 baris</span>
<span class="badge bg-secondary rounded-pill" id="filteredRowsLabel">0 ditapis</span>
</div>
<div class="d-flex flex-wrap gap-2">
<input type="text" class="form-control form-control-sm" id="globalSearch" placeholder="Cari semua..." style="width:180px;">
<button class="btn btn-sm btn-success" id="exportCsvBtn"><i class="fas fa-file-csv"></i> CSV</button>
<button class="btn btn-sm btn-warning" id="exportExcelBtn"><i class="fas fa-file-excel"></i> Excel</button>
<button class="btn btn-sm btn-info text-white" id="exportHtmlBtn"><i class="fas fa-code"></i> HTML</button>
</div>
</div>
<!-- Table -->
<div class="card p-0 overflow-hidden">
<div class="table-wrapper" style="max-height:500px; overflow-y:auto;">
<table class="table table-hover table-striped mb-0" id="dataTable">
<thead>
<tr id="tableHeader"></tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- Pagination -->
<div class="d-flex justify-content-between align-items-center p-3 border-top">
<span id="pageInfo" class="small">Halaman 1 / 1</span>
<div>
<button class="btn btn-outline-primary btn-sm" id="prevPage"><i class="fas fa-chevron-left"></i></button>
<button class="btn btn-outline-primary btn-sm" id="nextPage"><i class="fas fa-chevron-right"></i></button>
</div>
</div>
</div>
<div class="mt-4 text-center text-muted small">
<i class="fas fa-database"></i> Data disimpan secara automatik di localStorage pelayar anda.
</div>
</div>
<script>
// ============================================================
// STATE
// ============================================================
let fullData = [];
let filteredData = [];
let columns = [];
let currentPage = 1;
const rowsPerPage = 10;
let sortCol = null;
let sortAsc = true;
let columnFilters = {}; // { colName: searchString }
let chartInstances = { chart1: null, chart2: null };
let currentFileName = '';
const STORAGE_KEY = 'power_dashboard_data';
// ============================================================
// DOM REFS
// ============================================================
const fileInput = document.getElementById('fileInput');
const uploadArea = document.getElementById('uploadArea');
const uploadPrompt = document.getElementById('uploadPrompt');
const fileInfo = document.getElementById('fileInfo');
const fileNameDisplay = document.getElementById('fileNameDisplay');
const rowCountDisplay = document.getElementById('rowCountDisplay');
const changeFileBtn = document.getElementById('changeFileBtn');
const tableHeader = document.getElementById('tableHeader');
const tableBody = document.getElementById('tableBody');
const statsContainer = document.getElementById('statsContainer');
const qualityReport = document.getElementById('qualityReport');
const totalRowsLabel = document.getElementById('totalRowsLabel');
const filteredRowsLabel = document.getElementById('filteredRowsLabel');
const pageInfo = document.getElementById('pageInfo');
const prevPage = document.getElementById('prevPage');
const nextPage = document.getElementById('nextPage');
const globalSearch = document.getElementById('globalSearch');
const clearStorageBtn = document.getElementById('clearStorageBtn');
const darkToggle = document.getElementById('darkToggle');
const exportCsvBtn = document.getElementById('exportCsvBtn');
const exportExcelBtn = document.getElementById('exportExcelBtn');
const exportHtmlBtn = document.getElementById('exportHtmlBtn');
// ============================================================
// DARK MODE
// ============================================================
let darkMode = localStorage.getItem('power_dashboard_dark') === 'true';
if (darkMode) document.documentElement.setAttribute('data-theme', 'dark');
darkToggle.innerHTML = darkMode ? '<i class="fas fa-sun"></i>' : '<i class="fas fa-moon"></i>';
darkToggle.addEventListener('click', () => {
darkMode = !darkMode;
localStorage.setItem('power_dashboard_dark', darkMode);
document.documentElement.setAttribute('data-theme', darkMode ? 'dark' : '');
darkToggle.innerHTML = darkMode ? '<i class="fas fa-sun"></i>' : '<i class="fas fa-moon"></i>';
});
// ============================================================
// LOCAL STORAGE
// ============================================================
function saveToStorage() {
if (fullData.length > 0) {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ data: fullData, fileName: currentFileName }));
}
}
function loadFromStorage() {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
try {
const obj = JSON.parse(raw);
if (obj.data && obj.data.length > 0) {
fullData = obj.data;
currentFileName = obj.fileName || 'Disimpan';
return true;
}
} catch(e) {}
}
return false;
}
// ============================================================
// UPLOAD
// ============================================================
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.style.borderColor = '#0d6efd'; });
uploadArea.addEventListener('dragleave', () => { uploadArea.style.borderColor = 'var(--border-color)'; });
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.style.borderColor = 'var(--border-color)';
if (e.dataTransfer.files.length) handleFile(e.dataTransfer.files[0]);
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length) handleFile(e.target.files[0]);
fileInput.value = '';
});
changeFileBtn.addEventListener('click', () => {
fullData = [];
filteredData = [];
columns = [];
currentFileName = '';
localStorage.removeItem(STORAGE_KEY);
renderAll();
uploadPrompt.style.display = 'block';
fileInfo.style.display = 'none';
});
function handleFile(file) {
const ext = file.name.split('.').pop().toLowerCase();
currentFileName = file.name;
const reader = new FileReader();
reader.onload = (e) => {
let parsed = [];
if (ext === 'csv') {
const result = Papa.parse(e.target.result, { header: true, dynamicTyping: true, skipEmptyLines: true });
if (result.errors.length > 0) {
alert('Ralat CSV: ' + result.errors[0].message);
return;
}
parsed = result.data;
} else if (ext === 'json') {
try { parsed = JSON.parse(e.target.result); }
catch(err) { alert('JSON tidak sah: ' + err.message); return; }
if (!Array.isArray(parsed)) { alert('JSON mestilah array objek.'); return; }
} else {
alert('Sila muat naik CSV atau JSON.');
return;
}
if (parsed.length === 0) { alert('Fail kosong.'); return; }
fullData = parsed;
saveToStorage();
renderAll();
uploadPrompt.style.display = 'none';
fileInfo.style.display = 'block';
fileNameDisplay.textContent = file.name;
rowCountDisplay.textContent = parsed.length + ' baris';
};
reader.readAsText(file);
}
// ============================================================
// FILTER, SORT, PAGINATE
// ============================================================
function processData() {
let data = [...fullData];
if (data.length === 0) { filteredData = []; columns = []; return; }
columns = Object.keys(data[0]);
// Global search
const global = globalSearch.value.toLowerCase().trim();
if (global) {
data = data.filter(row => {
return columns.some(col => {
const val = row[col];
if (val === null || val === undefined) return false;
return String(val).toLowerCase().includes(global);
});
});
}
// Column filters
for (const [col, search] of Object.entries(columnFilters)) {
if (search && search.trim() !== '') {
const term = search.trim().toLowerCase();
data = data.filter(row => {
const val = row[col];
if (val === null || val === undefined) return false;
return String(val).toLowerCase().includes(term);
});
}
}
// Sort
if (sortCol) {
data.sort((a, b) => {
const va = a[sortCol] ?? '';
const vb = b[sortCol] ?? '';
if (typeof va === 'number' && typeof vb === 'number') {
return sortAsc ? va - vb : vb - va;
}
const sa = String(va).toLowerCase();
const sb = String(vb).toLowerCase();
return sortAsc ? sa.localeCompare(sb) : sb.localeCompare(sa);
});
}
filteredData = data;
}
function getPaginatedData() {
const start = (currentPage - 1) * rowsPerPage;
return filteredData.slice(start, start + rowsPerPage);
}
function totalPages() {
return Math.ceil(filteredData.length / rowsPerPage) || 1;
}
// ============================================================
// RENDER TABLE
// ============================================================
function renderTable() {
if (fullData.length === 0) {
tableHeader.innerHTML = '';
tableBody.innerHTML = '<tr><td class="text-center text-muted">Tiada data. Muat naik fail CSV/JSON.</td></tr>';
return;
}
// Header with sort
let headerHtml = '<tr>';
for (const col of columns) {
let sortIcon = '';
if (sortCol === col) sortIcon = sortAsc ? ' ▲' : ' ▼';
headerHtml += `<th style="cursor:pointer;" data-col="${col}">${col}${sortIcon}</th>`;
}
headerHtml += '</tr>';
tableHeader.innerHTML = headerHtml;
// Click sort
tableHeader.querySelectorAll('th').forEach(th => {
th.addEventListener('click', () => {
const col = th.dataset.col;
if (sortCol === col) sortAsc = !sortAsc;
else { sortCol = col; sortAsc = true; }
currentPage = 1;
renderAll();
});
});
// Body
const paginated = getPaginatedData();
if (paginated.length === 0) {
tableBody.innerHTML = '<tr><td colspan="999" class="text-center text-muted">Tiada rekod sepadan.</td></tr>';
return;
}
let bodyHtml = '';
for (const row of paginated) {
bodyHtml += '<tr>';
for (const col of columns) {
let val = row[col];
if (val === null || val === undefined) val = '';
const display = String(val);
bodyHtml += `<td title="${display.replace(/"/g, '"')}">
<span class="cell-preview">${display.length > 30 ? display.substring(0,30)+'…' : display}</span>
<span class="cell-full">${display}</span>
</td>`;
}
bodyHtml += '</tr>';
}
tableBody.innerHTML = bodyHtml;
// Update labels
totalRowsLabel.textContent = fullData.length + ' baris';
filteredRowsLabel.textContent = filteredData.length + ' ditapis';
const tp = totalPages();
pageInfo.textContent = `Halaman ${currentPage} / ${tp}`;
}
// ============================================================
// RENDER STATS & QUALITY
// ============================================================
function renderStats() {
if (fullData.length === 0) {
statsContainer.innerHTML = '<span class="text-muted">Tiada data.</span>';
qualityReport.innerHTML = '<p class="text-muted">Tiada data.</p>';
return;
}
const
|
@Sazwan1996 please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add HTML structure and styles for the application.tambahkan data semuanya,sync_portfolio.pyrequest.url.pathws.onmessagews.onmessagegoogle.cloudbigquery_client.pyledger_doc.existsredis.asynciofastapi.middleware.corsmain.pysgae.identityNext.jsqualityReport.innerHTMLstatsContainer.innerHTMLpageInfo.textContentfilteredData.lengthfilteredRowsLabel.textContentfullData.lengthtotalRowsLabel.textContenttableBody.innerHTMLtableHeader.innerHTMLparsed.lengthrowCountDisplay.textContentfileNameDisplay.textContentreader.onloadfileInfo.style.displayuploadPrompt.style.displayfileInput.valueuploadArea.style.borderColorobj.fileNameobj.data.lengthdarkToggle.innerHTMLChart.js
<title>⚡ Data Dashboard Power</title> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.4.1/papaparse.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> <style> :root { --bg-body: #f4f6f9; --bg-card: #ffffff; --text-color: #212529; --border-color: #dee2e6; --shadow: 0 8px 24px rgba(0,0,0,0.08); } [data-theme="dark"] { --bg-body: #1a1d23; --bg-card: #2c3038; --text-color: #e9ecef; --border-color: #495057; --shadow: 0 8px 24px rgba(0,0,0,0.4); } body { background-color: var(--bg-body); color: var(--text-color); transition: background 0.3s, color 0.3s; padding: 1.5rem; } .card { background-color: var(--bg-card); border: 1px solid var(--border-color); box-shadow: var(--shadow); transition: background 0.3s, border 0.3s; border-radius: 16px; } .table { color: var(--text-color); font-size: 0.85rem; } .table th { background: #0d6efd; color: white; position: sticky; top: 0; z-index: 10; white-space: nowrap; } .table td { vertical-align: middle; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .table td .cell-full { display: none; white-space: normal; word-break: break-all; } .table td:hover .cell-full { display: block; } .table td:hover .cell-preview { display: none; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.5rem; } .stats-item { background: var(--bg-body); padding: 0.5rem 0.75rem; border-radius: 8px; border-left: 4px solid #0d6efd; } .chart-container { position: relative; height: 250px; width: 100%; } .btn-icon { margin-right: 4px; } #uploadArea { border: 2px dashed var(--border-color); border-radius: 16px; padding: 2.5rem; text-align: center; cursor: pointer; transition: 0.3s; } #uploadArea:hover { background: rgba(13, 110, 253, 0.05); border-color: #0d6efd; } .dark-toggle { cursor: pointer; font-size: 1.5rem; } .badge-count { background: #0d6efd; color: white; padding: 0.25rem 0.75rem; border-radius: 20px; } @media (max-width: 768px) { body { padding: 0.5rem; } .table-wrapper { overflow-x: auto; } } </style>Data Dashboard Power
Muat naik, analisis, eksport — semua dalam satu HTML
Tiada data.
'; return; } const