This commit is contained in:
Flatlogic Bot 2026-02-03 22:24:29 +00:00
parent 091645a299
commit fc8c69d0d5
4 changed files with 221 additions and 54 deletions

View File

@ -49,14 +49,22 @@
<tr>
<th>Name</th>
<th>ID Number</th>
<th>Teams</th>
<th class="text-end">Active Status</th>
</tr>
</thead>
<tbody>
{% for worker in workers %}
<tr>
<td>{{ worker.name }}</td>
<td><strong>{{ worker.name }}</strong></td>
<td>{{ worker.id_no }}</td>
<td>
{% for team in worker.teams.all %}
<span class="badge bg-light text-dark border">{{ team.name }}</span>
{% empty %}
<span class="text-muted small">No Team</span>
{% endfor %}
</td>
<td class="text-end">
<div class="form-check form-switch d-flex justify-content-end">
<input class="form-check-input resource-toggle" type="checkbox" role="switch"
@ -68,7 +76,7 @@
</td>
</tr>
{% empty %}
<tr><td colspan="3" class="text-center text-muted py-4">No workers found.</td></tr>
<tr><td colspan="4" class="text-center text-muted py-4">No workers found.</td></tr>
{% endfor %}
</tbody>
</table>
@ -93,7 +101,7 @@
<tbody>
{% for project in projects %}
<tr>
<td>{{ project.name }}</td>
<td><strong>{{ project.name }}</strong></td>
<td>{{ project.description|truncatechars:50 }}</td>
<td class="text-end">
<div class="form-check form-switch d-flex justify-content-end">
@ -132,7 +140,7 @@
<tbody>
{% for team in teams %}
<tr>
<td>{{ team.name }}</td>
<td><strong>{{ team.name }}</strong></td>
<td>{{ team.supervisor.username|default:"-" }}</td>
<td>{{ team.workers.count }}</td>
<td class="text-end">
@ -210,4 +218,4 @@
});
});
</script>
{% endblock %}
{% endblock %}

View File

