|
<!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> |
|
|
|
* { |
|
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 { |
|
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 { |
|
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-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; |
|
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; |
|
} |
|
|
|
|
|
.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; |
|
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 { |
|
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; |
|
} |
|
|
|
|
|
.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 { |
|
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; |
|
min-height: 400px; |
|
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; |
|
padding-left: 0; |
|
} |
|
|
|
.insight-content li { |
|
margin-bottom: 10px; |
|
position: relative; |
|
padding-left: 20px; |
|
} |
|
|
|
.insight-content li::before { |
|
content: '\2022'; |
|
color: #4f46e5; |
|
font-weight: bold; |
|
position: absolute; |
|
left: 0; |
|
font-size: 1.2em; |
|
top: 0; |
|
} |
|
|
|
|
|
.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 { |
|
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; |
|
} |
|
|
|
|
|
@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-div { |
|
border-radius: 10px; |
|
background-color: white; |
|
} |
|
|
|
|
|
.graph-container { |
|
animation: fadeInUp 0.6s ease-out; |
|
} |
|
|
|
@keyframes fadeInUp { |
|
from { |
|
opacity: 0; |
|
transform: translateY(30px); |
|
} |
|
to { |
|
opacity: 1; |
|
transform: translateY(0); |
|
} |
|
} |
|
|
|
|
|
.progress-bar { |
|
width: 100%; |
|
height: 6px; |
|
background: #e2e8f0; |
|
border-radius: 3px; |
|
margin-top: 20px; |
|
overflow: hidden; |
|
display: none; |
|
} |
|
|
|
.progress-fill { |
|
height: 100%; |
|
background: linear-gradient(90deg, #4f46e5, #7c3aed); |
|
width: 0%; |
|
transition: width 0.3s ease; |
|
border-radius: 3px; |
|
} |
|
|
|
|
|
.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; |
|
} |
|
|
|
.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"> |
|
|
|
{% 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> |
|
|
|
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'); |
|
|
|
|
|
fileInput.addEventListener('change', function(e) { |
|
const file = e.target.files[0]; |
|
if (file) { |
|
handleFileSelection(file); |
|
} |
|
}); |
|
|
|
|
|
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; |
|
handleFileSelection(file); |
|
} else { |
|
alert('Invalid file type. Please select a CSV file.'); |
|
fileInput.value = ''; |
|
fileNameDiv.innerHTML = ''; |
|
submitBtn.disabled = true; |
|
} |
|
} |
|
}); |
|
|
|
function handleFileSelection(file) { |
|
const maxSize = 16 * 1024 * 1024; |
|
|
|
if (file.size > maxSize) { |
|
alert('File size exceeds the 16MB limit. Please select a smaller file.'); |
|
fileInput.value = ''; |
|
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; |
|
|
|
|
|
progressBar.style.display = 'block'; |
|
let progress = 0; |
|
const interval = setInterval(() => { |
|
progress += 10; |
|
progressFill.style.width = progress + '%'; |
|
if (progress >= 100) { |
|
clearInterval(interval); |
|
|
|
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%'; |
|
} |
|
} |
|
|
|
|
|
uploadForm.addEventListener('submit', function(e) { |
|
console.log('Form submission started'); |
|
|
|
|
|
const file = fileInput.files[0]; |
|
if (!file) { |
|
e.preventDefault(); |
|
alert('No file has been selected for upload.'); |
|
return; |
|
} |
|
|
|
|
|
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); |
|
|
|
|
|
|
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing Data...'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
progressBar.style.display = 'none'; |
|
progressFill.style.width = '0%'; |
|
|
|
|
|
let loadingDiv = document.querySelector('.loading'); |
|
if (!loadingDiv) { |
|
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.'; |
|
|
|
const uploadSection = document.querySelector('.upload-section'); |
|
uploadSection.insertAdjacentElement('afterend', loadingDiv); |
|
} else { |
|
loadingDiv.style.display = 'block'; |
|
} |
|
|
|
console.log('Form submitted successfully, expecting redirect or response.'); |
|
}); |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
const graphs = document.querySelectorAll('.graph-container'); |
|
graphs.forEach((graph, index) => { |
|
graph.style.animationDelay = `${index * 0.2}s`; |
|
}); |
|
}); |
|
</script> |
|
</body> |
|
</html> |