- Attendance form: date range (start+end), Sat/Sun checkboxes, conflict detection with Skip/Overwrite, supervisor auto-set, estimated cost card - Work history: filter by worker/project/payment status, CSV export, payment status badges (Paid/Unpaid) - Supervisor dashboard: stat cards for projects, teams, workers count - Forms: supervisor filtering (non-admins only see their projects/workers) - Navbar: History link now works, cleaned up inline styles in base.html - Management command: setup_groups creates Admin + Work Logger groups - No model/migration changes — database is untouched Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
379 lines
18 KiB
HTML
379 lines
18 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}Dashboard | FoxFitt{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Gradient Header -->
|
|
<div class="dashboard-header mb-5 rounded shadow-sm p-4 d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h1 class="h3 mb-0 text-white" style="font-family: 'Poppins', sans-serif;">Dashboard</h1>
|
|
<p class="text-white-50 mb-0">Welcome back, {{ user.first_name|default:user.username }}!</p>
|
|
</div>
|
|
<a href="{% url 'attendance_log' %}" class="btn btn-accent shadow-sm">
|
|
<i class="fas fa-plus fa-sm me-1"></i> Log Daily Work
|
|
</a>
|
|
</div>
|
|
|
|
<div class="container py-2" style="margin-top: -3rem;">
|
|
{% if is_admin %}
|
|
<!-- Admin View -->
|
|
<div class="row g-4 mb-4 position-relative">
|
|
<!-- Outstanding Payments Card -->
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card stat-card h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #ef4444;">
|
|
Outstanding Payments</div>
|
|
<div class="h5 mb-0 font-weight-bold text-gray-800">R {{ outstanding_payments|floatformat:2 }}</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-exclamation-circle fa-2x text-danger opacity-50"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Paid This Month Card -->
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card stat-card h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #10b981;">
|
|
Paid This Month</div>
|
|
<div class="h5 mb-0 font-weight-bold text-gray-800">R {{ paid_this_month|floatformat:2 }}</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-check-circle fa-2x text-success opacity-50"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Active Loans Card -->
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card stat-card h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #f59e0b;">
|
|
Active Loans ({{ active_loans_count }})</div>
|
|
<div class="h5 mb-0 font-weight-bold text-gray-800">R {{ active_loans_balance|floatformat:2 }}</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-hand-holding-usd fa-2x text-warning opacity-50"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Outstanding by Project -->
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card stat-card h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #3b82f6;">
|
|
Outstanding by Project</div>
|
|
<div class="mb-0 text-gray-800" style="font-size: 0.85rem; max-height: 60px; overflow-y: auto;">
|
|
{% if outstanding_by_project %}
|
|
<ul class="list-unstyled mb-0">
|
|
{% for proj, amount in outstanding_by_project.items %}
|
|
<li><strong>{{ proj }}:</strong> R {{ amount|floatformat:2 }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% else %}
|
|
<span class="text-muted">None</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-chart-pie fa-2x text-primary opacity-50"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions and This Week -->
|
|
<div class="row mb-4">
|
|
<!-- This Week -->
|
|
<div class="col-lg-4 mb-4 mb-lg-0">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-header py-3 bg-white">
|
|
<h6 class="m-0 font-weight-bold" style="color: #0f172a;">This Week Summary</h6>
|
|
</div>
|
|
<div class="card-body text-center d-flex flex-column justify-content-center">
|
|
<div class="h1 mb-0 font-weight-bold text-primary">{{ this_week_logs }}</div>
|
|
<div class="text-muted">Work Logs Created This Week</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="col-lg-8">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-header py-3 bg-white">
|
|
<h6 class="m-0 font-weight-bold" style="color: #0f172a;">Quick Actions</h6>
|
|
</div>
|
|
<div class="card-body d-flex align-items-center justify-content-around flex-wrap">
|
|
<a href="{% url 'attendance_log' %}" class="btn btn-lg btn-outline-primary mb-2">
|
|
<i class="fas fa-clipboard-list mb-2 d-block fa-2x"></i> Log Work
|
|
</a>
|
|
<a href="#" class="btn btn-lg btn-outline-success mb-2">
|
|
<i class="fas fa-money-check-alt mb-2 d-block fa-2x"></i> Run Payroll
|
|
</a>
|
|
<a href="{% url 'work_history' %}" class="btn btn-lg btn-outline-secondary mb-2">
|
|
<i class="fas fa-history mb-2 d-block fa-2x"></i> View History
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Recent Activity -->
|
|
<div class="col-lg-6 mb-4">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-header py-3 bg-white">
|
|
<h6 class="m-0 font-weight-bold" style="color: #0f172a;">Recent Activity</h6>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="list-group list-group-flush">
|
|
{% for log in recent_activity %}
|
|
<div class="list-group-item px-4 py-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="mb-1">{{ log.project.name }}</h6>
|
|
<small class="text-muted">{{ log.date }} · {{ log.workers.count }} workers</small>
|
|
</div>
|
|
<span class="badge bg-light text-dark border">{{ log.supervisor.username }}</span>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="p-4 text-center text-muted">
|
|
No recent activity.
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Manage Resources -->
|
|
<div class="col-lg-6 mb-4">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-header py-3 bg-white">
|
|
<h6 class="m-0 font-weight-bold" style="color: #0f172a;">Manage Resources</h6>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<ul class="nav nav-tabs px-3 pt-3" id="resourceTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="workers-tab" data-bs-toggle="tab" data-bs-target="#workers" type="button" role="tab" aria-selected="true">Workers</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="projects-tab" data-bs-toggle="tab" data-bs-target="#projects" type="button" role="tab" aria-selected="false">Projects</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="teams-tab" data-bs-toggle="tab" data-bs-target="#teams" type="button" role="tab" aria-selected="false">Teams</button>
|
|
</li>
|
|
</ul>
|
|
<div class="tab-content" id="resourceTabsContent">
|
|
<!-- Workers Tab -->
|
|
<div class="tab-pane fade show active" id="workers" role="tabpanel">
|
|
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
|
{% for item in workers %}
|
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
{{ item.name }}
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="worker" data-id="{{ item.id }}" {% if item.active %}checked{% endif %}>
|
|
</div>
|
|
</li>
|
|
{% empty %}
|
|
<li class="list-group-item text-muted">No workers found.</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
<!-- Projects Tab -->
|
|
<div class="tab-pane fade" id="projects" role="tabpanel">
|
|
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
|
{% for item in projects %}
|
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
{{ item.name }}
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="project" data-id="{{ item.id }}" {% if item.active %}checked{% endif %}>
|
|
</div>
|
|
</li>
|
|
{% empty %}
|
|
<li class="list-group-item text-muted">No projects found.</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
<!-- Teams Tab -->
|
|
<div class="tab-pane fade" id="teams" role="tabpanel">
|
|
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
|
{% for item in teams %}
|
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
{{ item.name }}
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="team" data-id="{{ item.id }}" {% if item.active %}checked{% endif %}>
|
|
</div>
|
|
</li>
|
|
{% empty %}
|
|
<li class="list-group-item text-muted">No teams found.</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<!-- Supervisor View -->
|
|
<!-- Stat Cards — how many projects, teams, and workers this supervisor manages -->
|
|
<div class="row g-4 mb-4 position-relative">
|
|
<div class="col-md-4">
|
|
<div class="card stat-card h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #8b5cf6;">
|
|
My Projects</div>
|
|
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ my_projects_count }}</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-project-diagram fa-2x opacity-50" style="color: #8b5cf6;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card stat-card h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #3b82f6;">
|
|
My Teams</div>
|
|
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ my_teams_count }}</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-users fa-2x opacity-50" style="color: #3b82f6;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card stat-card h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col me-2">
|
|
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #10b981;">
|
|
My Workers</div>
|
|
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ my_workers_count }}</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-hard-hat fa-2x opacity-50" style="color: #10b981;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- This Week + Recent Activity -->
|
|
<div class="row mb-4">
|
|
<div class="col-lg-4 mb-4 mb-lg-0">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-header py-3 bg-white">
|
|
<h6 class="m-0 fw-bold" style="color: #0f172a;">This Week Summary</h6>
|
|
</div>
|
|
<div class="card-body text-center d-flex flex-column justify-content-center">
|
|
<div class="h1 mb-0 fw-bold text-primary">{{ this_week_logs }}</div>
|
|
<div class="text-muted">Work Logs Created This Week</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-8">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-header py-3 bg-white">
|
|
<h6 class="m-0 fw-bold" style="color: #0f172a;">Recent Activity</h6>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="list-group list-group-flush">
|
|
{% for log in recent_activity %}
|
|
<div class="list-group-item px-4 py-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="mb-1">{{ log.project.name }}</h6>
|
|
<small class="text-muted">{{ log.date }} · {{ log.workers.count }} workers</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="p-4 text-center text-muted">
|
|
<i class="fas fa-inbox fa-2x mb-2 d-block opacity-50"></i>
|
|
No recent activity.
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const toggleSwitches = document.querySelectorAll('.toggle-active');
|
|
|
|
toggleSwitches.forEach(switchEl => {
|
|
switchEl.addEventListener('change', function() {
|
|
const type = this.getAttribute('data-type');
|
|
const id = this.getAttribute('data-id');
|
|
const isChecked = this.checked;
|
|
|
|
fetch(`/toggle/${type}/${id}/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': '{{ csrf_token }}',
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.status !== 'success') {
|
|
// Revert if failed
|
|
this.checked = !isChecked;
|
|
alert('Error updating status.');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
// Revert on error
|
|
this.checked = !isChecked;
|
|
alert('Error updating status.');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
|
|
{% endblock %}
|