Skip to content

Instantly share code, notes, and snippets.

@paulwababu
Created July 9, 2024 12:53
Show Gist options
  • Save paulwababu/cce6d758934e02cdb844a9d3f0ed8793 to your computer and use it in GitHub Desktop.
Save paulwababu/cce6d758934e02cdb844a9d3f0ed8793 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FriendlyForce BotDash</title>
<link rel="icon" type="image/x-icon" href="https://friendlyforce.live/favicon.ico" />
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/1.11.5/css/jquery.dataTables.min.css">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.11.5/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
/* Modal styles */
.modal {
display: none;
position: fixed;
z-index: 1;
padding-top: 60px;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 5% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<header>
<img src="https://res.cloudinary.com/prometheusapi/image/upload/v1714431302/7682900d-2e64-408d-9d24-d01e50ee979b_yhnkuk-removebg-preview_sqhoyj.png" alt="Logo" class="logo">
<h1>FriendlyForce Monitor Administrators Only</h1>
<div class="user-info">
<i class="fas fa-user"></i>
<div class="user-menu">
<a href="/profile">Profile</a>
<a href="/logout">Logout</a>
</div>
</div>
</header>
<aside class="sidebar">
<nav>
<ul>
<li><a href="#overview" class="active"><i class="fas fa-tachometer-alt"></i> Overview</a></li>
<li><a href="#nasa-events"><i class="fas fa-globe"></i> NASA EONET Events</a></li>
<li><a href="#weather-alerts"><i class="fas fa-cloud"></i> Weather Gov Alerts</a></li>
<li><a href="#profile"><i class="fas fa-user"></i> User Profile</a></li>
<li><a href="#settings"><i class="fas fa-cog"></i> Settings</a></li>
<li><a href="/logout"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
</ul>
</nav>
</aside>
<main>
<section id="overview" class="content-section active">
<h2>Overview</h2>
<div class="summary-cards">
<div class="card">
<h3>Map Posts</h3>
<p id="total-events">1234</p>
</div>
<div class="card">
<h3>Feed Posts</h3>
<p id="processed-tweets">5678</p>
</div>
<div class="card">
<h3>Satellite Posts</h3>
<p id="scheduled-posts">256</p>
</div>
<div class="card">
<h3>Scheduled Posts</h3>
<p id="sites-down">18</p>
</div>
<div class="card">
<h3>Critical Users & Sites Down</h3>
<p id="new-users">345</p>
</div>
<div class="card">
<h3>Alerts <br>
<span class="small-text">Sent & Received</span>
</h3>
<style>
.small-text {
font-size: 0.8em; /* Adjust the size as needed */
}
</style>
<p id="alerts-sent">789</p>
</div>
<div class="card">
<h3>Updates <br>
<span class="small-text">Sent & Received</span>
</h3>
<style>
.small-text {
font-size: 0.8em; /* Adjust the size as needed */
}
</style>
<p id="alerts-sent">789</p>
</div>
<div class="card">
<h3>$$$ Transfers <br>
<span class="small-text">Sent & Received</span>
</h3>
<style>
.small-text {
font-size: 0.8em; /* Adjust the size as needed */
}
</style>
<p id="alerts-sent">$7,890.00</p>
</div>
</div>
<div class="charts">
<div class="chart">
<h3>Events Over Time</h3>
<canvas id="eventsChart"></canvas>
</div>
<div class="chart">
<h3>Total $ Transfers Over Time</h3>
<canvas id="transfersChart"></canvas>
</div>
<div class="chart">
<h3>Threat-related Posts by Keywords</h3>
<canvas id="threatsChart"></canvas>
</div>
<div class="chart">
<h3>Downtime Reports by Category</h3>
<canvas id="downtimeReportsChart"></canvas>
</div>
</div>
<div class="tables">
<div class="table">
<h3>Activity Feed Details</h3>
<table id="event-details-table" class="display" cellspacing="0" width="100%">
<thead>
<tr>
<th>Event ID</th>
<th>Time</th>
<th>Location</th>
<th>Category</th>
<th>Message</th>
<th>Media</th>
<th>Actions</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Event ID</th>
<th>Time</th>
<th>Location</th>
<th>Category</th>
<th>Message</th>
<th>Media</th>
<th>Actions</th>
</tr>
</tfoot>
<tbody id="event-details-body">
<!-- Placeholder for event details -->
</tbody>
</table>
</div>
</div>
</section>
<section id="nasa-events" class="content-section">
<h2>NASA EONET Events</h2>
<!-- NASA EONET events content -->
</section>
<section id="weather-alerts" class="content-section">
<h2>Weather Gov Alerts</h2>
<!-- Weather Gov alerts content -->
</section>
<section id="profile" class="content-section">
<h2>User Profile</h2>
<!-- User profile content -->
</section>
<section id="settings" class="content-section">
<h2>Settings</h2>
<!-- Settings content -->
</section>
<section id="logout" class="content-section">
<h2>Logout</h2>
<!-- Logout content -->
</section>
</main>
</div>
<div class="container">
<!-- Header, Sidebar, and Main content -->
<div id="myModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<h2>Event Details</h2>
<p id="modal-content"></p>
</div>
</div>
<div id="editModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<h2>Edit Event</h2>
<form id="editForm">
<input type="hidden" id="edit-event-time">
<input type="hidden" id="edit-event-time" readonly>
<label for="edit-event-location">Location:</label>
<input type="text" id="edit-event-location">
<label for="edit-event-category">Category:</label>
<input type="text" id="edit-event-category">
<label for="edit-event-message">Message:</label>
<textarea id="edit-event-message"></textarea>
<label for="edit-event-tags">Tags:</label>
<input type="text" id="edit-event-tags">
<label for="edit-event-media">Media URLs (comma-separated):</label>
<input type="text" id="edit-event-media">
<button type="submit">Save Changes</button>
</form>
</div>
</div>
</div>
<script>
// Example data for Events Over Time chart
const eventsLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
const eventsData = {
labels: eventsLabels,
datasets: [{
label: 'Total Analyzed & Processed',
data: [0, 10, 5, 20, 15, 30, 25],
fill: false,
borderColor: 'rgba(75, 192, 192, 1)',
tension: 0.1
}]
};
const eventsConfig = {
type: 'line',
data: eventsData,
options: {
scales: {
y: {
beginAtZero: true
}
}
}
};
// Example data for Total $ Transfers Over Time chart
const transfersLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
const transfersData = {
labels: transfersLabels,
datasets: [{
label: 'Total Analyzed & Processed',
data: [500, 800, 600, 1500, 2000, 2500, 3500],
fill: false,
borderColor: 'rgba(75, 192, 75, 1)',
tension: 0.1
}]
};
const transfersConfig = {
type: 'line',
data: transfersData,
options: {
scales: {
y: {
beginAtZero: true
}
}
}
};
// Example data for Threat-related Posts by Keywords chart
const threatsLabels = ['Weather', 'Air & Water', 'Wildfire', 'Food Supply', 'Biologicals', 'Infrastructure'];
const threatsData = {
labels: threatsLabels,
datasets: [{
label: 'Posts by Keyword',
data: [300, 50, 100, 75, 20, 150],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
};
const threatsConfig = {
type: 'doughnut',
data: threatsData,
options: {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: 'Threat-related Posts by Keywords'
}
}
}
};
// Example data for Downtime Reports by Category chart
const downtimeLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
const downtimeData = {
labels: downtimeLabels,
datasets: [{
label: 'Downtime Reports',
data: [1, 2, 1, 3, 2, 4, 1],
backgroundColor: 'rgba(255, 159, 64, 0.2)',
borderColor: 'rgba(255, 159, 64, 1)',
borderWidth: 1
}]
};
const downtimeConfig = {
type: 'bar',
data: downtimeData,
options: {
scales: {
y: {
beginAtZero: true
}
}
}
};
// Initialize all charts
window.onload = function() {
const eventsChart = new Chart(
document.getElementById('eventsChart'),
eventsConfig
);
const transfersChart = new Chart(
document.getElementById('transfersChart'),
transfersConfig
);
const threatsChart = new Chart(
document.getElementById('threatsChart'),
threatsConfig
);
const downtimeChart = new Chart(
document.getElementById('downtimeReportsChart'),
downtimeConfig
);
};
</script>
<script>
// Example function to handle the response and update the UI
function handlePostEventResponse(response) {
if (response.status === 'success') {
alert(response.message);
} else if (response.status === 'scheduled') {
alert(response.message);
} else {
alert('An error occurred. Please try again.');
}
}
// Example AJAX request to post an event
function postEvent(eventData) {
$.ajax({
url: '/post_event',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ event: eventData }),
success: handlePostEventResponse,
error: function(jqXHR, textStatus, errorThrown) {
alert('An error occurred. Please try again.');
}
});
}
</script>
<script src="scripts.js"></script>
</body>
</html>
document.addEventListener('DOMContentLoaded', () => {
const links = document.querySelectorAll('.sidebar nav ul li a');
const sections = document.querySelectorAll('.content-section');
const modal = document.getElementById('myModal');
const modalContent = document.getElementById('modal-content');
const editModal = document.getElementById('editModal');
const editForm = document.getElementById('editForm');
const span = document.getElementsByClassName('close');
links.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = e.target.getAttribute('href').substring(1);
sections.forEach(section => {
section.classList.remove('active');
});
links.forEach(link => {
link.classList.remove('active');
});
document.getElementById(targetId).classList.add('active');
e.target.classList.add('active');
});
});
// Activate the first section by default
document.getElementById('overview').classList.add('active');
document.querySelector('.sidebar nav ul li a[href="#overview"]').classList.add('active');
// Fetch and display analytics data
fetch('https://monitordb.friendlyforce.live/analytics')
.then(response => response.json())
.then(data => {
document.getElementById('total-events').innerText = data.total_events;
document.getElementById('processed-tweets').innerText = data.processed_tweets;
document.getElementById('scheduled-posts').innerText = data.scheduled_posts;
document.getElementById('sites-down').innerText = data.popular_sites_down;
document.getElementById('new-users').innerText = data.new_users;
document.getElementById('alerts-sent').innerText = data.alerts_sent;
document.getElementById('most-active-users').innerText = data.most_active_users.join(', ');
// Update events chart
const eventsCtx = document.getElementById('eventsChart').getContext('2d');
const eventsChart = new Chart(eventsCtx, {
type: 'line',
data: {
labels: data.events_over_time.labels,
datasets: [{
label: 'Total Events',
data: data.events_over_time.data,
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
x: {
beginAtZero: true
},
y: {
beginAtZero: true
}
}
}
});
// Update tweets processed chart
const tweetsCtx = document.getElementById('tweetsChart').getContext('2d');
const tweetsChart = new Chart(tweetsCtx, {
type: 'line',
data: {
labels: data.tweets_processed_over_time.labels,
datasets: [{
label: 'Processed Tweets',
data: data.tweets_processed_over_time.data,
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
x: {
beginAtZero: true
},
y: {
beginAtZero: true
}
}
}
});
// Update types of posts chart
const postTypesCtx = document.getElementById('postTypesChart').getContext('2d');
const postTypesChart = new Chart(postTypesCtx, {
type: 'doughnut',
data: {
labels: data.types_of_posts.labels,
datasets: [{
label: 'Types of Posts',
data: data.types_of_posts.data,
backgroundColor: [
'rgba(75, 192, 192, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(153, 102, 255, 0.2)'
],
borderColor: [
'rgba(75, 192, 192, 1)',
'rgba(255, 206, 86, 1)',
'rgba(54, 162, 235, 1)',
'rgba(153, 102, 255, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'top'
},
title: {
display: true,
text: 'Types of Posts'
}
}
}
});
// Update downtime reports by category chart
const downtimeReportsCtx = document.getElementById('downtimeReportsChart').getContext('2d');
const downtimeReportsChart = new Chart(downtimeReportsCtx, {
type: 'bar',
data: {
labels: data.downtime_reports_by_category.labels,
datasets: [{
label: 'Downtime Reports',
data: data.downtime_reports_by_category.data,
backgroundColor: [
'rgba(255, 159, 64, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(153, 102, 255, 0.2)'
],
borderColor: [
'rgba(255, 159, 64, 1)',
'rgba(75, 192, 192, 1)',
'rgba(255, 206, 86, 1)',
'rgba(153, 102, 255, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
x: {
beginAtZero: true
},
y: {
beginAtZero: true
}
}
}
});
})
.catch(error => console.error('Error fetching analytics data:', error));
// Function to fetch and display event details
const fetchAndDisplayEvents = () => {
Promise.all([
fetch('https://monitordb.friendlyforce.live/nasaFireAlerts?limit=1000000'),
fetch('https://monitordb.friendlyforce.live/weatherGovAlerts?limit=1000000')
])
.then(async ([fireResponse, weatherResponse]) => {
if (!fireResponse.ok || !weatherResponse.ok) {
throw new Error('Network response was not ok');
}
const fireEvents = await fireResponse.json();
const weatherEvents = await weatherResponse.json();
return { fireEvents, weatherEvents };
})
.then(({ fireEvents, weatherEvents }) => {
const eventTableBody = document.getElementById('event-details-body');
eventTableBody.innerHTML = ''; // Clear existing table rows
// Merge and sort events by time
const allEvents = [...fireEvents.data, ...weatherEvents.data];
allEvents.sort((a, b) => new Date(b.date ?? b.created_at) - new Date(a.date ?? a.created_at));
const createRow = (event, type, index) => {
const row = document.createElement('tr');
const eventIdCell = document.createElement('td');
eventIdCell.textContent = (index + 1).toString().padStart(3, '0');
row.appendChild(eventIdCell);
const timeCell = document.createElement('td');
timeCell.textContent = event.date ?? event.created_at ?? 'N/A';
row.appendChild(timeCell);
const locationCell = document.createElement('td');
locationCell.textContent = event.title ?? event.event_details ?? 'N/A';
row.appendChild(locationCell);
const categoryCell = document.createElement('td');
categoryCell.textContent = type;
row.appendChild(categoryCell);
const messageCell = document.createElement('td');
messageCell.textContent = event.conversational_message ?? event.tweet_text ?? 'N/A';
row.appendChild(messageCell);
const mediaCell = document.createElement('td');
mediaCell.innerHTML = event.media_urls ? event.media_urls.split(',').map(url => `<img src="${url}" alt="media" style="max-width: 100px;">`).join(' ') : 'N/A';
row.appendChild(mediaCell);
const actionsCell = document.createElement('td');
try {
const eventStr = JSON.stringify(event).replace(/'/g, "&apos;");
actionsCell.innerHTML = `
<button class="action-btn view-btn" data-event='${eventStr}' data-type='${type}'><i class="fas fa-eye"></i></button>
<button class="action-btn publish-btn" data-event='${eventStr}'><i class="fas fa-upload"></i></button>
<button class="action-btn edit-btn" data-event='${eventStr}' data-type='${type}' data-id='${event.id}'><i class="fas fa-edit"></i></button>
<button class="action-btn delete-btn" data-event-time='${event.date ?? event.created_at}' data-type='${type}'><i class="fas fa-trash"></i></button>
`;
} catch (error) {
console.error('Error stringifying event:', error, event);
}
row.appendChild(actionsCell);
return row;
};
allEvents.forEach((event, index) => {
const type = event.date ? 'Fire Alert' : 'Weather Alert';
eventTableBody.appendChild(createRow(event, type, index));
});
// Destroy existing DataTable instance if it exists
if ($.fn.DataTable.isDataTable('#event-details-table')) {
$('#event-details-table').DataTable().destroy();
}
// Initialize DataTable
const dataTable = $('#event-details-table').DataTable({
pageLength: 100000000000000
});
// Function to reattach event listeners
const reattachEventListeners = () => {
const eventDetailsBody = document.getElementById('event-details-body');
// Remove existing event listeners
eventDetailsBody.replaceWith(eventDetailsBody.cloneNode(true));
// Use event delegation to handle clicks on action buttons
document.getElementById('event-details-body').addEventListener('click', (e) => {
if (e.target.closest('.view-btn')) {
const button = e.target.closest('.view-btn');
const event = JSON.parse(button.getAttribute('data-event').replace(/&apos;/g, "'"));
const type = button.getAttribute('data-type');
const eventDetails = `
<p><strong>Event ID:</strong> ${(Array.from(eventTableBody.children).indexOf(button.closest('tr')) + 1).toString().padStart(3, '0')}</p>
<p><strong>Time:</strong> ${event.date ?? event.created_at ?? 'N/A'}</p>
<p><strong>Location:</strong> ${event.title ?? event.event_details ?? 'N/A'}</p>
<p><strong>Category:</strong> ${type}</p>
<p><strong>Message:</strong> ${event.conversational_message ?? event.tweet_text ?? 'N/A'}</p>
<p><strong>Media:</strong> ${event.media_urls ? event.media_urls.split(',').map(url => `<img src="${url}" alt="media" style="max-width: 100px;">`).join(' ') : 'N/A'}</p>
`;
modalContent.innerHTML = eventDetails;
modal.style.display = 'block';
} else if (e.target.closest('.publish-btn')) {
const button = e.target.closest('.publish-btn');
const event = JSON.parse(button.getAttribute('data-event').replace(/&apos;/g, "'"));
publishEvent(event);
} else if (e.target.closest('.delete-btn')) {
const button = e.target.closest('.delete-btn');
const eventTime = button.getAttribute('data-event-time');
const type = button.getAttribute('data-type');
const endpoint = type === 'Fire Alert' ? 'nasaFireAlerts' : 'weatherGovAlerts';
fetch(`https://monitordb.friendlyforce.live/${endpoint}?time=${eventTime}`, {
method: 'DELETE'
})
.then(response => {
if (response.ok) {
fetchAndDisplayEvents(); // Refresh the events
} else {
alert('Failed to delete the event.');
}
})
.catch(error => console.error('Error deleting event:', error));
} else if (e.target.closest('.edit-btn')) {
const button = e.target.closest('.edit-btn');
const event = JSON.parse(button.getAttribute('data-event').replace(/&apos;/g, "'"));
const type = button.getAttribute('data-type');
const id = button.getAttribute('data-id');
const propertyName = type === 'Fire Alert' ? 'date' : 'created_at';
if (!(propertyName in event)) {
alert('Invalid event time. Please ensure the event object contains the required time property.');
return;
}
const eventTime = event[propertyName];
document.getElementById('edit-event-time').value = eventTime;
document.getElementById('edit-event-location').value = event.title ?? event.event_details ?? '';
document.getElementById('edit-event-category').value = event.category ?? '';
document.getElementById('edit-event-message').value = event.conversational_message ?? event.tweet_text ?? '';
document.getElementById('edit-event-tags').value = (Array.isArray(event.tags) ? event.tags.join(', ') : event.tags);
editModal.style.display = 'block';
editForm.onsubmit = (formEvent) => {
formEvent.preventDefault();
const updatedEvent = {
[propertyName]: document.getElementById('edit-event-time').value,
title: document.getElementById('edit-event-location').value,
category: document.getElementById('edit-event-category').value,
conversational_message: document.getElementById('edit-event-message').value,
tweet_text: document.getElementById('edit-event-message').value,
tags: document.getElementById('edit-event-tags').value.split(',').map(tag => tag.trim())
};
const endpoint = type === 'Fire Alert' ? 'nasaFireAlerts' : 'weatherGovAlerts';
fetch(`https://monitordb.friendlyforce.live/${endpoint}/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updatedEvent)
})
.then(response => {
if (response.ok) {
editModal.style.display = 'none';
fetchAndDisplayEvents(); // Refresh the events
alert('Event updated successfully.');
} else {
alert('Failed to update the event. Please check the console for more details.');
console.error('Failed to update event:', response.statusText);
}
})
.catch(error => {
alert('An error occurred while updating the event. Please check the console for more details.');
console.error('Error updating event:', error);
});
};
}
});
};
// Attach event listeners initially
reattachEventListeners();
// Attach event listeners on each DataTable draw (including page changes)
dataTable.on('draw', () => {
reattachEventListeners();
});
})
.catch(error => console.error('Error fetching event details:', error));
};
fetchAndDisplayEvents();
// Close the modal when the user clicks on <span> (x)
span[0].onclick = () => {
modal.style.display = 'none';
}
span[1].onclick = () => {
editModal.style.display = 'none';
}
// Close the modal when the user clicks anywhere outside of the modal
window.onclick = (event) => {
if (event.target == modal) {
modal.style.display = 'none';
}
if (event.target == editModal) {
editModal.style.display = 'none';
}
}
});
// Function to publish an event
const publishEvent = (event) => {
fetch('/post_event', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ event })
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert('Event published successfully.');
} else if (data.status === 'scheduled') {
alert(data.message);
} else {
alert('Event published successfully.');
}
})
.catch(error => {
console.error('Error publishing event:', error);
alert('Event scheduled to be published successfully.');
});
};
:root {
--primary-color: #2c3e50; /* Lighter navy blue */
--secondary-color: #f26522; /* Orange from logo */
--background-color: #f4f4f4;
--text-color: #333;
--card-background: #fff;
--sidebar-width: 250px;
--header-height: 70px;
--transition-speed: 0.3s;
}
body, html {
margin: 0;
padding: 0;
font-family: 'Roboto', Arial, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
height: 100%;
line-height: 1.6;
font-size: 16px;
}
.container {
display: flex;
height: 100%;
}
header {
background-color: #ffffff;
color: var(--primary-color);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
position: fixed;
width: 100%;
height: var(--header-height);
top: 0;
z-index: 1000;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
header .logo {
height: 40px;
margin-right: 15px;
transition: transform var(--transition-speed);
}
header .logo:hover {
transform: scale(1.05);
}
header h1 {
margin: 0;
font-size: 1.5em;
font-weight: 500;
}
header .user-info {
display: flex;
align-items: center;
}
header .user-info i {
font-size: 24px;
margin-right: 10px;
color: var(--secondary-color);
}
.sidebar {
background-color: #ffffff;
color: var(--primary-color);
width: var(--sidebar-width);
padding-top: var(--header-height);
position: fixed;
top: 0;
bottom: 0;
overflow-y: auto;
transition: transform var(--transition-speed);
box-shadow: 2px 0 10px rgba(0,0,0,0.1);
}
.sidebar nav ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.sidebar nav ul li {
padding: 0;
margin-bottom: 5px;
}
.sidebar nav ul li a {
color: var(--primary-color);
text-decoration: none;
display: flex;
align-items: center;
padding: 15px 20px;
transition: all var(--transition-speed);
border-left: 4px solid transparent;
}
.sidebar nav ul li a i {
margin-right: 15px;
font-size: 18px;
width: 20px;
text-align: center;
}
.sidebar nav ul li a:hover,
.sidebar nav ul li a.active {
background-color: rgba(44, 62, 80, 0.1);
border-left: 4px solid var(--secondary-color);
}
main {
margin-left: var(--sidebar-width);
padding: calc(var(--header-height) + 20px) 30px 30px;
width: calc(100% - var(--sidebar-width));
transition: margin-left var(--transition-speed);
}
.content-section {
display: none;
animation: fadeIn 0.5s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.content-section.active {
display: block;
}
h2 {
color: var(--primary-color);
font-weight: 500;
margin-bottom: 25px;
}
.summary-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
background-color: var(--card-background);
padding: 25px;
text-align: center;
border-radius: 12px;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
transition: transform var(--transition-speed), box-shadow var(--transition-speed);
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}
.card h3 {
margin-top: 0;
font-weight: 500;
color: var(--primary-color);
}
.charts {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 30px;
margin-bottom: 30px;
}
.chart {
background-color: var(--card-background);
padding: 25px;
border-radius: 12px;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
}
.tables {
margin-bottom: 30px;
}
.table {
background-color: var(--card-background);
padding: 25px;
border-radius: 12px;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
overflow-x: auto;
}
.table h3 {
margin-bottom: 20px;
font-weight: 500;
color: var(--primary-color);
}
.table table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
}
.table table th,
.table table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
.table table th {
background-color: #f5f5f5;
font-weight: 500;
color: var(--primary-color);
}
.table table tr:last-child td {
border-bottom: none;
}
.table table tr:hover {
background-color: #f9f9f9;
}
/* Modal styles */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.5);
}
.modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 30px;
border: none;
border-radius: 8px;
width: 50%;
max-width: 500px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
/* Form styles */
form {
display: flex;
flex-direction: column;
}
form label {
margin-top: 10px;
margin-bottom: 5px;
font-weight: 500;
}
form input[type="text"],
form input[type="password"],
form textarea {
padding: 12px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
form button {
background-color: var(--secondary-color);
color: white;
border: none;
padding: 12px 20px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
font-size: 16px;
font-weight: 500;
}
form button:hover {
background-color: #d54d0d; /* Darker shade of secondary color */
}
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
}
main {
margin-left: 0;
width: 100%;
}
.sidebar-open .sidebar {
transform: translateX(0);
}
.sidebar-open main {
margin-left: var(--sidebar-width);
}
.modal-content {
width: 80%;
}
}
/* Add this to your existing CSS */
.user-info {
display: flex;
align-items: center;
margin-left: auto;
margin-right: 20px;
position: relative;
}
.user-icon {
background-color: var(--secondary-color);
color: white;
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.3s;
}
.user-icon:hover {
background-color: #d54d0d;
}
.user-menu {
position: absolute;
top: 100%;
right: 0;
background-color: white;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
display: none;
z-index: 1000;
}
.user-info:hover .user-menu {
display: block;
}
.user-menu a {
display: block;
padding: 10px 20px;
color: var(--primary-color);
text-decoration: none;
transition: background-color 0.3s;
}
.user-menu a:hover {
background-color: #f0f0f0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment