Instructor home shows a setup banner while a course has no class schedule, since that is the one gap that degrades the student experience (free-date opt-outs, no withdrawal locking); it points at the prefilled schedule form and settings and disappears once any class day exists. Calls gain assessment_entered_at, stamped when an outcome is recorded or actually changed (bulk saves that change nothing don't restamp), null for pending calls and legacy imports, and included in the calls export — distinguishing assessed-in-class from assessed-later. The README setup section now gives both install routes: pure pip/venv, and Debian system packages with the apt line. Fixing this uncovered that numpy was missing from the project dependencies (the grading engine needs it; the system-site-packages venv had masked the omission). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
97 lines
3.6 KiB
HTML
97 lines
3.6 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}{{ course.title or "Cold Call" }}{% endblock %}
|
|
{% block body %}
|
|
<h1>{{ course.title or course.lti_context_id }}</h1>
|
|
|
|
{% if not has_schedule %}
|
|
<div class="banner">
|
|
<p><strong>No class schedule yet.</strong> Until you add your meeting
|
|
days, students can report an absence for <em>any</em> date, and
|
|
opt-out withdrawals can't be locked to class start times.
|
|
<a href="{{ url_for('instructor.schedule') }}">Set up the
|
|
schedule</a> — pick your weekdays and the range is prefilled from
|
|
the Canvas course dates when available.</p>
|
|
<p class="muted">Also worth one look before your first class:
|
|
<a href="{{ url_for('instructor.settings') }}">course settings</a>
|
|
(selection mode, weight, the assessment scale). Grading parameters
|
|
can wait until the end of the term.</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<p>
|
|
{{ roster_count }} active students on the roster;
|
|
{{ present_count }} available on {{ day.isoformat() }}
|
|
({{ optout_count }} opted out).
|
|
</p>
|
|
|
|
<form method="get" action="{{ url_for('instructor.home') }}">
|
|
<label>Working date:
|
|
<input type="date" name="date" value="{{ day.isoformat() }}"></label>
|
|
<button type="submit">Switch</button>
|
|
{% if day != today %}
|
|
<a href="{{ url_for('instructor.home') }}">back to today</a>
|
|
{% endif %}
|
|
{% if upcoming_class_days %}
|
|
<span class="muted">Class days:</span>
|
|
{% for d in upcoming_class_days %}
|
|
<a href="{{ url_for('instructor.home', date=d.isoformat()) }}"
|
|
{% if d == day %}style="font-weight: bold"{% endif %}>
|
|
{{ d.strftime("%a %b %-d") }}</a>
|
|
{% endfor %}
|
|
{% endif %}
|
|
</form>
|
|
|
|
<p>
|
|
<a href="{{ url_for('instructor.live', date=day.isoformat()) }}">Live cold call</a> ·
|
|
<a href="{{ url_for('instructor.day_edit', date_str=day.isoformat()) }}">Calls for {{ day.isoformat() }}</a> ·
|
|
<a href="{{ url_for('instructor.report') }}">Report</a> ·
|
|
<a href="{{ url_for('grades.view') }}">Grades</a> ·
|
|
<a href="{{ url_for('instructor.schedule') }}">Schedule</a> ·
|
|
<a href="{{ url_for('instructor.settings') }}">Settings</a>
|
|
</p>
|
|
|
|
<form method="post" action="{{ url_for('instructor.roster_sync') }}">
|
|
<button type="submit">Sync roster from Canvas now</button>
|
|
<span class="muted">The roster also refreshes automatically every
|
|
time you open this tool from Canvas.</span>
|
|
</form>
|
|
|
|
<h2>Printable list</h2>
|
|
{% if has_pending %}
|
|
<p>
|
|
<a href="{{ url_for('instructor.day_print', date_str=day.isoformat()) }}">
|
|
View the list for {{ day.isoformat() }}</a>
|
|
</p>
|
|
{% endif %}
|
|
<form method="post"
|
|
action="{{ url_for('instructor.generate', date_str=day.isoformat()) }}"
|
|
{% if resolved %}
|
|
onsubmit="return confirm('{{ resolved }} call(s) on {{ day.isoformat() }} already have recorded outcomes. Those are kept, but the unused list lines are replaced by a fresh draw. Continue?');"
|
|
{% endif %}>
|
|
<label>Number of calls (in cycle mode, the next batch of the
|
|
rotation): <input type="number" name="n" min="1" value="{{ present_count }}"></label>
|
|
<button type="submit">
|
|
{{ "Regenerate list (replaces the current one)" if has_pending
|
|
else "Generate list for " + day.isoformat() }}
|
|
</button>
|
|
</form>
|
|
|
|
{% if recent_days %}
|
|
<h2>Recent days</h2>
|
|
<table>
|
|
<tr><th>Date</th><th>Calls</th><th>Answered</th><th></th></tr>
|
|
{% for d, total, answered in recent_days %}
|
|
<tr>
|
|
<td>{{ d.isoformat() }}</td>
|
|
<td>{{ total }}</td>
|
|
<td>{{ answered or 0 }}</td>
|
|
<td>
|
|
<a href="{{ url_for('instructor.day_edit', date_str=d.isoformat()) }}">edit</a> ·
|
|
<a href="{{ url_for('instructor.day_print', date_str=d.isoformat()) }}">print</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
{% endif %}
|
|
{% endblock %}
|