Jun 29 - Admin view user's notes
This commit is contained in:
@@ -1647,6 +1647,42 @@ def purge_app_log(older_than_days: int = 30):
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Today's Check Notes ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def get_todays_check_notes() -> list:
|
||||||
|
"""Return all shift_checks from today that have a non-empty user_note."""
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor(dictionary=True)
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
sc.id AS check_id,
|
||||||
|
sc.checked_at,
|
||||||
|
sc.user_note,
|
||||||
|
w.id AS website_id,
|
||||||
|
w.name AS website_name,
|
||||||
|
w.url AS website_url,
|
||||||
|
COALESCE(u.full_name, u.username) AS full_name,
|
||||||
|
u.username
|
||||||
|
FROM shift_checks sc
|
||||||
|
JOIN websites w ON w.id = sc.website_id
|
||||||
|
JOIN users u ON u.id = sc.user_id
|
||||||
|
WHERE DATE(sc.checked_at) = CURDATE()
|
||||||
|
AND sc.user_note IS NOT NULL
|
||||||
|
AND TRIM(sc.user_note) <> ''
|
||||||
|
ORDER BY sc.checked_at DESC
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
cur.close()
|
||||||
|
return rows
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
# ─── Missed Shifts ────────────────────────────────────────────────────────────
|
# ─── Missed Shifts ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def get_missed_shifts_today() -> list:
|
def get_missed_shifts_today() -> list:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ routes/admin_dashboard.py — Admin dashboard: today's completion stats.
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from flask import Blueprint, render_template
|
from flask import Blueprint, render_template
|
||||||
from models import get_admin_dashboard_stats, get_missed_shifts_today
|
from models import get_admin_dashboard_stats, get_missed_shifts_today, get_todays_check_notes
|
||||||
from utils.decorators import admin_required
|
from utils.decorators import admin_required
|
||||||
|
|
||||||
logger = logging.getLogger("routes.admin_dashboard")
|
logger = logging.getLogger("routes.admin_dashboard")
|
||||||
@@ -25,4 +25,10 @@ def dashboard():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Missed shifts error: {e}")
|
logger.error(f"Missed shifts error: {e}")
|
||||||
missed = []
|
missed = []
|
||||||
return render_template("admin/dashboard.html", stats=stats, missed=missed)
|
try:
|
||||||
|
check_notes = get_todays_check_notes()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Check notes error: {e}")
|
||||||
|
check_notes = []
|
||||||
|
return render_template("admin/dashboard.html", stats=stats, missed=missed,
|
||||||
|
check_notes=check_notes)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<button class="btn btn-ghost btn-sm" onclick="location.reload()">↻ Refresh</button>
|
<button class="btn btn-ghost btn-sm" onclick="location.reload()">↻ Refresh</button>
|
||||||
<form method="post" action="{{ url_for('admin_shifts.send_incomplete_reminders') }}" style="display:inline"
|
<form method="post" action="{{ url_for('admin_shifts.send_incomplete_reminders') }}" style="display:inline"
|
||||||
onsubmit="return confirm('Send reminder emails to all staff with incomplete checks in shifts ending within 40 minutes?')">
|
onsubmit="return confirm('Send reminder emails to all staff with incomplete checks in shifts ending within 45 minutes?')">
|
||||||
<button class="btn btn-secondary btn-sm" type="submit">📧 Remind Incomplete</button>
|
<button class="btn btn-secondary btn-sm" type="submit">📧 Remind Incomplete</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,6 +99,40 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Today's Check Notes -->
|
||||||
|
{% if check_notes %}
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title">Today's Check Notes</h2>
|
||||||
|
<span class="text-muted text-sm">{{ check_notes|length }} note{{ 's' if check_notes|length != 1 }}</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>User</th>
|
||||||
|
<th>Website</th>
|
||||||
|
<th>Note</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for n in check_notes %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted text-sm" style="white-space:nowrap">
|
||||||
|
{{ n.checked_at.strftime('%H:%M') if n.checked_at else '' }}
|
||||||
|
</td>
|
||||||
|
<td style="white-space:nowrap">{{ n.full_name }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ n.website_url }}" target="_blank" rel="noopener" class="link">{{ n.website_name }}</a>
|
||||||
|
</td>
|
||||||
|
<td style="white-space:pre-wrap; word-break:break-word">{{ n.user_note }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('today-date').textContent =
|
document.getElementById('today-date').textContent =
|
||||||
new Date().toLocaleDateString('en-US', {weekday:'long', year:'numeric', month:'long', day:'numeric'});
|
new Date().toLocaleDateString('en-US', {weekday:'long', year:'numeric', month:'long', day:'numeric'});
|
||||||
|
|||||||
Reference in New Issue
Block a user