38156-vm/core/templates/core/log_attendance.html
Konradzar d922a14ec5 Ver 14 - Dashboard redesign, permissions, payslip preview, team tracking
- Dashboard: Outstanding Payments, Paid This Month, Active Loans cards
- Dashboard: This Week summary, Recent Activity, Quick Actions, Manage Resources
- Dashboard: Active/Inactive/All filter for resources
- Payroll: Preview payslip modal (no DB/email side effects)
- Payroll: Multi-select workers in adjustment modal
- History: Team column + direct team FK on WorkLog
- History: Shift+click multi-date selection on calendar
- Permissions: Replaced PIN system with Django groups (Admin, Work Logger)
- Permissions: Renamed Supervisor to Work Logger throughout
- Nav: Hide financial links (Payroll) from non-admin users
- Admin: Enhanced Django admin with group management
- New migrations: 0011 (remove pin/is_admin), 0012 (add team to WorkLog)
- New management command: setup_groups

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 23:43:38 +02:00

284 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'base.html' %}
{% load static %}
{% block title %}Log Daily Attendance | LabourFlow{% endblock %}
{% block content %}
<div class="dashboard-header">
<div class="container">
<h1 class="display-5 mb-2">Log Daily Attendance</h1>
<p class="lead opacity-75">Record work for projects and labourers.</p>
</div>
</div>
<div class="container mb-5 mt-n4">
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="card p-4 shadow-sm">
<form method="post" id="workLogForm">
{% csrf_token %}
<div class="row mb-4">
<div class="col-md-3">
<label class="form-label fw-bold">Start Date</label>
{{ form.date }}
{% if form.date.errors %}
<div class="text-danger mt-1 small">{{ form.date.errors }}</div>
{% endif %}
</div>
<div class="col-md-3">
<label class="form-label fw-bold">End Date (Optional)</label>
{{ form.end_date }}
<div class="mt-2">
<div class="form-check form-check-inline">
{{ form.include_saturday }}
<label class="form-check-label small" for="{{ form.include_saturday.id_for_label }}">Sat</label>
</div>
<div class="form-check form-check-inline">
{{ form.include_sunday }}
<label class="form-check-label small" for="{{ form.include_sunday.id_for_label }}">Sun</label>
</div>
</div>
</div>
<div class="col-md-3">
<label class="form-label fw-bold">Project</label>
{{ form.project }}
{% if form.project.errors %}
<div class="text-danger mt-1 small">{{ form.project.errors }}</div>
{% endif %}
</div>
<div class="col-md-3">
<label class="form-label fw-bold">Team (Optional)</label>
{{ form.team }}
{% if form.team.errors %}
<div class="text-danger mt-1 small">{{ form.team.errors }}</div>
{% endif %}
</div>
</div>
<div class="mb-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<label class="form-label fw-bold mb-0">Select Labourers</label>
<a href="{% url 'manage_resources' %}" class="small text-decoration-none text-primary">Manage Resources</a>
</div>
<div class="row">
{% for checkbox in form.workers %}
<div class="col-md-6 col-lg-4 mb-2">
<div class="form-check p-3 border rounded-3 hover-shadow transition-all">
{{ checkbox.tag }}
<label class="form-check-label ms-2" for="{{ checkbox.id_for_label }}">
{{ checkbox.choice_label }}
</label>
</div>
</div>
{% endfor %}
</div>
{% if form.workers.errors %}
<div class="text-danger mt-1 small">{{ form.workers.errors }}</div>
{% endif %}
</div>
<div class="mb-4">
<label class="form-label fw-bold">Notes / Comments</label>
{{ form.notes }}
{% if form.notes.errors %}
<div class="text-danger mt-1 small">{{ form.notes.errors }}</div>
{% endif %}
</div>
{% if is_admin_user %}
<!-- Total Cost Estimation (Admin only) -->
<div class="card p-3 mb-4 bg-light border-0 shadow-sm">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="mb-0 text-muted">Estimated Cost</h5>
<small class="text-muted" id="calculationDetails">0 workers × 0 days</small>
</div>
<h3 class="mb-0 fw-bold text-dark font-monospace" id="estimatedTotal">R 0.00</h3>
</div>
</div>
{% endif %}
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<a href="{% url 'home' %}" class="btn btn-light px-4">Cancel</a>
<button type="submit" class="btn btn-primary px-5">Save Work Log</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% if is_conflict %}
<!-- Conflict Resolution Modal -->
<div class="modal fade" id="conflictModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="conflictModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-warning text-dark">
<h5 class="modal-title fw-bold" id="conflictModalLabel">
Duplicate Logs Detected
</h5>
</div>
<div class="modal-body">
<p class="fw-bold">The following duplicate entries were found:</p>
<div class="card bg-light mb-3" style="max-height: 200px; overflow-y: auto;">
<ul class="list-group list-group-flush">
{% for conflict in conflicting_workers %}
<li class="list-group-item bg-transparent">{{ conflict.name }}</li>
{% endfor %}
</ul>
</div>
<p>How would you like to proceed?</p>
<div class="alert alert-info small mb-0">
<strong>Skip:</strong> Log only the new entries. Existing logs remain unchanged.<br>
<strong>Overwrite:</strong> Update existing logs for these dates with the new project/team selection.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="submitConflict('skip')">Skip Duplicates</button>
<button type="button" class="btn btn-primary" onclick="submitConflict('overwrite')">Overwrite Existing</button>
</div>
</div>
</div>
</div>
{% endif %}
<style>
.hover-shadow:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
border-color: #0d6efd !important;
}
.transition-all {
transition: all 0.2s ease-in-out;
}
.mt-n4 {
margin-top: -3rem !important;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const teamSelect = document.getElementById('{{ form.team.id_for_label }}');
const teamWorkersMap = {{ team_workers_json|safe }};
if (teamSelect) {
teamSelect.addEventListener('change', function() {
const teamId = this.value;
if (teamId && teamWorkersMap[teamId]) {
const workerIds = teamWorkersMap[teamId];
// Select workers belonging to the team
workerIds.forEach(function(id) {
const checkbox = document.querySelector(`input[name="workers"][value="${id}"]`);
if (checkbox) {
checkbox.checked = true;
}
});
}
});
}
// Show conflict modal if it exists
const conflictModalEl = document.getElementById('conflictModal');
if (conflictModalEl) {
var myModal = new bootstrap.Modal(conflictModalEl);
myModal.show();
}
// --- Cost Calculation Logic (Admin only) ---
{% if is_admin_user %}
const workerRates = {{ worker_rates_json|safe }};
const startDateInput = document.getElementById('{{ form.date.id_for_label }}');
const endDateInput = document.getElementById('{{ form.end_date.id_for_label }}');
const satCheckbox = document.getElementById('{{ form.include_saturday.id_for_label }}');
const sunCheckbox = document.getElementById('{{ form.include_sunday.id_for_label }}');
const workerCheckboxes = document.querySelectorAll('input[name="workers"]');
const totalDisplay = document.getElementById('estimatedTotal');
const detailsDisplay = document.getElementById('calculationDetails');
function calculateTotal() {
// 1. Calculate Days
let days = 0;
const start = startDateInput.value ? new Date(startDateInput.value) : null;
const end = endDateInput.value ? new Date(endDateInput.value) : null;
if (start) {
if (!end || end < start) {
days = 1;
} else {
// Iterate dates
let curr = new Date(start);
// Reset time components to avoid TZ issues
curr.setHours(0,0,0,0);
const last = new Date(end);
last.setHours(0,0,0,0);
while (curr <= last) {
const dayOfWeek = curr.getDay(); // 0 = Sun, 6 = Sat
let isWorkingDay = true;
if (dayOfWeek === 6 && !satCheckbox.checked) isWorkingDay = false;
if (dayOfWeek === 0 && !sunCheckbox.checked) isWorkingDay = false;
if (isWorkingDay) days++;
curr.setDate(curr.getDate() + 1);
}
}
}
// 2. Sum Worker Rates
let dailyRateSum = 0;
let workerCount = 0;
workerCheckboxes.forEach(cb => {
if (cb.checked) {
const rate = workerRates[cb.value] || 0;
dailyRateSum += rate;
workerCount++;
}
});
// 3. Update UI
const total = dailyRateSum * days;
totalDisplay.textContent = 'R ' + total.toLocaleString('en-ZA', {minimumFractionDigits: 2, maximumFractionDigits: 2});
detailsDisplay.textContent = `${workerCount} worker${workerCount !== 1 ? 's' : ''} × ${days} day${days !== 1 ? 's' : ''}`;
}
// Attach Listeners
if (startDateInput) startDateInput.addEventListener('change', calculateTotal);
if (endDateInput) endDateInput.addEventListener('change', calculateTotal);
if (satCheckbox) satCheckbox.addEventListener('change', calculateTotal);
if (sunCheckbox) sunCheckbox.addEventListener('change', calculateTotal);
workerCheckboxes.forEach(cb => {
cb.addEventListener('change', calculateTotal);
});
// Also update when team changes (since it selects workers programmatically)
if (teamSelect) {
teamSelect.addEventListener('change', function() {
// Give it a moment for the check logic to finish
setTimeout(calculateTotal, 100);
});
}
// Initial Run
calculateTotal();
{% endif %}
});
function submitConflict(action) {
const form = document.getElementById('workLogForm');
// Check if input already exists
let input = form.querySelector('input[name="conflict_action"]');
if (!input) {
input = document.createElement('input');
input.type = 'hidden';
input.name = 'conflict_action';
form.appendChild(input);
}
input.value = action;
form.submit();
}
</script>
{% endblock %}