38156-vm/core/templates/core/log_attendance.html
2026-02-08 22:30:30 +00:00

292 lines
13 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;
// First, deselect ALL workers to clear previous selection
const allCheckboxes = document.querySelectorAll('input[name="workers"]');
allCheckboxes.forEach(cb => {
cb.checked = false;
});
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 %}