pranit144's picture
Update templates/index.html
bae0855 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Placement Data Analyzer</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
/* General Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
/* Header */
.header {
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
color: white;
padding: 40px 30px;
text-align: center;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>');
opacity: 0.3;
z-index: 0;
}
.header h1 {
font-size: 2.5em;
font-weight: 700;
margin-bottom: 10px;
position: relative;
z-index: 1;
}
.header p {
font-size: 1.1em;
opacity: 0.9;
position: relative;
z-index: 1;
}
/* Upload Section */
.upload-section {
padding: 40px 30px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
}
.upload-container {
max-width: 600px;
margin: 0 auto;
text-align: center;
}
.upload-title {
font-size: 1.8em;
color: #1e293b;
margin-bottom: 15px;
font-weight: 600;
}
.upload-subtitle {
color: #64748b;
margin-bottom: 30px;
font-size: 1.1em;
}
/* File Upload Styling */
.file-upload-wrapper {
position: relative;
display: inline-block;
margin-bottom: 20px;
}
.file-upload {
display: none;
}
.file-upload-btn {
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
color: white;
padding: 15px 30px;
border: none;
border-radius: 50px;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);
position: relative;
overflow: hidden;
display: inline-flex; /* Use flexbox for icon and text alignment */
align-items: center;
justify-content: center;
}
.file-upload-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4);
}
.file-upload-btn:active {
transform: translateY(0);
}
.file-upload-btn i {
margin-right: 10px;
}
.file-name {
margin-top: 15px;
color: #10b981;
font-weight: 600;
font-size: 1.1em;
display: flex;
align-items: center;
justify-content: center;
gap: 8px; /* Space between icon and text */
}
/* Submit Button */
.submit-btn {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
padding: 15px 40px;
border: none;
border-radius: 50px;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
margin-top: 20px;
display: inline-flex; /* Use flexbox for icon and text alignment */
align-items: center;
justify-content: center;
gap: 10px;
}
.submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
}
.submit-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
/* Requirements Section */
.requirements {
background: #fef3c7;
border: 1px solid #fbbf24;
border-radius: 10px;
padding: 20px;
margin-top: 30px;
text-align: left;
}
.requirements h3 {
color: #92400e;
margin-bottom: 15px;
font-size: 1.2em;
display: flex;
align-items: center;
gap: 10px;
}
.requirements ul {
color: #92400e;
padding-left: 20px;
}
.requirements li {
margin-bottom: 8px;
line-height: 1.5;
}
/* Flash Messages */
.alert {
padding: 15px;
margin-bottom: 20px;
border-radius: 10px;
display: flex;
align-items: center;
gap: 10px;
font-weight: 500;
}
.alert-success {
background: #f0fdf4;
border: 1px solid #86efac;
color: #166534;
}
.alert-error {
background: #fef2f2;
border: 1px solid #fca5a5;
color: #dc2626;
}
.alert-warning {
background: #fffbeb;
border: 1px solid #fcd34d;
color: #b45309;
}
/* Analysis Section */
.analysis-section {
padding: 40px 30px;
}
.analysis-title {
text-align: center;
font-size: 2.2em;
color: #1e293b;
margin-bottom: 40px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
}
.graph-container {
margin-bottom: 50px;
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.graph-container:hover {
transform: translateY(-5px);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
}
.graph-wrapper {
padding: 0; /* Plotly itself handles internal padding */
min-height: 400px; /* Ensure graphs have a minimum height */
display: flex;
align-items: center;
justify-content: center;
}
.insight-wrapper {
background: #f8fafc;
padding: 25px;
border-top: 1px solid #e2e8f0;
}
.insight-title {
color: #4f46e5;
font-size: 1.3em;
font-weight: 600;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.insight-content {
color: #475569;
line-height: 1.7;
font-size: 1.05em;
}
.insight-content ul {
list-style: none; /* Remove default bullet */
padding-left: 0;
}
.insight-content li {
margin-bottom: 10px;
position: relative;
padding-left: 20px; /* Space for custom bullet */
}
.insight-content li::before {
content: '\2022'; /* Unicode bullet character */
color: #4f46e5;
font-weight: bold;
position: absolute;
left: 0;
font-size: 1.2em;
top: 0; /* Align bullet with text top */
}
/* Loading State */
.loading {
text-align: center;
padding: 50px;
color: #64748b;
font-size: 1.2em;
}
.loading i {
font-size: 3em;
animation: spin 1s linear infinite;
color: #4f46e5;
margin-bottom: 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Footer */
.footer {
background: #1e293b;
color: white;
text-align: center;
padding: 30px;
margin-top: 50px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
}
.footer p {
opacity: 0.8;
font-size: 0.9em;
}
/* Responsive Design */
@media (max-width: 768px) {
body {
padding: 10px;
}
.header {
padding: 30px 20px;
}
.header h1 {
font-size: 2em;
}
.upload-section,
.analysis-section {
padding: 30px 20px;
}
.upload-title {
font-size: 1.5em;
}
.analysis-title {
font-size: 1.8em;
}
.file-upload-btn,
.submit-btn {
padding: 12px 25px;
font-size: 1em;
}
.graph-container {
margin-bottom: 30px;
}
.insight-wrapper {
padding: 20px;
}
}
/* Plotly graph styling */
.plotly-graph-div {
border-radius: 10px;
background-color: white; /* Ensure background is white */
}
/* Animation for graphs */
.graph-container {
animation: fadeInUp 0.6s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Progress bar for file upload */
.progress-bar {
width: 100%;
height: 6px;
background: #e2e8f0;
border-radius: 3px;
margin-top: 20px;
overflow: hidden;
display: none; /* Hidden by default */
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4f46e5, #7c3aed);
width: 0%;
transition: width 0.3s ease;
border-radius: 3px;
}
/* Drag and drop styling */
.drag-drop-area {
border: 2px dashed #cbd5e1;
border-radius: 15px;
padding: 40px;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
margin-bottom: 20px;
}
.drag-drop-area.drag-over {
border-color: #4f46e5;
background: #f0f4ff; /* Lighter blue hint on drag-over */
}
.drag-drop-area i {
font-size: 3em;
color: #64748b;
margin-bottom: 20px;
}
.drag-drop-text {
color: #64748b;
font-size: 1.1em;
margin-bottom: 15px;
}
.drag-drop-subtext {
color: #94a3b8;
font-size: 0.9em;
}
</style>
</head>
<body>
<div class="container">
<header class="header">
<h1> Placement Data Analyzer</h1>
</header>
<section class="upload-section">
<div class="upload-container">
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
<i class="fas fa-{{ 'check-circle' if category == 'success' else 'exclamation-triangle' if category == 'error' else 'info-circle' }}"></i>
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" enctype="multipart/form-data" id="uploadForm" action="/"><div class="drag-drop-area" id="dragDropArea"><i class="fas fa-cloud-upload-alt"></i><div class="drag-drop-text">Drag and drop the CSV file here</div><div class="drag-drop-subtext">Alternatively, click to browse for a file.</div></div><div class="file-upload-wrapper"><input type="file" id="file" name="file" class="file-upload" accept=".csv" required><label for="file" class="file-upload-btn"><i class="fas fa-file-csv"></i>Select CSV Data File</label></div><div class="file-name" id="fileName"></div><div class="progress-bar" id="progressBar"><div class="progress-fill" id="progressFill"></div></div><button type="submit" name="upload_csv" value="1" class="submit-btn" id="submitBtn" disabled><i class="fas fa-chart-bar"></i>Initiate Analysis</button></form>
</form>
</div>
</section>
{% if graphs_and_insights %}
<section class="analysis-section">
<h2 class="analysis-title">
<i class="fas fa-analytics"></i>
Placements
</h2>
{% for item in graphs_and_insights %}
<div class="graph-container">
<div class="graph-wrapper">
{{ item.graph|safe }}
</div>
<div class="insight-wrapper">
<h3 class="insight-title">
<i class="fas fa-lightbulb"></i>
Key Insights
</h3>
<div class="insight-content">
{{ item.insight|safe }}
</div>
</div>
</div>
{% endfor %}
</section>
{% endif %}
</div>
<script>
// File upload handling
const fileInput = document.getElementById('file');
const fileNameDiv = document.getElementById('fileName');
const submitBtn = document.getElementById('submitBtn');
const uploadForm = document.getElementById('uploadForm');
const progressBar = document.getElementById('progressBar');
const progressFill = document.getElementById('progressFill');
const dragDropArea = document.getElementById('dragDropArea');
// File input change handler
fileInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
handleFileSelection(file);
}
});
// Drag and drop handlers
dragDropArea.addEventListener('click', () => {
fileInput.click();
});
dragDropArea.addEventListener('dragover', (e) => {
e.preventDefault();
dragDropArea.classList.add('drag-over');
});
dragDropArea.addEventListener('dragleave', () => {
dragDropArea.classList.remove('drag-over');
});
dragDropArea.addEventListener('drop', (e) => {
e.preventDefault();
dragDropArea.classList.remove('drag-over');
const files = e.dataTransfer.files;
if (files.length > 0) {
const file = files[0];
if (file.name.toLowerCase().endsWith('.csv')) {
fileInput.files = files; // Assign files to input element
handleFileSelection(file);
} else {
alert('Invalid file type. Please select a CSV file.');
fileInput.value = ''; // Clear selected file if invalid
fileNameDiv.innerHTML = '';
submitBtn.disabled = true;
}
}
});
function handleFileSelection(file) {
const maxSize = 16 * 1024 * 1024; // 16MB in bytes
if (file.size > maxSize) {
alert('File size exceeds the 16MB limit. Please select a smaller file.');
fileInput.value = ''; // Clear selected file
fileNameDiv.innerHTML = '<i class="fas fa-exclamation-triangle"></i> File size exceeds limit.';
submitBtn.disabled = true;
progressBar.style.display = 'none';
progressFill.style.width = '0%';
return;
}
if (file.name.toLowerCase().endsWith('.csv')) {
fileNameDiv.innerHTML = `<i class="fas fa-check-circle"></i> Selected: ${file.name}`;
submitBtn.disabled = false;
// Simulate progress
progressBar.style.display = 'block';
let progress = 0;
const interval = setInterval(() => {
progress += 10;
progressFill.style.width = progress + '%';
if (progress >= 100) {
clearInterval(interval);
// Hide progress bar shortly after completion
setTimeout(() => {
progressBar.style.display = 'none';
progressFill.style.width = '0%';
}, 500);
}
}, 50);
} else {
fileNameDiv.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Please select a CSV file.';
submitBtn.disabled = true;
progressBar.style.display = 'none';
progressFill.style.width = '0%';
}
}
// Form submission handler
uploadForm.addEventListener('submit', function(e) {
console.log('Form submission started');
// Check if file is selected
const file = fileInput.files[0];
if (!file) {
e.preventDefault();
alert('No file has been selected for upload.');
return;
}
// Check file type (redundant with handleFileSelection, but good as a final check)
if (!file.name.toLowerCase().endsWith('.csv')) {
e.preventDefault();
alert('Only CSV files are permitted for upload.');
return;
}
console.log('File selected:', file.name);
console.log('File size:', file.size);
// Update button text and disable, but crucial: AFTER the form data has been collected
// (Browser collects form data before this JS execution finishes on submit)
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing Data...';
// !!! IMPORTANT FIX (from previous explanation): The line below was removed/commented out previously.
// Do NOT re-add `submitBtn.disabled = true;` here if you want `name="upload_csv"` to be sent.
// The browser sends the form data *before* this script finishes,
// so disabling it here would prevent the button's name/value from being sent.
// It's better to just show the spinner and rely on server-side redirect/flash
// to re-enable/reset the form state.
// Hide the progress bar immediately before submission to prevent weird states
progressBar.style.display = 'none';
progressFill.style.width = '0%';
// Show loading message dynamically
let loadingDiv = document.querySelector('.loading');
if (!loadingDiv) { // Create if it doesn't exist
loadingDiv = document.createElement('div');
loadingDiv.className = 'loading';
loadingDiv.innerHTML = '<i class="fas fa-spinner fa-spin"></i><br>Processing the dataset and generating analytical insights. This operation may require a brief moment.';
// Insert loading message after upload section
const uploadSection = document.querySelector('.upload-section');
uploadSection.insertAdjacentElement('afterend', loadingDiv);
} else {
loadingDiv.style.display = 'block'; // Show if it already exists
}
console.log('Form submitted successfully, expecting redirect or response.');
});
// Add animation delay to graphs on DOMContentLoaded
document.addEventListener('DOMContentLoaded', function() {
const graphs = document.querySelectorAll('.graph-container');
graphs.forEach((graph, index) => {
graph.style.animationDelay = `${index * 0.2}s`;
});
});
</script>
</body>
</html>