From 1d94c3ef51672cb66418ee260895ad5b251a1961 Mon Sep 17 00:00:00 2001
From: Flatlogic Bot
Date: Thu, 2 Oct 2025 19:19:12 +0000
Subject: [PATCH] initial csv uploader and dashboard
---
assets/css/custom.css | 115 +++++++++++++++++
assets/js/main.js | 203 ++++++++++++++++++++++++++++++
dashboard.php | 225 +++++++++++++++++++++++++++++++++
index.php | 282 +++++++++++++++++++++---------------------
upload.php | 127 +++++++++++++++++++
5 files changed, 810 insertions(+), 142 deletions(-)
create mode 100644 assets/css/custom.css
create mode 100644 assets/js/main.js
create mode 100644 dashboard.php
create mode 100644 upload.php
diff --git a/assets/css/custom.css b/assets/css/custom.css
new file mode 100644
index 0000000..9d4d11b
--- /dev/null
+++ b/assets/css/custom.css
@@ -0,0 +1,115 @@
+/*
+Palette:
+- Base: #FFFFFF
+- Panels: #F7F3ED
+- Hovers: #E8E1D9
+- Accent: #D4A373
+- Text: #333333
+*/
+
+body {
+ background-color: #FFFFFF;
+ font-family: 'Inter', sans-serif;
+ color: #333333;
+ padding-top: 2rem;
+ padding-bottom: 2rem;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-family: 'Montserrat', sans-serif;
+ font-weight: 700;
+}
+
+h1 { font-size: 32px; }
+h2 { font-size: 24px; }
+h3 { font-size: 20px; }
+
+.container {
+ max-width: 1200px;
+}
+
+.card {
+ background-color: rgba(247, 243, 237, 0.8);
+ backdrop-filter: blur(4px);
+ border: 1px solid rgba(0,0,0,0.05);
+ border-radius: 14px;
+ box-shadow: 0 8px 16px rgba(0,0,0,0.04);
+ padding: 1.5rem;
+}
+
+.card-title {
+ margin-bottom: 1.5rem;
+}
+
+.btn {
+ border-radius: 50px;
+ padding: 12px 24px;
+ font-weight: 500;
+ transition: all 0.2s ease-in-out;
+}
+
+.btn-primary {
+ background-color: #D4A373;
+ border-color: #D4A373;
+ color: #fff;
+}
+
+.btn-primary:hover {
+ background-color: #C39263;
+ border-color: #C39263;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+}
+
+.btn-secondary {
+ background-color: #fff;
+ border-color: #E8E1D9;
+ color: #333;
+}
+
+.btn-secondary:hover {
+ background-color: #E8E1D9;
+ border-color: #E8E1D9;
+}
+
+.form-control, .form-select {
+ border-radius: 10px;
+ background-color: #FFFFFF;
+ border: 1px solid #E8E1D9;
+ padding: 12px;
+}
+
+.form-control:focus, .form-select:focus {
+ border-color: #D4A373;
+ box-shadow: 0 0 0 4px rgba(212, 163, 115, 0.15);
+}
+
+.table {
+ border-color: #E8E1D9;
+}
+
+.table th {
+ font-weight: 500;
+ color: #777;
+}
+
+.table-hover tbody tr:hover {
+ background-color: rgba(232, 225, 217, 0.5);
+}
+
+.lead {
+ color: #666;
+ font-size: 1.1rem;
+}
+
+.divider {
+ border-top: 1px solid rgba(0,0,0,0.07);
+ margin: 2rem 0;
+}
+
+/* Chart.js minimal styling */
+.chart-container {
+ position: relative;
+ height: 40vh;
+ width: 100%;
+}
\ No newline at end of file
diff --git a/assets/js/main.js b/assets/js/main.js
new file mode 100644
index 0000000..4f32a0c
--- /dev/null
+++ b/assets/js/main.js
@@ -0,0 +1,203 @@
+// Main javascript file
+
+document.addEventListener('DOMContentLoaded', function () {
+ const filters = document.querySelectorAll('select[id^="filter_"]');
+ filters.forEach(filter => {
+ filter.addEventListener('change', applyFilters);
+ });
+
+ if (document.getElementById('dateRange')) {
+ const dateRangePicker = $('#dateRange').daterangepicker({
+ opens: 'left',
+ autoUpdateInput: false,
+ locale: {
+ cancelLabel: 'Clear'
+ }
+ });
+
+ dateRangePicker.on('apply.daterangepicker', function(ev, picker) {
+ $(this).val(picker.startDate.format('YYYY-MM-DD') + ' - ' + picker.endDate.format('YYYY-MM-DD'));
+ applyFilters();
+ });
+
+ dateRangePicker.on('cancel.daterangepicker', function(ev, picker) {
+ $(this).val('');
+ applyFilters();
+ });
+ }
+
+ // Keep track of chart instances
+ const charts = {};
+
+ function applyFilters() {
+ const activeFilters = {};
+ filters.forEach(f => {
+ if (f.value) {
+ activeFilters[f.id.replace('filter_', '')] = f.value;
+ }
+ });
+
+ let filteredData = sampleData.filter(row => {
+ for (const col in activeFilters) {
+ if (row[col] != activeFilters[col]) {
+ return false;
+ }
+ }
+ return true;
+ });
+
+ const dateRange = $('#dateRange').val();
+ if (dateRange && charts.timeChart) {
+ const dates = dateRange.split(' - ');
+ const startDate = moment(dates[0], 'YYYY-MM-DD');
+ const endDate = moment(dates[1], 'YYYY-MM-DD');
+ const timeCol = Object.keys(charts.timeChart.data.datasets[0]._meta)[0];
+
+ filteredData = filteredData.filter(row => {
+ const rowDate = moment(row[timeCol], 'YYYY-MM-DD');
+ return rowDate.isBetween(startDate, endDate, null, '[]');
+ });
+ }
+
+ updateDashboard(filteredData);
+ }
+
+ function updateDashboard(data) {
+ updateKpis(data);
+ updateCharts(data);
+ updateTable(data);
+ }
+
+ function updateKpis(data) {
+ // This is a simplified example. It assumes the first KPI is always the one to be updated.
+ const kpiElements = document.querySelectorAll('.card-text.fs-4');
+ if (kpiElements.length >= 3) {
+ kpiElements[0].textContent = data.length;
+
+ const kpiCol = document.querySelector('.card-subtitle.mb-2.text-muted').textContent.replace('Avg. ', '').replace('Total ', '');
+
+ const total = data.reduce((sum, row) => sum + parseFloat(row[kpiCol] || 0), 0);
+ const avg = data.length > 0 ? total / data.length : 0;
+
+ kpiElements[1].textContent = Math.round(avg * 100) / 100;
+ kpiElements[2].textContent = total;
+ }
+ }
+
+ function updateCharts(data) {
+ if (charts.timeChart) {
+ const timeCol = charts.timeChart.data.datasets[0].label.replace(' over Time', '');
+ charts.timeChart.data.labels = data.map(row => row[timeCol]);
+ charts.timeChart.data.datasets[0].data = data.map(row => row[timeCol]);
+ charts.timeChart.update();
+ }
+
+ if (charts.categoryChart) {
+ const catCol = charts.categoryChart.data.datasets[0].label.replace('Top 10 by ', '');
+ const metCol = charts.categoryChart.data.datasets[0].label.replace('Top 10 by ', '');
+
+ const grouped_data = {};
+ data.forEach(row => {
+ const category = row[catCol];
+ if (!grouped_data[category]) {
+ grouped_data[category] = 0;
+ }
+ grouped_data[category] += parseFloat(row[metCol]);
+ });
+
+ const sorted_data = Object.entries(grouped_data).sort(([,a],[,b]) => b-a).slice(0, 10);
+
+ charts.categoryChart.data.labels = sorted_data.map(item => item[0]);
+ charts.categoryChart.data.datasets[0].data = sorted_data.map(item => item[1]);
+ charts.categoryChart.update();
+ }
+
+ if (charts.histogramChart) {
+ const metCol = charts.histogramChart.data.datasets[0].label.replace('Histogram of ', '');
+ charts.histogramChart.data.labels = data.map(row => row[metCol]);
+ charts.histogramChart.data.datasets[0].data = data.map(row => row[metCol]);
+ charts.histogramChart.update();
+ }
+ }
+
+ function updateTable(data) {
+ const tableBody = document.querySelector('.table-responsive tbody');
+ tableBody.innerHTML = '';
+ data.forEach(row => {
+ const tr = document.createElement('tr');
+ for (const cell in row) {
+ const td = document.createElement('td');
+ td.textContent = row[cell];
+ tr.appendChild(td);
+ }
+ tableBody.appendChild(tr);
+ });
+ }
+
+ document.getElementById('exportCsv').addEventListener('click', exportToCsv);
+
+ function exportToCsv() {
+ const activeFilters = {};
+ filters.forEach(f => {
+ if (f.value) {
+ activeFilters[f.id.replace('filter_', '')] = f.value;
+ }
+ });
+
+ let filteredData = sampleData.filter(row => {
+ for (const col in activeFilters) {
+ if (row[col] != activeFilters[col]) {
+ return false;
+ }
+ }
+ return true;
+ });
+
+ const dateRange = $('#dateRange').val();
+ if (dateRange && charts.timeChart) {
+ const dates = dateRange.split(' - ');
+ const startDate = moment(dates[0], 'YYYY-MM-DD');
+ const endDate = moment(dates[1], 'YYYY-MM-DD');
+ const timeCol = Object.keys(charts.timeChart.data.datasets[0]._meta)[0];
+
+ filteredData = filteredData.filter(row => {
+ const rowDate = moment(row[timeCol], 'YYYY-MM-DD');
+ return rowDate.isBetween(startDate, endDate, null, '[]');
+ });
+ }
+
+ if (filteredData.length === 0) {
+ alert("No data to export.");
+ return;
+ }
+
+ const headers = Object.keys(filteredData[0]);
+ const csvContent = [
+ headers.join(','),
+ ...filteredData.map(row => headers.map(header => JSON.stringify(row[header])).join(','))
+ ].join('\n');
+
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
+ const link = document.createElement('a');
+ const url = URL.createObjectURL(blob);
+ link.setAttribute('href', url);
+ link.setAttribute('download', 'filtered_data.csv');
+ link.style.visibility = 'hidden';
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ }
+
+ document.querySelectorAll('.export-png').forEach(button => {
+ button.addEventListener('click', function() {
+ const chartId = this.dataset.chart;
+ const canvas = document.getElementById(chartId);
+ html2canvas(canvas.parentElement).then(canvas => {
+ const link = document.createElement('a');
+ link.download = chartId + '.png';
+ link.href = canvas.toDataURL();
+ link.click();
+ });
+ });
+ });
+}
\ No newline at end of file
diff --git a/dashboard.php b/dashboard.php
new file mode 100644
index 0000000..dacee36
--- /dev/null
+++ b/dashboard.php
@@ -0,0 +1,225 @@
+No data available to generate a dashboard.
';
+ return;
+}
+
+$analysis = $_SESSION['csv_analysis'];
+$schema = $analysis['schema'];
+$suggestions = $analysis['suggestions'];
+$sample = $analysis['sample'];
+
+// Apply user-defined types from preview screen
+if (isset($_POST['column_types'])) {
+ foreach ($_POST['column_types'] as $col => $type) {
+ if (isset($schema[$col])) {
+ $schema[$col]['type'] = $type;
+ }
+ }
+ // Re-run suggestions with updated types
+ // (This requires the suggestion logic to be available here)
+ // For now, we'll just use the updated schema
+}
+
+?>
+
+
+
+
+
+
+
Filters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Time Chart
+
+
+
+
+
+
+
+
Histogram
+
+
+
+
+
+
+
+
+
+
+
Category Chart
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Sample Data
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/index.php b/index.php
index 7205f3d..1755a07 100644
--- a/index.php
+++ b/index.php
@@ -1,150 +1,148 @@
- $type) {
+ if (isset($_SESSION['csv_analysis']['schema'][$col])) {
+ $_SESSION['csv_analysis']['schema'][$col]['type'] = $type;
+ }
+ }
+ // Re-run suggestions if needed (or just use updated schema in dashboard.php)
+ }
+ $_SESSION['show_dashboard'] = true;
+ }
+ header('Location: index.php');
+ exit;
+}
+
+$show_dashboard = isset($_SESSION['show_dashboard']) && $_SESSION['show_dashboard'];
+$csv_analysis = isset($_SESSION['csv_analysis']) ? $_SESSION['csv_analysis'] : null;
-$phpVersion = PHP_VERSION;
-$now = date('Y-m-d H:i:s');
?>
-
+
-
-
- New Style
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ dashboard-for-philip-v001
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
Analyzing your requirements and generating your website…
-
- Loading…
-
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
+
+
+
+ Instant CSV Dashboard
+ Upload a CSV or TSV file to automatically generate an interactive dashboard.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
-
+