Ver 1.04
This commit is contained in:
parent
d513f6ec09
commit
306fb0e95d
Binary file not shown.
Binary file not shown.
@ -4,64 +4,97 @@
|
|||||||
{% block title %}Dashboard | FoxFitt{% endblock %}
|
{% block title %}Dashboard | FoxFitt{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container py-4">
|
<!-- Gradient Header -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="dashboard-header mb-5 rounded shadow-sm p-4 d-flex justify-content-between align-items-center">
|
||||||
<h1 class="h3 mb-0 text-gray-800">Welcome back, {{ user.first_name|default:user.username }}!</h1>
|
<div>
|
||||||
<a href="{% url 'attendance_log' %}" class="btn text-white shadow-sm" style="background-color: #10b981;">
|
<h1 class="h3 mb-0 text-white" style="font-family: 'Poppins', sans-serif;">Dashboard</h1>
|
||||||
<i class="fas fa-plus fa-sm text-white-50 me-1"></i> Log Work
|
<p class="text-white-50 mb-0">Welcome back, {{ user.first_name|default:user.username }}!</p>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
<a href="{% url 'attendance_log' %}" class="btn btn-accent shadow-sm">
|
||||||
|
<i class="fas fa-plus fa-sm me-1"></i> Log Daily Work
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Stats Row -->
|
<div class="container py-2" style="margin-top: -3rem;">
|
||||||
<div class="row g-4 mb-4">
|
{% if is_admin %}
|
||||||
<!-- Active Workers Card -->
|
<!-- Admin View -->
|
||||||
<div class="col-xl-4 col-md-6">
|
<div class="row g-4 mb-4 position-relative">
|
||||||
<div class="card border-0 shadow-sm h-100 py-2" style="border-left: 4px solid #10b981 !important;">
|
<!-- Outstanding Payments Card -->
|
||||||
|
<div class="col-xl-3 col-md-6">
|
||||||
|
<div class="card stat-card h-100 py-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row no-gutters align-items-center">
|
||||||
|
<div class="col me-2">
|
||||||
|
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #ef4444;">
|
||||||
|
Outstanding Payments</div>
|
||||||
|
<div class="h5 mb-0 font-weight-bold text-gray-800">${{ outstanding_payments|floatformat:2 }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<i class="fas fa-exclamation-circle fa-2x text-danger opacity-50"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Paid This Month Card -->
|
||||||
|
<div class="col-xl-3 col-md-6">
|
||||||
|
<div class="card stat-card h-100 py-2">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row no-gutters align-items-center">
|
<div class="row no-gutters align-items-center">
|
||||||
<div class="col me-2">
|
<div class="col me-2">
|
||||||
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #10b981;">
|
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #10b981;">
|
||||||
Active Workers</div>
|
Paid This Month</div>
|
||||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ total_workers|default:"0" }}</div>
|
<div class="h5 mb-0 font-weight-bold text-gray-800">${{ paid_this_month|floatformat:2 }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<i class="fas fa-users fa-2x text-secondary opacity-50"></i>
|
<i class="fas fa-check-circle fa-2x text-success opacity-50"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Projects Card -->
|
<!-- Active Loans Card -->
|
||||||
<div class="col-xl-4 col-md-6">
|
<div class="col-xl-3 col-md-6">
|
||||||
<div class="card border-0 shadow-sm h-100 py-2" style="border-left: 4px solid #3b82f6 !important;">
|
<div class="card stat-card h-100 py-2">
|
||||||
<div class="card-body">
|
|
||||||
<div class="row no-gutters align-items-center">
|
|
||||||
<div class="col me-2">
|
|
||||||
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #3b82f6;">
|
|
||||||
Active Projects</div>
|
|
||||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ total_projects|default:"0" }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<i class="fas fa-hard-hat fa-2x text-secondary opacity-50"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Today's Attendance Card -->
|
|
||||||
<div class="col-xl-4 col-md-6">
|
|
||||||
<div class="card border-0 shadow-sm h-100 py-2" style="border-left: 4px solid #f59e0b !important;">
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row no-gutters align-items-center">
|
<div class="row no-gutters align-items-center">
|
||||||
<div class="col me-2">
|
<div class="col me-2">
|
||||||
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #f59e0b;">
|
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #f59e0b;">
|
||||||
Today's Logs</div>
|
Active Loans ({{ active_loans_count }})</div>
|
||||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ today_attendance|default:"0" }}</div>
|
<div class="h5 mb-0 font-weight-bold text-gray-800">${{ active_loans_balance|floatformat:2 }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<i class="fas fa-clipboard-check fa-2x text-secondary opacity-50"></i>
|
<i class="fas fa-hand-holding-usd fa-2x text-warning opacity-50"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Outstanding by Project -->
|
||||||
|
<div class="col-xl-3 col-md-6">
|
||||||
|
<div class="card stat-card h-100 py-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row no-gutters align-items-center">
|
||||||
|
<div class="col me-2">
|
||||||
|
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #3b82f6;">
|
||||||
|
Outstanding by Project</div>
|
||||||
|
<div class="mb-0 text-gray-800" style="font-size: 0.85rem; max-height: 60px; overflow-y: auto;">
|
||||||
|
{% if outstanding_by_project %}
|
||||||
|
<ul class="list-unstyled mb-0">
|
||||||
|
{% for proj, amount in outstanding_by_project.items %}
|
||||||
|
<li><strong>{{ proj }}:</strong> ${{ amount|floatformat:2 }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<i class="fas fa-chart-pie fa-2x text-primary opacity-50"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -69,18 +102,223 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content Row -->
|
<!-- Quick Actions and This Week -->
|
||||||
<div class="row">
|
<div class="row mb-4">
|
||||||
<div class="col-lg-12 mb-4">
|
<!-- This Week -->
|
||||||
<div class="card shadow-sm border-0 mb-4">
|
<div class="col-lg-4 mb-4 mb-lg-0">
|
||||||
<div class="card-header py-3 bg-white d-flex flex-row align-items-center justify-content-between">
|
<div class="card shadow-sm border-0 h-100">
|
||||||
<h6 class="m-0 font-weight-bold" style="color: #0f172a;">Recent Activity</h6>
|
<div class="card-header py-3 bg-white">
|
||||||
|
<h6 class="m-0 font-weight-bold" style="color: #0f172a;">This Week Summary</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body text-center d-flex flex-column justify-content-center">
|
||||||
<p class="text-muted mb-0">No recent activity to show yet. Start by logging today's attendance!</p>
|
<div class="h1 mb-0 font-weight-bold text-primary">{{ this_week_logs }}</div>
|
||||||
|
<div class="text-muted">Work Logs Created This Week</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card shadow-sm border-0 h-100">
|
||||||
|
<div class="card-header py-3 bg-white">
|
||||||
|
<h6 class="m-0 font-weight-bold" style="color: #0f172a;">Quick Actions</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body d-flex align-items-center justify-content-around flex-wrap">
|
||||||
|
<a href="{% url 'attendance_log' %}" class="btn btn-lg btn-outline-primary mb-2">
|
||||||
|
<i class="fas fa-clipboard-list mb-2 d-block fa-2x"></i> Log Work
|
||||||
|
</a>
|
||||||
|
<a href="#" class="btn btn-lg btn-outline-success mb-2">
|
||||||
|
<i class="fas fa-money-check-alt mb-2 d-block fa-2x"></i> Run Payroll
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'work_history' %}" class="btn btn-lg btn-outline-secondary mb-2">
|
||||||
|
<i class="fas fa-history mb-2 d-block fa-2x"></i> View History
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Recent Activity -->
|
||||||
|
<div class="col-lg-6 mb-4">
|
||||||
|
<div class="card shadow-sm border-0 h-100">
|
||||||
|
<div class="card-header py-3 bg-white">
|
||||||
|
<h6 class="m-0 font-weight-bold" style="color: #0f172a;">Recent Activity</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="list-group list-group-flush">
|
||||||
|
{% for log in recent_activity %}
|
||||||
|
<div class="list-group-item px-4 py-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-1">{{ log.project.name }}</h6>
|
||||||
|
<small class="text-muted">{{ log.date }} · {{ log.workers.count }} workers</small>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-light text-dark border">{{ log.supervisor.username }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="p-4 text-center text-muted">
|
||||||
|
No recent activity.
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Manage Resources -->
|
||||||
|
<div class="col-lg-6 mb-4">
|
||||||
|
<div class="card shadow-sm border-0 h-100">
|
||||||
|
<div class="card-header py-3 bg-white">
|
||||||
|
<h6 class="m-0 font-weight-bold" style="color: #0f172a;">Manage Resources</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<ul class="nav nav-tabs px-3 pt-3" id="resourceTabs" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="workers-tab" data-bs-toggle="tab" data-bs-target="#workers" type="button" role="tab" aria-selected="true">Workers</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="projects-tab" data-bs-toggle="tab" data-bs-target="#projects" type="button" role="tab" aria-selected="false">Projects</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="teams-tab" data-bs-toggle="tab" data-bs-target="#teams" type="button" role="tab" aria-selected="false">Teams</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" id="resourceTabsContent">
|
||||||
|
<!-- Workers Tab -->
|
||||||
|
<div class="tab-pane fade show active" id="workers" role="tabpanel">
|
||||||
|
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
||||||
|
{% for item in workers %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ item.name }}
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="worker" data-id="{{ item.id }}" {% if item.active %}checked{% endif %}>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="list-group-item text-muted">No workers found.</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<!-- Projects Tab -->
|
||||||
|
<div class="tab-pane fade" id="projects" role="tabpanel">
|
||||||
|
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
||||||
|
{% for item in projects %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ item.name }}
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="project" data-id="{{ item.id }}" {% if item.active %}checked{% endif %}>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="list-group-item text-muted">No projects found.</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<!-- Teams Tab -->
|
||||||
|
<div class="tab-pane fade" id="teams" role="tabpanel">
|
||||||
|
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
||||||
|
{% for item in teams %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ item.name }}
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="team" data-id="{{ item.id }}" {% if item.active %}checked{% endif %}>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="list-group-item text-muted">No teams found.</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<!-- Supervisor View -->
|
||||||
|
<div class="row mb-4 position-relative">
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card shadow-sm border-0 h-100">
|
||||||
|
<div class="card-header py-3 bg-white">
|
||||||
|
<h6 class="m-0 font-weight-bold" style="color: #0f172a;">This Week Summary</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body text-center d-flex flex-column justify-content-center">
|
||||||
|
<div class="h1 mb-0 font-weight-bold text-primary">{{ this_week_logs }}</div>
|
||||||
|
<div class="text-muted">Work Logs Created This Week</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card shadow-sm border-0 h-100">
|
||||||
|
<div class="card-header py-3 bg-white">
|
||||||
|
<h6 class="m-0 font-weight-bold" style="color: #0f172a;">Recent Activity</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="list-group list-group-flush">
|
||||||
|
{% for log in recent_activity %}
|
||||||
|
<div class="list-group-item px-4 py-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-1">{{ log.project.name }}</h6>
|
||||||
|
<small class="text-muted">{{ log.date }} · {{ log.workers.count }} workers</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="p-4 text-center text-muted">
|
||||||
|
No recent activity.
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const toggleSwitches = document.querySelectorAll('.toggle-active');
|
||||||
|
|
||||||
|
toggleSwitches.forEach(switchEl => {
|
||||||
|
switchEl.addEventListener('change', function() {
|
||||||
|
const type = this.getAttribute('data-type');
|
||||||
|
const id = this.getAttribute('data-id');
|
||||||
|
const isChecked = this.checked;
|
||||||
|
|
||||||
|
fetch(`/toggle/${type}/${id}/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': '{{ csrf_token }}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data.status !== 'success') {
|
||||||
|
// Revert if failed
|
||||||
|
this.checked = !isChecked;
|
||||||
|
alert('Error updating status.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Revert on error
|
||||||
|
this.checked = !isChecked;
|
||||||
|
alert('Error updating status.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
62
core/templates/core/work_history.html
Normal file
62
core/templates/core/work_history.html
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Work History | FoxFitt{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h1 class="h3 mb-0" style="color: #0f172a; font-family: 'Poppins', sans-serif;">Work History</h1>
|
||||||
|
<a href="{% url 'home' %}" class="btn btn-outline-secondary btn-sm shadow-sm">
|
||||||
|
<i class="fas fa-arrow-left fa-sm me-1"></i> Back
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="ps-4">Date</th>
|
||||||
|
<th scope="col">Project</th>
|
||||||
|
<th scope="col">Team</th>
|
||||||
|
<th scope="col">Workers</th>
|
||||||
|
<th scope="col">Supervisor</th>
|
||||||
|
<th scope="col">Overtime</th>
|
||||||
|
<th scope="col" class="pe-4">Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for log in logs %}
|
||||||
|
<tr>
|
||||||
|
<td class="ps-4 align-middle">{{ log.date }}</td>
|
||||||
|
<td class="align-middle"><strong>{{ log.project.name }}</strong></td>
|
||||||
|
<td class="align-middle">{{ log.team.name|default:"-" }}</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<span class="badge bg-secondary">{{ log.workers.count }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle">{{ log.supervisor.username|default:"-" }}</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
{% if log.overtime_amount > 0 %}
|
||||||
|
<span class="badge bg-warning text-dark">{{ log.get_overtime_amount_display }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">-</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="pe-4 align-middle text-muted small">
|
||||||
|
{{ log.notes|truncatechars:30 }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center py-4 text-muted">No work history found.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -4,4 +4,6 @@ from . import views
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.index, name='home'),
|
path('', views.index, name='home'),
|
||||||
path('attendance/log/', views.attendance_log, name='attendance_log'),
|
path('attendance/log/', views.attendance_log, name='attendance_log'),
|
||||||
]
|
path('history/', views.work_history, name='work_history'),
|
||||||
|
path('toggle/<str:model_name>/<int:item_id>/', views.toggle_active, name='toggle_active'),
|
||||||
|
]
|
||||||
|
|||||||
132
core/views.py
132
core/views.py
@ -1,23 +1,95 @@
|
|||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from .models import Worker, Project, WorkLog, Team
|
from django.db.models import Sum
|
||||||
|
from decimal import Decimal
|
||||||
|
from .models import Worker, Project, WorkLog, Team, PayrollRecord, Loan, PayrollAdjustment
|
||||||
from .forms import AttendanceLogForm
|
from .forms import AttendanceLogForm
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.http import JsonResponse, HttpResponseForbidden
|
||||||
|
|
||||||
|
def is_admin(user):
|
||||||
|
return user.is_staff or user.is_superuser
|
||||||
|
|
||||||
|
def is_supervisor(user):
|
||||||
|
return user.supervised_teams.exists() or user.assigned_projects.exists() or user.groups.filter(name='Work Logger').exists()
|
||||||
|
|
||||||
|
def is_staff_or_supervisor(user):
|
||||||
|
return is_admin(user) or is_supervisor(user)
|
||||||
|
|
||||||
# Home view for the dashboard
|
# Home view for the dashboard
|
||||||
@login_required
|
@login_required
|
||||||
def index(request):
|
def index(request):
|
||||||
total_workers = Worker.objects.filter(active=True).count()
|
user = request.user
|
||||||
total_projects = Project.objects.filter(active=True).count()
|
|
||||||
today_attendance = WorkLog.objects.filter(date=timezone.now().date()).count()
|
|
||||||
|
|
||||||
context = {
|
if is_admin(user):
|
||||||
'total_workers': total_workers,
|
# Calculate total value of unpaid work and break it down by project
|
||||||
'total_projects': total_projects,
|
unpaid_worklogs = WorkLog.objects.filter(payroll_records__isnull=True).prefetch_related('workers', 'project')
|
||||||
'today_attendance': today_attendance,
|
outstanding_payments = Decimal('0.00')
|
||||||
}
|
outstanding_by_project = {}
|
||||||
return render(request, 'core/index.html', context)
|
|
||||||
|
for wl in unpaid_worklogs:
|
||||||
|
project_name = wl.project.name
|
||||||
|
if project_name not in outstanding_by_project:
|
||||||
|
outstanding_by_project[project_name] = Decimal('0.00')
|
||||||
|
for worker in wl.workers.all():
|
||||||
|
cost = worker.daily_rate
|
||||||
|
outstanding_payments += cost
|
||||||
|
outstanding_by_project[project_name] += cost
|
||||||
|
|
||||||
|
# Include unpaid payroll adjustments in the outstanding calculations
|
||||||
|
unpaid_adjustments = PayrollAdjustment.objects.filter(payroll_record__isnull=True)
|
||||||
|
for adj in unpaid_adjustments:
|
||||||
|
outstanding_payments += adj.amount
|
||||||
|
project_name = adj.project.name if adj.project else 'General'
|
||||||
|
if project_name not in outstanding_by_project:
|
||||||
|
outstanding_by_project[project_name] = Decimal('0.00')
|
||||||
|
outstanding_by_project[project_name] += adj.amount
|
||||||
|
|
||||||
|
# Sum the total amount paid out over the last 60 days
|
||||||
|
sixty_days_ago = timezone.now().date() - timezone.timedelta(days=60)
|
||||||
|
paid_this_month = PayrollRecord.objects.filter(date__gte=sixty_days_ago).aggregate(total=Sum('amount_paid'))['total'] or Decimal('0.00')
|
||||||
|
|
||||||
|
# Tally the count and total balance of active loans
|
||||||
|
active_loans_qs = Loan.objects.filter(active=True)
|
||||||
|
active_loans_count = active_loans_qs.count()
|
||||||
|
active_loans_balance = active_loans_qs.aggregate(total=Sum('remaining_balance'))['total'] or Decimal('0.00')
|
||||||
|
|
||||||
|
start_of_week = timezone.now().date() - timezone.timedelta(days=timezone.now().date().weekday())
|
||||||
|
this_week_logs = WorkLog.objects.filter(date__gte=start_of_week).count()
|
||||||
|
|
||||||
|
recent_activity = WorkLog.objects.all().order_by('-date', '-id')[:5]
|
||||||
|
|
||||||
|
# Get all workers, projects, and teams for the Manage Resources tab
|
||||||
|
workers = Worker.objects.all().order_by('name')
|
||||||
|
projects = Project.objects.all().order_by('name')
|
||||||
|
teams = Team.objects.all().order_by('name')
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'is_admin': True,
|
||||||
|
'outstanding_payments': outstanding_payments,
|
||||||
|
'paid_this_month': paid_this_month,
|
||||||
|
'active_loans_count': active_loans_count,
|
||||||
|
'active_loans_balance': active_loans_balance,
|
||||||
|
'outstanding_by_project': outstanding_by_project,
|
||||||
|
'this_week_logs': this_week_logs,
|
||||||
|
'recent_activity': recent_activity,
|
||||||
|
'workers': workers,
|
||||||
|
'projects': projects,
|
||||||
|
'teams': teams,
|
||||||
|
}
|
||||||
|
return render(request, 'core/index.html', context)
|
||||||
|
else:
|
||||||
|
start_of_week = timezone.now().date() - timezone.timedelta(days=timezone.now().date().weekday())
|
||||||
|
this_week_logs = WorkLog.objects.filter(date__gte=start_of_week, supervisor=user).count()
|
||||||
|
recent_activity = WorkLog.objects.filter(supervisor=user).order_by('-date', '-id')[:5]
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'is_admin': False,
|
||||||
|
'this_week_logs': this_week_logs,
|
||||||
|
'recent_activity': recent_activity,
|
||||||
|
}
|
||||||
|
return render(request, 'core/index.html', context)
|
||||||
|
|
||||||
# View for logging attendance
|
# View for logging attendance
|
||||||
@login_required
|
@login_required
|
||||||
@ -29,6 +101,42 @@ def attendance_log(request):
|
|||||||
messages.success(request, 'Attendance logged successfully!')
|
messages.success(request, 'Attendance logged successfully!')
|
||||||
return redirect('home')
|
return redirect('home')
|
||||||
else:
|
else:
|
||||||
form = AttendanceLogForm(initial={'date': timezone.now().date()})
|
form = AttendanceLogForm(initial={'date': timezone.now().date(), 'supervisor': request.user})
|
||||||
|
|
||||||
return render(request, 'core/attendance_log.html', {'form': form})
|
return render(request, 'core/attendance_log.html', {'form': form})
|
||||||
|
|
||||||
|
# Work history view
|
||||||
|
@login_required
|
||||||
|
def work_history(request):
|
||||||
|
if is_admin(request.user):
|
||||||
|
logs = WorkLog.objects.all().order_by('-date', '-id')
|
||||||
|
else:
|
||||||
|
logs = WorkLog.objects.filter(supervisor=request.user).order_by('-date', '-id')
|
||||||
|
return render(request, 'core/work_history.html', {'logs': logs})
|
||||||
|
|
||||||
|
# API view to toggle resource active status
|
||||||
|
@login_required
|
||||||
|
def toggle_active(request, model_name, item_id):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return HttpResponseForbidden("Only POST requests are allowed.")
|
||||||
|
|
||||||
|
if not is_admin(request.user):
|
||||||
|
return HttpResponseForbidden("Not authorized.")
|
||||||
|
|
||||||
|
model_map = {
|
||||||
|
'worker': Worker,
|
||||||
|
'project': Project,
|
||||||
|
'team': Team
|
||||||
|
}
|
||||||
|
|
||||||
|
if model_name not in model_map:
|
||||||
|
return JsonResponse({'error': 'Invalid model'}, status=400)
|
||||||
|
|
||||||
|
model = model_map[model_name]
|
||||||
|
try:
|
||||||
|
item = model.objects.get(id=item_id)
|
||||||
|
item.active = not item.active
|
||||||
|
item.save()
|
||||||
|
return JsonResponse({'status': 'success', 'active': item.active})
|
||||||
|
except model.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'Item not found'}, status=404)
|
||||||
Loading…
x
Reference in New Issue
Block a user