243 lines
11 KiB
HTML
243 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Import from Google Sheets - Edu-Space Admin{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid pt-5 mt-4">
|
|
<!-- Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h1 class="h3 mb-1">
|
|
<i class="bi bi-file-earmark-spreadsheet text-success me-2"></i>Google Sheets Academic Sync
|
|
</h1>
|
|
<p class="text-muted small mb-0">Extract and synchronize classrooms, careers, subjects, and weekly schedules directly from the official Google Sheet.</p>
|
|
</div>
|
|
<div>
|
|
<a href="/main/dashboard" class="btn btn-outline-secondary">
|
|
<i class="bi bi-arrow-left me-1"></i>Dashboard
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Metrics Cards -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-sm-6 col-xl-2">
|
|
<div class="card border-0 shadow-sm text-center p-3 h-100">
|
|
<div class="text-primary mb-1"><i class="bi bi-mortarboard fs-3"></i></div>
|
|
<div class="h4 mb-0 fw-bold">{{ metrics.total_careers }}</div>
|
|
<small class="text-muted">Careers</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-xl-2">
|
|
<div class="card border-0 shadow-sm text-center p-3 h-100">
|
|
<div class="text-info mb-1"><i class="bi bi-book fs-3"></i></div>
|
|
<div class="h4 mb-0 fw-bold">{{ metrics.total_subjects }}</div>
|
|
<small class="text-muted">Subjects</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-xl-2">
|
|
<div class="card border-0 shadow-sm text-center p-3 h-100">
|
|
<div class="text-success mb-1"><i class="bi bi-door-open fs-3"></i></div>
|
|
<div class="h4 mb-0 fw-bold">{{ metrics.total_classrooms }}</div>
|
|
<small class="text-muted">Classrooms</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-xl-3">
|
|
<div class="card border-0 shadow-sm text-center p-3 h-100">
|
|
<div class="text-warning mb-1"><i class="bi bi-people fs-3"></i></div>
|
|
<div class="h4 mb-0 fw-bold">{{ metrics.total_commissions }}</div>
|
|
<small class="text-muted">Commissions</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-xl-3">
|
|
<div class="card border-0 shadow-sm text-center p-3 h-100">
|
|
<div class="text-danger mb-1"><i class="bi bi-calendar-check fs-3"></i></div>
|
|
<div class="h4 mb-0 fw-bold">{{ metrics.total_reservations }}</div>
|
|
<small class="text-muted">Calendar Classes</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sync Action Banner -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-body p-4">
|
|
<div class="row align-items-center">
|
|
<div class="col-lg-8 mb-3 mb-lg-0">
|
|
<div class="d-flex align-items-center mb-2">
|
|
<span class="badge bg-success-subtle text-success border border-success-subtle px-3 py-1 me-2 fw-semibold">
|
|
<i class="bi bi-cloud-check me-1"></i>Google Sheets Live Integration
|
|
</span>
|
|
<span class="badge bg-light text-muted border">UniCABA 2026</span>
|
|
</div>
|
|
<h4 class="h5 mb-2 fw-bold">Official Schedule Board (Cartelera de Aulas)</h4>
|
|
<p class="text-muted small mb-2">
|
|
Reads published CSV sheets for all 6 days of the week, automatically creating or updating academic careers, classrooms, subject codes, commissions, and weekly calendar entries without duplicating records.
|
|
</p>
|
|
<div class="d-flex flex-wrap gap-1">
|
|
{% for s in metrics.sheets %}
|
|
<span class="badge bg-body-tertiary text-secondary border small">
|
|
<i class="bi bi-calendar-event me-1 text-primary"></i>{{ s.name }}
|
|
</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-4 text-lg-end">
|
|
<form method="POST" id="syncForm" onsubmit="startSync()">
|
|
<button type="submit" id="syncBtn" class="btn btn-success btn-lg px-4 py-2 shadow fw-semibold">
|
|
<i class="bi bi-arrow-repeat me-2"></i>Sync Data Now
|
|
</button>
|
|
</form>
|
|
<div id="syncSpinner" class="d-none mt-2 text-muted small">
|
|
<div class="spinner-border spinner-border-sm text-success me-1" role="status"></div>
|
|
Connecting to Google Sheets and processing records...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sync Results (if just completed) -->
|
|
{% if stats %}
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-success bg-opacity-10 text-success py-3">
|
|
<h5 class="card-title h6 mb-0 fw-bold">
|
|
<i class="bi bi-check-circle-fill me-2"></i>Synchronization Report
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3 text-center mb-3">
|
|
<div class="col-md-2">
|
|
<div class="p-2 border rounded bg-body-tertiary">
|
|
<div class="fw-bold fs-5">{{ stats.total_rows_processed }}</div>
|
|
<small class="text-muted">Rows Processed</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="p-2 border rounded bg-body-tertiary">
|
|
<div class="fw-bold fs-5 text-primary">+{{ stats.careers_created }}</div>
|
|
<small class="text-muted">Careers Created</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="p-2 border rounded bg-body-tertiary">
|
|
<div class="fw-bold fs-5 text-success">+{{ stats.classrooms_created }}</div>
|
|
<small class="text-muted">Classrooms Created</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="p-2 border rounded bg-body-tertiary">
|
|
<div class="fw-bold fs-5 text-info">+{{ stats.subjects_created }}</div>
|
|
<small class="text-muted">Subjects Created</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="p-2 border rounded bg-body-tertiary">
|
|
<div class="fw-bold fs-5 text-warning">+{{ stats.commissions_created }}</div>
|
|
<small class="text-muted">Commissions Created</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="p-2 border rounded bg-body-tertiary">
|
|
<div class="fw-bold fs-5 text-danger">+{{ stats.reservations_created }}</div>
|
|
<small class="text-muted">Reservations Created</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if stats.errors and (stats.errors|length > 0) %}
|
|
<div class="alert alert-warning mb-0 small">
|
|
<h6 class="alert-heading fw-bold mb-1">Warnings / Errors detected:</h6>
|
|
<ul class="mb-0 ps-3">
|
|
{% for err in stats.errors %}
|
|
<li>{{ err }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Database Preview Table -->
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-body-tertiary d-flex justify-content-between align-items-center py-3">
|
|
<h5 class="card-title h6 mb-0">
|
|
<i class="bi bi-table text-primary me-2"></i>Recently Synchronized Subjects & Comissions
|
|
</h5>
|
|
<a href="/schedule/calendar_view" class="btn btn-outline-primary btn-sm">
|
|
<i class="bi bi-calendar3 me-1"></i>View Calendar
|
|
</a>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="bg-body-tertiary">
|
|
<tr>
|
|
<th class="ps-4">Code</th>
|
|
<th>Subject Name</th>
|
|
<th>Career</th>
|
|
<th>Commissions</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for s in recent_subjects %}
|
|
<tr>
|
|
<td class="ps-4">
|
|
<span class="badge bg-secondary-subtle text-secondary font-monospace border">
|
|
{{ s.code }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div class="fw-semibold">{{ s.name }}</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge badge-brand small">
|
|
{{ s.career_obj.name if s.career_obj else (s.department or _('General')) }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="small text-muted">
|
|
{{ s.commissions|length }} commission(s)
|
|
</span>
|
|
</td>
|
|
<td>
|
|
{% if s.is_active %}
|
|
<span class="badge bg-success-subtle text-success border border-success-subtle small">
|
|
<i class="bi bi-check-circle me-1"></i>Active
|
|
</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary small">Inactive</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="5" class="text-center py-5 text-muted">
|
|
<i class="bi bi-cloud-arrow-down fs-1 d-block mb-2 text-secondary"></i>
|
|
No subjects in database yet. Click "Sync Data Now" to import.
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
function startSync() {
|
|
const btn = document.getElementById('syncBtn');
|
|
const spinner = document.getElementById('syncSpinner');
|
|
if (btn) {
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status"></span>Sincronizando...';
|
|
}
|
|
if (spinner) {
|
|
spinner.classList.remove('d-none');
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|