7.2
This commit is contained in:
parent
091645a299
commit
fc8c69d0d5
Binary file not shown.
@ -49,14 +49,22 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>ID Number</th>
|
<th>ID Number</th>
|
||||||
|
<th>Teams</th>
|
||||||
<th class="text-end">Active Status</th>
|
<th class="text-end">Active Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for worker in workers %}
|
{% for worker in workers %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ worker.name }}</td>
|
<td><strong>{{ worker.name }}</strong></td>
|
||||||
<td>{{ worker.id_no }}</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">
|
<td class="text-end">
|
||||||
<div class="form-check form-switch d-flex justify-content-end">
|
<div class="form-check form-switch d-flex justify-content-end">
|
||||||
<input class="form-check-input resource-toggle" type="checkbox" role="switch"
|
<input class="form-check-input resource-toggle" type="checkbox" role="switch"
|
||||||
@ -68,7 +76,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% 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 %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -93,7 +101,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for project in projects %}
|
{% for project in projects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ project.name }}</td>
|
<td><strong>{{ project.name }}</strong></td>
|
||||||
<td>{{ project.description|truncatechars:50 }}</td>
|
<td>{{ project.description|truncatechars:50 }}</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<div class="form-check form-switch d-flex justify-content-end">
|
<div class="form-check form-switch d-flex justify-content-end">
|
||||||
@ -132,7 +140,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for team in teams %}
|
{% for team in teams %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ team.name }}</td>
|
<td><strong>{{ team.name }}</strong></td>
|
||||||
<td>{{ team.supervisor.username|default:"-" }}</td>
|
<td>{{ team.supervisor.username|default:"-" }}</td>
|
||||||
<td>{{ team.workers.count }}</td>
|
<td>{{ team.workers.count }}</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
@ -210,4 +218,4 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -4,61 +4,176 @@
|
|||||||
{% block title %}Work Log History | LabourFlow{% endblock %}
|
{% block title %}Work Log History | LabourFlow{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="dashboard-header">
|
<div class="dashboard-header pb-5">
|
||||||
<div class="container">
|
<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>
|
<div>
|
||||||
<h1 class="display-5 mb-2">Work Log History</h1>
|
<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>
|
</div>
|
||||||
<a href="{% url 'log_attendance' %}" class="btn btn-accent shadow-sm">
|
<a href="{% url 'log_attendance' %}" class="btn btn-accent shadow-sm">
|
||||||
+ New Entry
|
+ New Entry
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<div class="container mb-5 mt-n4">
|
<div class="container mb-5 mt-n4">
|
||||||
<div class="card p-4 shadow-sm">
|
<div class="card shadow-sm border-0">
|
||||||
{% if logs %}
|
<div class="card-body p-0">
|
||||||
<div class="table-responsive">
|
{% if logs %}
|
||||||
<table class="table table-hover align-middle">
|
<div class="table-responsive">
|
||||||
<thead class="table-light">
|
<table class="table table-hover align-middle mb-0">
|
||||||
<tr>
|
<thead class="bg-light">
|
||||||
<th>Date</th>
|
<tr>
|
||||||
<th>Project</th>
|
<th class="ps-4">Date</th>
|
||||||
<th>Supervisor</th>
|
<th>Project</th>
|
||||||
<th>Labourers</th>
|
<th>Labourers</th>
|
||||||
<th>Notes</th>
|
<th>Status / Payslip</th>
|
||||||
<th>Action</th>
|
<th>Supervisor</th>
|
||||||
</tr>
|
<th class="pe-4 text-end">Action</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
{% for log in logs %}
|
<tbody>
|
||||||
<tr>
|
{% for log in logs %}
|
||||||
<td>{{ log.date }}</td>
|
<tr>
|
||||||
<td><strong>{{ log.project.name }}</strong></td>
|
<td class="ps-4">
|
||||||
<td>{{ log.supervisor.username|default:"System" }}</td>
|
<span class="fw-bold">{{ log.date|date:"D, d M Y" }}</span>
|
||||||
<td>
|
</td>
|
||||||
<span class="badge bg-primary bg-opacity-10 text-primary">
|
<td>
|
||||||
{{ log.workers.count }} Workers
|
<span class="badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-25 px-2 py-1">
|
||||||
</span>
|
{{ log.project.name }}
|
||||||
</td>
|
</span>
|
||||||
<td><small class="text-muted">{{ log.notes|truncatechars:30 }}</small></td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="/admin/core/worklog/{{ log.id }}/change/" class="btn btn-sm btn-outline-secondary">Edit</a>
|
{% if selected_worker %}
|
||||||
</td>
|
<span class="text-primary fw-medium">
|
||||||
</tr>
|
{% for w in log.workers.all %}
|
||||||
{% endfor %}
|
{% if w.id == selected_worker %}
|
||||||
</tbody>
|
{{ w.name }}
|
||||||
</table>
|
{% 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>
|
</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>
|
||||||
</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 %}
|
||||||
@ -4,7 +4,7 @@ import json
|
|||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.auth.decorators import login_required
|
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.core.mail import send_mail
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@ -148,14 +148,58 @@ def log_attendance(request):
|
|||||||
return render(request, 'core/log_attendance.html', context)
|
return render(request, 'core/log_attendance.html', context)
|
||||||
|
|
||||||
def work_log_list(request):
|
def work_log_list(request):
|
||||||
logs = WorkLog.objects.all().order_by('-date')
|
"""View work log history with advanced filtering."""
|
||||||
return render(request, 'core/work_log_list.html', {'logs': logs})
|
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):
|
def manage_resources(request):
|
||||||
"""View to manage active status of resources."""
|
"""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')
|
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 = {
|
context = {
|
||||||
'workers': workers,
|
'workers': workers,
|
||||||
@ -302,4 +346,4 @@ def payslip_detail(request, pk):
|
|||||||
'record': record,
|
'record': record,
|
||||||
'logs': logs,
|
'logs': logs,
|
||||||
}
|
}
|
||||||
return render(request, 'core/payslip.html', context)
|
return render(request, 'core/payslip.html', context)
|
||||||
Loading…
x
Reference in New Issue
Block a user