@ -4,61 +4,176 @@
{% block title %}Work Log History | LabourFlow{% endblock %}
{% block content %}
<div class="dashboard-header">
<div class="dashboard-header pb-5">
<div class="container">
<div class="d-flex justify-content-between align-items-center">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="display-5 mb-2">Work Log History</h1>
<p class="lead opacity-75">View and filter historical daily work logs.</p>
<p class="lead opacity-75">Filter and review historical daily work logs.</p>
</div>
<a href="{% url 'log_attendance' %}" class="btn btn-accent shadow-sm">
+ New Entry
</a>
</div>
<!-- Filter Card -->
<div class="card border-0 shadow-sm">
<div class="card-body p-4">
<form method="get" class="row g-3">
<div class="col-md-3">
<label class="form-label small text-muted text-uppercase fw-bold">Worker</label>
<select name="worker" class="form-select">
<option value="">All Workers</option>
{% for w in workers %}
<option value="{{ w.id }}" {% if selected_worker == w.id %}selected{% endif %}>{{ w.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label class="form-label small text-muted text-uppercase fw-bold">Team</label>
<select name="team" class="form-select">
<option value="">All Teams</option>
{% for t in teams %}
<option value="{{ t.id }}" {% if selected_team == t.id %}selected{% endif %}>{{ t.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label class="form-label small text-muted text-uppercase fw-bold">Project</label>
<select name="project" class="form-select">
<option value="">All Projects</option>
{% for p in projects %}
<option value="{{ p.id }}" {% if selected_project == p.id %}selected{% endif %}>{{ p.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label class="form-label small text-muted text-uppercase fw-bold">Payment</label>
<select name="payment_status" class="form-select">
<option value="all" {% if selected_payment_status == 'all' %}selected{% endif %}>All Status</option>
<option value="paid" {% if selected_payment_status == 'paid' %}selected{% endif %}>Paid</option>
<option value="unpaid" {% if selected_payment_status == 'unpaid' %}selected{% endif %}>Unpaid</option>
</select>
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100">Filter</button>
</div>
</form>
{% if selected_worker or selected_team or selected_project or selected_payment_status and selected_payment_status != 'all' %}
<div class="mt-3">
<a href="{% url 'work_log_list' %}" class="btn btn-sm btn-link text-decoration-none text-muted">
<i class="bi bi-x-circle"></i> Clear all filters
</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<div class="container mb-5 mt-n4">
<div class="card p-4 shadow-sm">
{% if logs %}
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>Date</th>
<th>Project</th>
<th>Supervisor</th>
<th>Labourers</th>
<th>Notes</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td>{{ log.date }}</td>
<td><strong>{{ log.project.name }}</strong></td>
<td>{{ log.supervisor.username|default:"System" }}</td>
<td>
<span class="badge bg-primary bg-opacity-10 text-primary">
{{ log.workers.count }} Workers
</span>
</td>
<td><small class="text-muted">{{ log.notes|truncatechars:30 }}</small></td>
<td>
<a href="/admin/core/worklog/{{ log.id }}/change/" class="btn btn-sm btn-outline-secondary">Edit</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="card shadow-sm border-0">
<div class="card-body p-0">
{% if logs %}
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">Date</th>
<th>Project</th>
<th>Labourers</th>
<th>Status / Payslip</th>
<th>Supervisor</th>
<th class="pe-4 text-end">Action</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td class="ps-4">
<span class="fw-bold">{{ log.date|date:"D, d M Y" }}</span>
</td>
<td>
<span class="badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-25 px-2 py-1">
{{ log.project.name }}
</span>
</td>
<td>
{% if selected_worker %}
<span class="text-primary fw-medium">
{% for w in log.workers.all %}
{% if w.id == selected_worker %}
{{ w.name }}
{% endif %}
{% endfor %}
</span>
{% if log.workers.count > 1 %}
<small class="text-muted">(+{{ log.workers.count|add:"-1" }} others)</small>
{% endif %}
{% else %}
<div class="d-flex flex-wrap gap-1">
{% for w in log.workers.all|slice:":3" %}
<span class="small bg-light px-2 py-1 rounded border">{{ w.name|truncatechars:12 }}</span>
{% endfor %}
{% if log.workers.count > 3 %}
<span class="small text-muted align-self-center ms-1">+{{ log.workers.count|add:"-3" }}</span>
{% endif %}
</div>
{% endif %}
</td>
<td>
{% with payslip=log.paid_in.first %}
{% if payslip %}
<a href="{% url 'payslip_detail' payslip.id %}" class="text-decoration-none">
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25">
<i class="bi bi-check-circle-fill"></i> Paid (Slip #{{ payslip.id }})
</span>
</a>
{% else %}
<span class="badge bg-warning bg-opacity-10 text-warning border border-warning border-opacity-25">
Pending
</span>
{% endif %}
{% endwith %}
</td>
<td>
<small class="text-muted">{{ log.supervisor.username|default:"System" }}</small>
</td>
<td class="pe-4 text-end">
<div class="dropdown">
<button class="btn btn-sm btn-outline-light text-dark border-0" type="button" data-bs-toggle="dropdown">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-content dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="/admin/core/worklog/{{ log.id }}/change/">Edit Entry</a></li>
{% if log.notes %}
<li><hr class="dropdown-divider"></li>
<li class="px-3 py-1"><small class="text-muted">Note: {{ log.notes }}</small></li>
{% endif %}
</ul>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<i class="bi bi-search display-1 text-muted opacity-25 mb-3 d-block"></i>
<h4 class="text-muted">No logs found matching filters.</h4>
<p class="text-muted mb-4">Try adjusting your filters or record a new entry.</p>
<a href="{% url 'log_attendance' %}" class="btn btn-primary">Log Attendance</a>
</div>
{% endif %}
</div>
{% else %}
<div class="text-center py-5">
<p class="text-muted">No work logs recorded yet.</p>
<a href="{% url 'log_attendance' %}" class="btn btn-primary">Log First Attendance</a>
</div>
{% endif %}
</div>
</div>
{% endblock %}
<style>
.mt-n4 { margin-top: -1.5rem !important; }
.bg-accent { background-color: var(--bs-accent); }
.btn-accent { background-color: #ffde59; color: #000; border: none; font-weight: 600; }
.btn-accent:hover { background-color: #e6c850; }
</style>
{% endblock %}

View File

@ -4,7 +4,7 @@ import json
from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone
from django.contrib.auth.decorators import login_required
from django.db.models import Sum, Q
from django.db.models import Sum, Q, Prefetch
from django.core.mail import send_mail
from django.conf import settings
from django.contrib import messages
@ -148,14 +148,58 @@ def log_attendance(request):
return render(request, 'core/log_attendance.html', context)
def work_log_list(request):
logs = WorkLog.objects.all().order_by('-date')
return render(request, 'core/work_log_list.html', {'logs': logs})
"""View work log history with advanced filtering."""
worker_id = request.GET.get('worker')
team_id = request.GET.get('team')
project_id = request.GET.get('project')
payment_status = request.GET.get('payment_status') # 'paid', 'unpaid', 'all'
logs = WorkLog.objects.all().prefetch_related('workers', 'project', 'supervisor', 'paid_in').order_by('-date', '-id')
if worker_id:
logs = logs.filter(workers__id=worker_id)
if team_id:
# Find workers in this team and filter logs containing them
team_workers = Worker.objects.filter(teams__id=team_id)
logs = logs.filter(workers__in=team_workers).distinct()
if project_id:
logs = logs.filter(project_id=project_id)
if payment_status == 'paid':
# Logs that are linked to at least one PayrollRecord
logs = logs.filter(paid_in__isnull=False).distinct()
elif payment_status == 'unpaid':
# This is tricky because a log can have multiple workers, some paid some not.
# But usually a WorkLog is marked paid when its workers are paid.
# If we filtered by worker, we can check if THAT worker is paid in that log.
if worker_id:
worker = get_object_or_404(Worker, pk=worker_id)
logs = logs.exclude(paid_in__worker=worker)
else:
logs = logs.filter(paid_in__isnull=True)
# Context for filters
context = {
'logs': logs,
'workers': Worker.objects.filter(is_active=True).order_by('name'),
'teams': Team.objects.filter(is_active=True).order_by('name'),
'projects': Project.objects.filter(is_active=True).order_by('name'),
'selected_worker': int(worker_id) if worker_id else None,
'selected_team': int(team_id) if team_id else None,
'selected_project': int(project_id) if project_id else None,
'selected_payment_status': payment_status,
}
return render(request, 'core/work_log_list.html', context)
def manage_resources(request):
"""View to manage active status of resources."""
workers = Worker.objects.all().order_by('name')
# Prefetch teams for workers to avoid N+1 in template
workers = Worker.objects.all().prefetch_related('teams').order_by('name')
projects = Project.objects.all().order_by('name')
teams = Team.objects.all().order_by('name')
teams = Team.objects.all().prefetch_related('workers').order_by('name')
context = {
'workers': workers,
@ -302,4 +346,4 @@ def payslip_detail(request, pk):
'record': record,
'logs': logs,
}
return render(request, 'core/payslip.html', context)
return render(request, 'core/payslip.html', context)