05/24 Enhance functionalities

This commit is contained in:
2026-05-24 18:10:11 -04:00
parent 6605484927
commit fbf271ed71
15 changed files with 593 additions and 30 deletions
+22
View File
@@ -22,6 +22,28 @@
</div>
</div>
<!-- Missed shifts alert -->
{% if missed %}
<div class="card mt-3" style="border-color:var(--warning-border)">
<div class="card-header" style="background:var(--warning-bg)">
<span class="card-title" style="color:var(--warning)">⚠ Missed Shifts Today</span>
<span class="text-muted text-sm">Users with 0 sites checked so far</span>
</div>
<table class="table table-sm">
<thead><tr><th>Shift</th><th>User</th><th>Sites Checked</th></tr></thead>
<tbody>
{% for m in missed %}
<tr>
<td>{{ m.shift_name }}</td>
<td>{{ m.full_name }}</td>
<td class="text-muted">{{ m.checked_sites }} / {{ m.total_sites }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<!-- Per-user completion table -->
<div class="card mt-4">
<div class="card-header">
+52 -1
View File
@@ -3,9 +3,17 @@
{% block content %}
<div class="page-header">
<h1 class="page-title">Shift Management</h1>
<button class="btn btn-primary" onclick="openModal('modal-create-shift')"> New Shift</button>
<div style="display:flex;gap:.5rem">
<button class="btn btn-ghost btn-sm tab-btn active" data-tab="tab-list" data-group="shifts-tabs">☰ List</button>
<button class="btn btn-ghost btn-sm tab-btn" data-tab="tab-calendar" data-group="shifts-tabs">📅 Calendar</button>
<button class="btn btn-primary" onclick="openModal('modal-create-shift')"> New Shift</button>
</div>
</div>
<div id="shifts-tabs">
<!-- List tab -->
<div class="tab-panel active" id="tab-list">
<div class="card">
<table class="table table-hover">
<thead>
@@ -34,6 +42,49 @@
</tbody>
</table>
</div>
</div>
<!-- Calendar tab -->
<div class="tab-panel" id="tab-calendar">
<div class="card">
<div class="card-header"><span class="card-title">Weekly Schedule</span></div>
<div style="overflow-x:auto">
<table class="table">
<thead>
<tr>
<th style="min-width:160px">Shift</th>
{% for label, _ in day_map %}
<th style="text-align:center;min-width:64px">{{ label }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for s in shifts if s.is_active %}
<tr>
<td>
<strong>{{ s.name }}</strong>
<div class="text-muted text-sm">{{ s.start_time|hhmm }}{{ s.end_time|hhmm }}</div>
</td>
{% for _, day_val in day_map %}
<td style="text-align:center">
{% if day_val in s.days_of_week %}
<span class="badge badge-success" title="{{ s.name }} runs on this day"></span>
{% else %}
<span class="text-muted" style="font-size:.9rem"></span>
{% endif %}
</td>
{% endfor %}
</tr>
{% else %}
<tr><td colspan="8" class="text-center text-muted">No active shifts.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div><!-- /shifts-tabs -->
<!-- Create Shift Modal -->
<div class="modal-overlay" id="modal-create-shift">
+5
View File
@@ -22,6 +22,11 @@
</div>
<button class="btn btn-secondary btn-sm" onclick="loadBids()">↻ Refresh</button>
{% if session['user']['role'] == 'admin' %}
<form method="post" action="{{ url_for('bid_tracker.send_reminders') }}" style="display:inline">
<button class="btn btn-secondary btn-sm" type="submit" title="Email a digest of bids due within 7 days to admins">📧 Remind</button>
</form>
{% endif %}
</div>
<!-- ── Split pane ─────────────────────────────────────────── -->
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Forgot Password — Website Checker</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body class="login-wrap">
<div class="login-box">
<div class="login-logo">
<span class="brand-icon">🌐</span>
<h1>Website Checker</h1>
<p>Password Reset</p>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% for cat, msg in messages %}
<div class="alert alert-{{ cat }} mb-2">{{ msg }}</div>
{% endfor %}
{% endwith %}
<form method="post" action="{{ url_for('auth.forgot_password') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email"
autocomplete="email" autofocus required
placeholder="your@email.com">
</div>
<button class="btn btn-primary w-full" type="submit">Send Reset Link</button>
</form>
<p style="text-align:center;margin-top:1rem;font-size:.875rem">
<a href="{{ url_for('auth.login') }}" style="color:var(--accent)">Back to sign in</a>
</p>
</div>
</body>
</html>
+4
View File
@@ -34,6 +34,10 @@
<button class="btn btn-primary" type="submit">Sign In</button>
</form>
<p style="text-align:center;margin-top:1rem;font-size:.875rem">
<a href="{{ url_for('auth.forgot_password') }}" style="color:var(--accent)">Forgot password?</a>
</p>
</div>
</body>
</html>
+46
View File
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Set New Password — Website Checker</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body class="login-wrap">
<div class="login-box">
<div class="login-logo">
<span class="brand-icon">🌐</span>
<h1>Website Checker</h1>
<p>Set New Password</p>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% for cat, msg in messages %}
<div class="alert alert-{{ cat }} mb-2">{{ msg }}</div>
{% endfor %}
{% endwith %}
<form method="post" action="{{ url_for('auth.reset_password', token=token) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group">
<label for="password">New Password</label>
<input type="password" id="password" name="password"
autofocus required minlength="8"
placeholder="Minimum 8 characters">
</div>
<div class="form-group">
<label for="confirm_password">Confirm Password</label>
<input type="password" id="confirm_password" name="confirm_password"
required placeholder="Repeat new password">
</div>
<button class="btn btn-primary w-full" type="submit">Set New Password</button>
</form>
<p style="text-align:center;margin-top:1rem;font-size:.875rem">
<a href="{{ url_for('auth.login') }}" style="color:var(--accent)">Back to sign in</a>
</p>
</div>
</body>
</html>
+20 -16
View File
@@ -381,25 +381,29 @@ function esc(str) {
});
}
/* ── Health dots ───────────────────────────────────────────── */
/* ── Health dots (server-side probe) ───────────────────────── */
document.querySelectorAll('.site-card').forEach(function(card) {
var id = card.dataset.id;
var url = card.dataset.url;
var dot = document.getElementById('health-' + id);
if (!dot || !url) return;
var img = new Image();
var t0 = Date.now();
img.onload = function() {
var ms = Date.now() - t0;
dot.style.color = ms > 3000 ? '#d97706' : '#16a34a';
dot.title = ms > 3000 ? 'Slow (' + ms + 'ms)' : 'Reachable (' + ms + 'ms)';
};
img.onerror = function() {
var ms = Date.now() - t0;
dot.style.color = ms < 6000 ? '#d97706' : '#dc2626';
dot.title = ms < 6000 ? 'Reachable (restricted)' : 'Unreachable';
};
img.src = 'https://www.google.com/s2/favicons?domain=' + encodeURIComponent(url) + '&t=' + Date.now();
if (!dot) return;
fetch('/dashboard/health/' + id, {credentials: 'same-origin'})
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.status === 'ok') {
dot.style.color = d.ms > 3000 ? '#d97706' : '#16a34a';
dot.title = 'Reachable (' + d.ms + 'ms)';
} else if (d.status === 'timeout') {
dot.style.color = '#d97706';
dot.title = 'Timeout (>6s)';
} else {
dot.style.color = '#dc2626';
dot.title = 'Unreachable';
}
})
.catch(function() {
dot.style.color = '#9ca3af';
dot.title = 'Health check failed';
});
});
</script>