test-site / index.html
Sabrack123's picture
Date Name Email Email verified Country Birth date
198928d verified
Raw
History Blame Contribute Delete
45.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User Analytics Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<style>
.dashboard-card {
transition: all 0.3s ease;
}
.dashboard-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.file-upload {
border: 2px dashed #cbd5e0;
transition: all 0.3s ease;
}
.file-upload:hover {
border-color: #4f46e5;
}
.data-table {
max-height: 400px;
overflow-y: auto;
}
.heatmap-container {
height: 400px;
}
@media (max-width: 768px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body class="bg-gray-50">
<div id="app" class="min-h-screen">
<!-- Navigation -->
<nav class="bg-indigo-600 text-white shadow-lg">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16 items-center">
<div class="flex items-center">
<i data-feather="bar-chart-2" class="h-6 w-6 mr-2"></i>
<span class="text-xl font-semibold">User Analytics</span>
</div>
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<a href="#" class="px-3 py-2 rounded-md text-sm font-medium bg-indigo-700">Dashboard</a>
<a href="#" class="px-3 py-2 rounded-md text-sm font-medium hover:bg-indigo-500">Reports</a>
<a href="#" class="px-3 py-2 rounded-md text-sm font-medium hover:bg-indigo-500">Settings</a>
</div>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Upload Section -->
<div class="mb-8 bg-white rounded-lg shadow p-6" data-aos="fade-up">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Upload User Data</h2>
<div class="mt-4 md:mt-0">
<button id="sampleDataBtn" class="mr-3 px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50">
Load Sample Data
</button>
<button id="exportBtn" class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700">
Export Data
</button>
</div>
</div>
<div class="file-upload p-8 rounded-lg text-center">
<div class="flex flex-col items-center justify-center">
<i data-feather="upload-cloud" class="h-12 w-12 text-indigo-500 mb-3"></i>
<p class="text-lg font-medium text-gray-700">Drag & drop your CSV file here</p>
<p class="text-sm text-gray-500 mb-4">or</p>
<label for="file-upload" class="cursor-pointer px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700">
Browse Files
</label>
<input id="file-upload" type="file" accept=".csv" class="hidden">
</div>
</div>
<div id="uploadPreview" class="mt-6 hidden">
<h3 class="text-lg font-medium text-gray-900 mb-4">Preview</h3>
<div class="data-table overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Verified</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Country</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Birth Date</th>
</tr>
</thead>
<tbody id="previewTableBody" class="bg-white divide-y divide-gray-200">
<!-- Data will be inserted here -->
</tbody>
</table>
</div>
<div class="mt-4 flex justify-end">
<button id="confirmUpload" class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-600 hover:bg-green-700">
Confirm Upload
</button>
</div>
</div>
</div>
<!-- Dashboard Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<!-- Total Users Card -->
<div class="dashboard-card bg-white rounded-lg shadow p-6" data-aos="fade-up">
<div class="flex items-center">
<div class="p-3 rounded-full bg-indigo-100 text-indigo-600 mr-4">
<i data-feather="users" class="h-6 w-6"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-500">Total Users</p>
<p id="totalUsers" class="text-2xl font-semibold text-gray-800">0</p>
</div>
</div>
</div>
<!-- New Users (7 days) Card -->
<div class="dashboard-card bg-white rounded-lg shadow p-6" data-aos="fade-up" data-aos-delay="100">
<div class="flex items-center">
<div class="p-3 rounded-full bg-green-100 text-green-600 mr-4">
<i data-feather="user-plus" class="h-6 w-6"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-500">New Users (7d)</p>
<div class="flex items-baseline">
<p id="newUsers7d" class="text-2xl font-semibold text-gray-800">0</p>
<span id="growth7d" class="ml-2 text-sm font-medium text-green-600 flex items-center">
<i data-feather="trending-up" class="h-4 w-4 mr-1"></i>
<span>0%</span>
</span>
</div>
</div>
</div>
</div>
<!-- Top Country Card -->
<div class="dashboard-card bg-white rounded-lg shadow p-6" data-aos="fade-up" data-aos-delay="200">
<div class="flex items-center">
<div class="p-3 rounded-full bg-blue-100 text-blue-600 mr-4">
<i data-feather="globe" class="h-6 w-6"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-500">Top Country</p>
<p id="topCountry" class="text-2xl font-semibold text-gray-800">-</p>
</div>
</div>
</div>
<!-- Verified Users Card -->
<div class="dashboard-card bg-white rounded-lg shadow p-6" data-aos="fade-up" data-aos-delay="300">
<div class="flex items-center">
<div class="p-3 rounded-full bg-purple-100 text-purple-600 mr-4">
<i data-feather="check-circle" class="h-6 w-6"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-500">Verified Users</p>
<p id="verifiedUsers" class="text-2xl font-semibold text-gray-800">0%</p>
</div>
</div>
</div>
</div>
<!-- Date Range Filter -->
<div class="bg-white rounded-lg shadow p-6 mb-8" data-aos="fade-up">
<h2 class="text-xl font-bold text-gray-800 mb-6">Date Range Analysis</h2>
<div class="flex flex-col md:flex-row md:items-center md:space-x-4 space-y-4 md:space-y-0">
<div class="flex-1">
<label for="startDate" class="block text-sm font-medium text-gray-700 mb-1">Start Date</label>
<input type="date" id="startDate" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div class="flex-1">
<label for="endDate" class="block text-sm font-medium text-gray-700 mb-1">End Date</label>
<input type="date" id="endDate" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div class="flex items-end">
<button id="applyDateFilter" class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700">
Apply Filter
</button>
</div>
</div>
</div>
<!-- Charts Section -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
<!-- Growth Chart -->
<div class="bg-white rounded-lg shadow p-6" data-aos="fade-up">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-bold text-gray-800">User Growth</h2>
<div class="relative">
<select id="growthPeriod" class="appearance-none bg-white border border-gray-300 rounded-md pl-3 pr-8 py-2 text-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
<option value="7">Last 7 days</option>
<option value="14">Last 14 days</option>
<option value="30">Last 30 days</option>
<option value="60">Last 60 days</option>
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<i data-feather="chevron-down" class="h-4 w-4"></i>
</div>
</div>
</div>
<div class="h-80">
<canvas id="growthChart"></canvas>
</div>
</div>
<!-- Country Distribution Chart -->
<div class="bg-white rounded-lg shadow p-6" data-aos="fade-up" data-aos-delay="100">
<h2 class="text-xl font-bold text-gray-800 mb-6">Country Distribution</h2>
<div class="h-80">
<canvas id="countryChart"></canvas>
</div>
</div>
</div>
<!-- Data Tables Section -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
<!-- User Data Table -->
<div class="bg-white rounded-lg shadow overflow-hidden" data-aos="fade-up">
<div class="px-6 py-4 border-b border-gray-200">
<h2 class="text-xl font-bold text-gray-800">User Data</h2>
</div>
<div class="p-4">
<div class="mb-4 flex justify-between items-center">
<div class="relative w-64">
<input type="text" id="searchUsers" placeholder="Search users..." class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<i data-feather="search" class="h-4 w-4 text-gray-400"></i>
</div>
</div>
<div class="relative">
<select id="filterCountry" class="appearance-none bg-white border border-gray-300 rounded-md pl-3 pr-8 py-2 text-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
<option value="">All Countries</option>
<!-- Countries will be added dynamically -->
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<i data-feather="chevron-down" class="h-4 w-4"></i>
</div>
</div>
</div>
<div class="data-table overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Country</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Verified</th>
</tr>
</thead>
<tbody id="userTableBody" class="bg-white divide-y divide-gray-200">
<!-- User data will be inserted here -->
</tbody>
</table>
</div>
<div class="mt-4 flex justify-between items-center">
<div class="text-sm text-gray-500">
Showing <span id="startItem">1</span> to <span id="endItem">10</span> of <span id="totalItems">0</span> users
</div>
<div class="flex space-x-2">
<button id="prevPage" class="px-3 py-1 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50">
Previous
</button>
<button id="nextPage" class="px-3 py-1 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50">
Next
</button>
</div>
</div>
</div>
</div>
<!-- Country Comparison Table -->
<div class="bg-white rounded-lg shadow overflow-hidden" data-aos="fade-up" data-aos-delay="100">
<div class="px-6 py-4 border-b border-gray-200">
<h2 class="text-xl font-bold text-gray-800">Country Comparison</h2>
</div>
<div class="p-4">
<div class="data-table overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Country</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total Users</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last 7d</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Growth</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Verified %</th>
</tr>
</thead>
<tbody id="countryTableBody" class="bg-white divide-y divide-gray-200">
<!-- Country data will be inserted here -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Heatmap Section -->
<div class="bg-white rounded-lg shadow p-6 mb-8" data-aos="fade-up">
<h2 class="text-xl font-bold text-gray-800 mb-6">User Density Heatmap</h2>
<div class="heatmap-container bg-gray-100 rounded-lg flex items-center justify-center">
<p class="text-gray-500">World map visualization would appear here with actual implementation</p>
</div>
</div>
</main>
<!-- Footer -->
<footer class="bg-gray-100 border-t border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="flex flex-col md:flex-row justify-between items-center">
<p class="text-sm text-gray-500">© 2023 User Analytics Dashboard. All rights reserved.</p>
<div class="mt-4 md:mt-0 flex space-x-6">
<a href="#" class="text-gray-400 hover:text-gray-500">
<i data-feather="github" class="h-5 w-5"></i>
</a>
<a href="#" class="text-gray-400 hover:text-gray-500">
<i data-feather="twitter" class="h-5 w-5"></i>
</a>
<a href="#" class="text-gray-400 hover:text-gray-500">
<i data-feather="linkedin" class="h-5 w-5"></i>
</a>
</div>
</div>
</div>
</footer>
</div>
<script>
// Initialize AOS animations
AOS.init({
duration: 800,
easing: 'ease-in-out',
once: true
});
// Initialize feather icons
feather.replace();
// Sample data for demonstration
const sampleData = [
{
"Date": "Wednesday, September 24, 2025 at 3:13:40 AM UTC",
"Name": "BGC",
"Email": "2640770072937555@facebook.com",
"Email verified": "✓",
"Country": "Philippines",
"Birth date": "25-Sep-03"
},
{
"Date": "Wednesday, September 24, 2025 at 3:13:37 AM UTC",
"Name": "Khairul12",
"Email": "shsnjzhzhshdb@gmail.com",
"Email verified": "✓",
"Country": "Andorra",
"Birth date": "12-Jun-08"
},
{
"Date": "Wednesday, September 24, 2025 at 2:35:53 AM UTC",
"Name": "yo_farah",
"Email": "arzadonjaiii09@gmail.com",
"Email verified": "X",
"Country": "Philippines",
"Birth date": "24-Sep-07"
},
{
"Date": "Wednesday, September 24, 2025 at 2:24:28 AM UTC",
"Name": "Trespuertes",
"Email": "1330019208758711@facebook.com",
"Email verified": "✓",
"Country": "Andorra",
"Birth date": "24-Sep-25"
},
{
"Date": "Wednesday, September 24, 2025 at 2:18:26 AM UTC",
"Name": "M4nghsu",
"Email": "seanjohndelacruz211@gmail.com",
"Email verified": "✓",
"Country": "Andorra",
"Birth date": "22-Jan-02"
},
{
"Date": "Wednesday, September 24, 2025 at 2:15:54 AM UTC",
"Name": "꧁☆☬ℜifaτ☬☆꧂",
"Email": "omiethasanripat@gmail.com",
"Email verified": "✓",
"Country": "Bahamas",
"Birth date": "24-Sep-25"
},
{
"Date": "Wednesday, September 24, 2025 at 1:46:38 AM UTC",
"Name": "go minsi",
"Email": "jamireponce15@gmail.com",
"Email verified": "✓",
"Country": "Philippines",
"Birth date": "18-Oct-09"
},
{
"Date": "Wednesday, September 24, 2025 at 1:29:18 AM UTC",
"Name": "kusakabe",
"Email": "puertollanocarl@gmail.com",
"Email verified": "X",
"Country": "Philippines",
"Birth date": "15-Jul-07"
},
{
"Date": "Wednesday, September 24, 2025 at 1:27:23 AM UTC",
"Name": "shoooo",
"Email": "kkazekazekaze@gmail.com",
"Email verified": "✓",
"Country": "Philippines",
"Birth date": "15-Jul-07"
},
{
"Date": "Wednesday, September 24, 2025 at 1:22:57 AM UTC",
"Name": "EveresT",
"Email": "linfordlora11@gmail.com",
"Email verified": "✓",
"Country": "Andorra",
"Birth date": "11-Aug-07"
}
];
// Global variables
let userData = [];
let currentPage = 1;
const itemsPerPage = 10;
// DOM elements
const fileUploadInput = document.getElementById('file-upload');
const uploadPreview = document.getElementById('uploadPreview');
const previewTableBody = document.getElementById('previewTableBody');
const confirmUploadBtn = document.getElementById('confirmUpload');
const sampleDataBtn = document.getElementById('sampleDataBtn');
const exportBtn = document.getElementById('exportBtn');
const totalUsersEl = document.getElementById('totalUsers');
const newUsers7dEl = document.getElementById('newUsers7d');
const growth7dEl = document.getElementById('growth7d');
const topCountryEl = document.getElementById('topCountry');
const verifiedUsersEl = document.getElementById('verifiedUsers');
const startDateInput = document.getElementById('startDate');
const endDateInput = document.getElementById('endDate');
const applyDateFilterBtn = document.getElementById('applyDateFilter');
const growthPeriodSelect = document.getElementById('growthPeriod');
const searchUsersInput = document.getElementById('searchUsers');
const filterCountrySelect = document.getElementById('filterCountry');
const userTableBody = document.getElementById('userTableBody');
const countryTableBody = document.getElementById('countryTableBody');
const prevPageBtn = document.getElementById('prevPage');
const nextPageBtn = document.getElementById('nextPage');
const startItemEl = document.getElementById('startItem');
const endItemEl = document.getElementById('endItem');
const totalItemsEl = document.getElementById('totalItems');
const growthChartCtx = document.getElementById('growthChart').getContext('2d');
const countryChartCtx = document.getElementById('countryChart').getContext('2d');
// Charts
let growthChart, countryChart;
// Event listeners
fileUploadInput.addEventListener('change', handleFileUpload);
confirmUploadBtn.addEventListener('click', confirmUpload);
sampleDataBtn.addEventListener('click', loadSampleData);
exportBtn.addEventListener('click', exportData);
applyDateFilterBtn.addEventListener('click', applyDateFilter);
growthPeriodSelect.addEventListener('change', updateGrowthChart);
searchUsersInput.addEventListener('input', filterUsers);
filterCountrySelect.addEventListener('change', filterUsers);
prevPageBtn.addEventListener('click', goToPrevPage);
nextPageBtn.addEventListener('click', goToNextPage);
// Initialize date inputs with default values
function initDateInputs() {
const today = new Date();
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(today.getDate() - 7);
startDateInput.valueAsDate = sevenDaysAgo;
endDateInput.valueAsDate = today;
}
// Handle file upload
function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
Papa.parse(file, {
header: true,
complete: function(results) {
if (results.data.length > 0) {
userData = results.data;
showUploadPreview(userData.slice(0, 5)); // Show first 5 rows as preview
}
},
error: function(error) {
console.error("Error parsing CSV:", error);
alert("Error parsing CSV file. Please check the file format.");
}
});
}
// Show upload preview
function showUploadPreview(data) {
previewTableBody.innerHTML = '';
data.forEach(row => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${row.Date || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${row.Name || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${row.Email || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${row['Email verified'] || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${row.Country || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${row['Birth date'] || '-'}</td>
`;
previewTableBody.appendChild(tr);
});
uploadPreview.classList.remove('hidden');
}
// Confirm upload and update dashboard
function confirmUpload() {
updateDashboard();
uploadPreview.classList.add('hidden');
fileUploadInput.value = '';
}
// Load sample data
function loadSampleData() {
userData = sampleData;
updateDashboard();
}
// Export data
function exportData() {
if (userData.length === 0) {
alert("No data to export");
return;
}
// In a real implementation, this would export the filtered data
alert("Export functionality would be implemented here");
}
// Apply date filter
function applyDateFilter() {
updateDashboard();
}
// Update growth chart
function updateGrowthChart() {
// In a real implementation, this would update the chart based on selected period
renderGrowthChart();
}
// Filter users
function filterUsers() {
currentPage = 1;
renderUserTable();
}
// Pagination functions
function goToPrevPage() {
if (currentPage > 1) {
currentPage--;
renderUserTable();
}
}
function goToNextPage() {
const filteredData = getFilteredData();
const totalPages = Math.ceil(filteredData.length / itemsPerPage);
if (currentPage < totalPages) {
currentPage++;
renderUserTable();
}
}
// Get filtered data based on search and country filter
function getFilteredData() {
const searchTerm = searchUsersInput.value.toLowerCase();
const countryFilter = filterCountrySelect.value;
return userData.filter(user => {
const matchesSearch =
(user.Name && user.Name.toLowerCase().includes(searchTerm)) ||
(user.Email && user.Email.toLowerCase().includes(searchTerm));
const matchesCountry = !countryFilter || user.Country === countryFilter;
return matchesSearch && matchesCountry;
});
}
// Update the entire dashboard
function updateDashboard() {
updateSummaryCards();
renderUserTable();
renderCountryTable();
renderGrowthChart();
renderCountryChart();
updateCountryFilterOptions();
}
// Update summary cards
function updateSummaryCards() {
// Total users
totalUsersEl.textContent = userData.length;
// New users in last 7 days
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const newUsersLast7d = userData.filter(user => {
const userDate = new Date(user.Date);
return userDate >= sevenDaysAgo;
}).length;
newUsers7dEl.textContent = newUsersLast7d;
// Growth percentage (mock data)
const growthPercentage = Math.floor(Math.random() * 30) - 5; // Random between -5% and 25%
growth7dEl.querySelector('span').textContent = `${growthPercentage}%`;
if (growthPercentage >= 0) {
growth7dEl.className = 'ml-2 text-sm font-medium text-green-600 flex items-center';
growth7dEl.innerHTML = '<i data-feather="trending-up" class="h-4 w-4 mr-1"></i>' + `<span>${growthPercentage}%</span>`;
} else {
growth7dEl.className = 'ml-2 text-sm font-medium text-red-600 flex items-center';
growth7dEl.innerHTML = '<i data-feather="trending-down" class="h-4 w-4 mr-1"></i>' + `<span>${Math.abs(growthPercentage)}%</span>`;
}
// Top country
const countryCounts = {};
userData.forEach(user => {
if (user.Country) {
countryCounts[user.Country] = (countryCounts[user.Country] || 0) + 1;
}
});
const topCountry = Object.entries(countryCounts).sort((a, b) => b[1] - a[1])[0];
topCountryEl.textContent = topCountry ? `${topCountry[0]} (${topCountry[1]})` : '-';
// Verified users percentage
const verifiedCount = userData.filter(user => user['Email verified'] === '✓').length;
const verifiedPercentage = userData.length > 0 ? Math.round((verifiedCount / userData.length) * 100) : 0;
verifiedUsersEl.textContent = `${verifiedPercentage}%`;
feather.replace();
}
// Render user table
function renderUserTable() {
const filteredData = getFilteredData();
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = Math.min(startIndex + itemsPerPage, filteredData.length);
const paginatedData = filteredData.slice(startIndex, endIndex);
userTableBody.innerHTML = '';
paginatedData.forEach(user => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10 rounded-full bg-gray-200 flex items-center justify-center">
<i data-feather="user" class="h-5 w-5 text-gray-500"></i>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">${user.Name || 'Unknown'}</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">${user.Email || '-'}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">
${user.Country || '-'}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
${user.Date ? new Date(user.Date).toLocaleDateString() : '-'}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${user['Email verified'] === '✓' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">
${user['Email verified'] === '✓' ? 'Verified' : 'Not Verified'}
</span>
</td>
`;
userTableBody.appendChild(tr);
});
// Update pagination info
startItemEl.textContent = startIndex + 1;
endItemEl.textContent = endIndex;
totalItemsEl.textContent = filteredData.length;
// Update pagination buttons
prevPageBtn.disabled = currentPage === 1;
nextPageBtn.disabled = endIndex >= filteredData.length;
feather.replace();
}
// Render country table
function renderCountryTable() {
const countryStats = {};
userData.forEach(user => {
if (!user.Country) return;
if (!countryStats[user.Country]) {
countryStats[user.Country] = {
total: 0,
verified: 0,
last7d: 0
};
}
countryStats[user.Country].total++;
if (user['Email verified'] === '✓') {
countryStats[user.Country].verified++;
}
// Check if user was created in last 7 days
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const userDate = new Date(user.Date);
if (userDate >= sevenDaysAgo) {
countryStats[user.Country].last7d++;
}
});
countryTableBody.innerHTML = '';
Object.entries(countryStats).forEach(([country, stats]) => {
const growthPercentage = Math.floor(Math.random() * 30) - 5; // Random between -5% and 25%
const verifiedPercentage = Math.round((stats.verified / stats.total) * 100);
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10 rounded-full bg-gray-200 flex items-center justify-center">
<i data-feather="flag" class="h-5 w-5 text-gray-500"></i>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">${country}</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
${stats.total}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
${stats.last7d}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${growthPercentage >= 0 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">
${growthPercentage >= 0 ? '+' : ''}${growthPercentage}%
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div class="bg-blue-600 h-2.5 rounded-full" style="width: ${verifiedPercentage}%"></div>
</div>
<span class="text-xs text-gray-500">${verifiedPercentage}%</span>
</td>
`;
countryTableBody.appendChild(tr);
});
feather.replace();
}
// Render growth chart
function renderGrowthChart() {
if (growthChart) {
growthChart.destroy();
}
// Mock data for demonstration
const period = parseInt(growthPeriodSelect.value);
const labels = [];
const data = [];
for (let i = period - 1; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
labels.push(date.toLocaleDateString());
data.push(Math.floor(Math.random() * 20) + 5); // Random between 5 and 25
}
growthChart = new Chart(growthChartCtx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'New Users',
data: data,
backgroundColor: 'rgba(79, 70, 229, 0.1)',
borderColor: 'rgba(79, 70, 229, 1)',
borderWidth: 2,
tension: 0.3,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
}
// Render country chart
function renderCountryChart() {
if (countryChart) {
countryChart.destroy();
}
// Get country counts
const countryCounts = {};
userData.forEach(user => {
if (user.Country) {
countryCounts[user.Country] = (countryCounts[user.Country] || 0) + 1;
}
});
// Sort countries by count and take top 5
const sortedCountries = Object.entries(countryCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 5);
const labels = sortedCountries.map(item => item[0]);
const data = sortedCountries.map(item => item[1]);
countryChart = new Chart(countryChartCtx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Users',
data: data,
backgroundColor: [
'rgba(79, 70, 229, 0.7)',
'rgba(99, 102, 241, 0.7)',
'rgba(129, 140, 248, 0.7)',
'rgba(165, 180, 252, 0.7)',
'rgba(199, 210, 254, 0.7)'
],
borderColor: [
'rgba(79, 70, 229, 1)',
'rgba(99, 102, 241, 1)',
'rgba(129, 140, 248, 1)',
'rgba(165, 180, 252, 1)',
'rgba(199, 210, 254, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
}
// Update country filter options
function updateCountryFilterOptions() {
const countries = new Set();
userData.forEach(user => {
if (user.Country) {
countries.add(user.Country);
}
});
filterCountrySelect.innerHTML = `
<option value="">All Countries</option>
${Array.from(countries).map(country => `<option value="${country}">${country}</option>`).join('')}
`;
feather.replace();
}
// Initialize the dashboard
initDateInputs();
updateDashboard();
</script>
</body>
</html>