Ver 9.1 Calender added
This commit is contained in:
parent
af006bef5f
commit
06d5101e39
Binary file not shown.
@ -11,7 +11,17 @@
|
||||
<h1 class="display-5 mb-2">Work Log History</h1>
|
||||
<p class="lead opacity-75">Filter and review historical daily work logs.</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<div class="d-flex gap-2 align-items-center">
|
||||
<!-- View Switcher -->
|
||||
<div class="btn-group me-2 shadow-sm" role="group">
|
||||
<a href="?view=list&worker={{ selected_worker|default:'' }}&team={{ selected_team|default:'' }}&project={{ selected_project|default:'' }}&payment_status={{ selected_payment_status|default:'' }}" class="btn btn-outline-secondary bg-white {% if view_mode != 'calendar' %}active fw-bold{% endif %}">
|
||||
<i class="bi bi-list-ul"></i> List
|
||||
</a>
|
||||
<a href="?view=calendar&worker={{ selected_worker|default:'' }}&team={{ selected_team|default:'' }}&project={{ selected_project|default:'' }}&payment_status={{ selected_payment_status|default:'' }}" class="btn btn-outline-secondary bg-white {% if view_mode == 'calendar' %}active fw-bold{% endif %}">
|
||||
<i class="bi bi-calendar3"></i> Calendar
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a href="{% url 'export_work_log_csv' %}?{{ request.GET.urlencode }}" class="btn btn-outline-secondary shadow-sm border-0 bg-white text-dark">
|
||||
<i class="bi bi-download me-1"></i> Export CSV
|
||||
</a>
|
||||
@ -25,6 +35,12 @@
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<form method="get" class="row g-3">
|
||||
<input type="hidden" name="view" value="{{ view_mode }}">
|
||||
{% if view_mode == 'calendar' %}
|
||||
<input type="hidden" name="month" value="{{ curr_month }}">
|
||||
<input type="hidden" name="year" value="{{ curr_year }}">
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small text-muted text-uppercase fw-bold">Worker</label>
|
||||
<select name="worker" class="form-select">
|
||||
@ -66,7 +82,7 @@
|
||||
</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">
|
||||
<a href="{% url 'work_log_list' %}?view={{ view_mode }}" class="btn btn-sm btn-link text-decoration-none text-muted">
|
||||
<i class="bi bi-x-circle"></i> Clear all filters
|
||||
</a>
|
||||
</div>
|
||||
@ -77,6 +93,84 @@
|
||||
</div>
|
||||
|
||||
<div class="container mb-5 mt-n4">
|
||||
{% if view_mode == 'calendar' %}
|
||||
<!-- CALENDAR VIEW -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3 class="mb-0 fw-bold">{{ month_name }} {{ curr_year }}</h3>
|
||||
<div class="btn-group shadow-sm">
|
||||
<a href="?view=calendar&month={{ prev_month }}&year={{ prev_year }}&worker={{ selected_worker|default:'' }}&team={{ selected_team|default:'' }}&project={{ selected_project|default:'' }}&payment_status={{ selected_payment_status|default:'' }}" class="btn btn-light border bg-white">
|
||||
<i class="bi bi-chevron-left"></i> Previous
|
||||
</a>
|
||||
<a href="?view=calendar&month={{ next_month }}&year={{ next_year }}&worker={{ selected_worker|default:'' }}&team={{ selected_team|default:'' }}&project={{ selected_project|default:'' }}&payment_status={{ selected_payment_status|default:'' }}" class="btn btn-light border bg-white">
|
||||
Next <i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered mb-0 calendar-table" style="table-layout: fixed;">
|
||||
<thead class="bg-light text-center text-uppercase text-muted small">
|
||||
<tr>
|
||||
<th style="width: 14.28%">Mon</th>
|
||||
<th style="width: 14.28%">Tue</th>
|
||||
<th style="width: 14.28%">Wed</th>
|
||||
<th style="width: 14.28%">Thu</th>
|
||||
<th style="width: 14.28%">Fri</th>
|
||||
<th style="width: 14.28%">Sat</th>
|
||||
<th style="width: 14.28%">Sun</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for week in calendar_weeks %}
|
||||
<tr>
|
||||
{% for day_info in week %}
|
||||
<td class="{% if not day_info.is_current_month %}bg-light text-muted bg-opacity-10{% endif %}" style="height: 140px; vertical-align: top; padding: 10px;">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<span class="fw-bold {% if day_info.date == current_time.date %}text-primary bg-primary bg-opacity-10 px-2 rounded-circle{% endif %}">{{ day_info.day }}</span>
|
||||
{% if day_info.logs %}
|
||||
<span class="badge bg-secondary opacity-50">{{ day_info.logs|length }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="calendar-events" style="max-height: 100px; overflow-y: auto;">
|
||||
{% for log in day_info.logs %}
|
||||
<div class="mb-1 p-1 rounded border small bg-white shadow-sm" style="font-size: 0.75rem; line-height: 1.2; border-left: 3px solid var(--bs-primary) !important;">
|
||||
<div class="fw-bold text-truncate">{{ log.project.name }}</div>
|
||||
{% with team=log.workers.first.teams.first %}
|
||||
{% if team %}
|
||||
<div class="text-muted small text-truncate" style="font-size: 0.7rem;">
|
||||
<i class="bi bi-people-fill me-1"></i>{{ team.name }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class="text-muted text-truncate" title="{{ log.workers.count }} workers">
|
||||
{% if selected_worker %}
|
||||
{{ log.workers.first.name }}
|
||||
{% elif log.workers.count == 1 %}
|
||||
{{ log.workers.first.name }}
|
||||
{% else %}
|
||||
{{ log.workers.count }} workers
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if log.notes %}
|
||||
<!-- Optional: indicator for notes -->
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<!-- LIST VIEW -->
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-0">
|
||||
{% if logs %}
|
||||
@ -184,6 +278,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@ -191,5 +286,14 @@
|
||||
.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; }
|
||||
|
||||
/* Calendar Scrollbar custom */
|
||||
.calendar-events::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.calendar-events::-webkit-scrollbar-thumb {
|
||||
background-color: #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@ -2,6 +2,8 @@ import os
|
||||
import platform
|
||||
import json
|
||||
import csv
|
||||
import calendar
|
||||
import datetime
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.decorators import login_required
|
||||
@ -198,8 +200,9 @@ def work_log_list(request):
|
||||
team_id = request.GET.get('team')
|
||||
project_id = request.GET.get('project')
|
||||
payment_status = request.GET.get('payment_status') # 'paid', 'unpaid', 'all'
|
||||
view_mode = request.GET.get('view', 'list')
|
||||
|
||||
logs = WorkLog.objects.all().prefetch_related('workers', 'project', 'supervisor', 'paid_in').order_by('-date', '-id')
|
||||
logs = WorkLog.objects.all().prefetch_related('workers', 'workers__teams', 'project', 'supervisor', 'paid_in').order_by('-date', '-id')
|
||||
|
||||
target_worker = None
|
||||
if worker_id:
|
||||
@ -232,6 +235,28 @@ def work_log_list(request):
|
||||
# Convert to list to attach attributes
|
||||
final_logs = []
|
||||
total_amount = 0
|
||||
|
||||
# If Calendar View: Filter logs by Month BEFORE iterating to prevent fetching ALL history
|
||||
if view_mode == 'calendar':
|
||||
today = timezone.now().date()
|
||||
try:
|
||||
curr_year = int(request.GET.get('year', today.year))
|
||||
curr_month = int(request.GET.get('month', today.month))
|
||||
except ValueError:
|
||||
curr_year = today.year
|
||||
curr_month = today.month
|
||||
|
||||
# Bounds safety
|
||||
if curr_month < 1: curr_month = 1;
|
||||
if curr_month > 12: curr_month = 12;
|
||||
|
||||
# Get range
|
||||
_, num_days = calendar.monthrange(curr_year, curr_month)
|
||||
start_date = datetime.date(curr_year, curr_month, 1)
|
||||
end_date = datetime.date(curr_year, curr_month, num_days)
|
||||
|
||||
logs = logs.filter(date__range=(start_date, end_date))
|
||||
|
||||
for log in logs:
|
||||
if target_worker:
|
||||
log.display_amount = target_worker.day_rate
|
||||
@ -243,7 +268,6 @@ def work_log_list(request):
|
||||
|
||||
# Context for filters
|
||||
context = {
|
||||
'logs': final_logs,
|
||||
'total_amount': total_amount,
|
||||
'workers': Worker.objects.filter(is_active=True).order_by('name'),
|
||||
'teams': Team.objects.filter(is_active=True).order_by('name'),
|
||||
@ -253,8 +277,50 @@ def work_log_list(request):
|
||||
'selected_project': int(project_id) if project_id else None,
|
||||
'selected_payment_status': payment_status,
|
||||
'target_worker': target_worker,
|
||||
'view_mode': view_mode,
|
||||
}
|
||||
|
||||
if view_mode == 'calendar':
|
||||
# Group by date for easy lookup in template
|
||||
logs_map = {}
|
||||
for log in final_logs:
|
||||
if log.date not in logs_map:
|
||||
logs_map[log.date] = []
|
||||
logs_map[log.date].append(log)
|
||||
|
||||
cal = calendar.Calendar(firstweekday=0) # Monday is 0
|
||||
month_dates = cal.monthdatescalendar(curr_year, curr_month)
|
||||
|
||||
# Prepare structured data for template
|
||||
calendar_weeks = []
|
||||
for week in month_dates:
|
||||
week_data = []
|
||||
for d in week:
|
||||
week_data.append({
|
||||
'date': d,
|
||||
'day': d.day,
|
||||
'is_current_month': d.month == curr_month,
|
||||
'logs': logs_map.get(d, [])
|
||||
})
|
||||
calendar_weeks.append(week_data)
|
||||
|
||||
# Nav Links
|
||||
prev_month_date = start_date - datetime.timedelta(days=1)
|
||||
next_month_date = end_date + datetime.timedelta(days=1)
|
||||
|
||||
context.update({
|
||||
'calendar_weeks': calendar_weeks,
|
||||
'curr_month': curr_month,
|
||||
'curr_year': curr_year,
|
||||
'month_name': calendar.month_name[curr_month],
|
||||
'prev_month': prev_month_date.month,
|
||||
'prev_year': prev_month_date.year,
|
||||
'next_month': next_month_date.month,
|
||||
'next_year': next_month_date.year,
|
||||
})
|
||||
else:
|
||||
context['logs'] = final_logs
|
||||
|
||||
return render(request, 'core/work_log_list.html', context)
|
||||
|
||||
def export_work_log_csv(request):
|
||||
@ -264,7 +330,7 @@ def export_work_log_csv(request):
|
||||
project_id = request.GET.get('project')
|
||||
payment_status = request.GET.get('payment_status')
|
||||
|
||||
logs = WorkLog.objects.all().prefetch_related('workers', 'project', 'supervisor', 'paid_in').order_by('-date', '-id')
|
||||
logs = WorkLog.objects.all().prefetch_related('workers', 'workers__teams', 'project', 'supervisor', 'paid_in').order_by('-date', '-id')
|
||||
|
||||
target_worker = None
|
||||
if worker_id:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user