This commit is contained in:
Flatlogic Bot 2026-02-22 12:55:15 +00:00
parent d10151cf40
commit 28c36a1e12
13 changed files with 236 additions and 113 deletions

View File

@ -182,3 +182,6 @@ if EMAIL_USE_SSL:
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'login'

View File

@ -5,6 +5,7 @@ from django.conf.urls.static import static
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("accounts/", include("django.contrib.auth.urls")),
path("", include("core.urls")), path("", include("core.urls")),
] ]

Binary file not shown.

View File

@ -4,65 +4,128 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Fox Fitt Construction{% endblock %}</title> <title>{% block title %}FoxFitt{% endblock %}</title>
<!-- Bootstrap 5.3 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<!-- Google Fonts --> <!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@700&family=Open+Sans:wght@400;600&display=swap" rel="stylesheet"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Bootstrap 5 CSS --> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Poppins:wght@500;600;700&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Font Awesome 6 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<!-- Custom CSS --> <!-- Custom CSS -->
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}"> <link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ request.timestamp|default:'1.0' }}">
<style>
{% block head %}{% endblock %} body { font-family: 'Inter', sans-serif; background-color: #f8f9fa; display: flex; flex-direction: column; min-height: 100vh; }
h1, h2, h3, h4, h5, h6, .navbar-brand { font-family: 'Poppins', sans-serif; }
.navbar { background-color: #0f172a !important; }
.navbar-brand-fox { color: #10b981; font-weight: 700; }
.navbar-brand-fitt { color: #ffffff; font-weight: 700; }
.nav-link { font-weight: 500; }
.dropdown-menu { border-radius: 8px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
main { flex-grow: 1; }
footer { background-color: #0f172a; color: #cbd5e1; }
</style>
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-lg navbar-dark sticky-top"> <nav class="navbar navbar-expand-lg navbar-dark sticky-top shadow-sm">
<div class="container"> <div class="container">
<a class="navbar-brand fw-bold" href="{% url 'index' %}">FOX FITT</a> <a class="navbar-brand d-flex align-items-center" href="{% url 'home' %}">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> <span class="navbar-brand-fox">Fox</span>
<span class="navbar-brand-fitt">Fitt</span>
</a>
{% if user.is_authenticated %}
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'index' %}">Dashboard</a> <a class="nav-link {% if request.resolver_match.url_name == 'home' %}active{% endif %}" href="{% url 'home' %}">
<i class="fas fa-home me-1"></i> Dashboard
</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'attendance_log' %}">Log Attendance</a> <a class="nav-link {% if request.resolver_match.url_name == 'attendance_log' %}active{% endif %}" href="{% url 'attendance_log' %}">
<i class="fas fa-clipboard-list me-1"></i> Log Work
</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/admin/">Admin Portal</a> <a class="nav-link" href="#">
<i class="fas fa-clock me-1"></i> History
</a>
</li>
{% if user.is_staff %}
<li class="nav-item">
<a class="nav-link" href="#">
<i class="fas fa-wallet me-1"></i> Payroll
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="#">
<i class="fas fa-receipt me-1"></i> Receipts
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle d-flex align-items-center" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-user-circle me-2 fs-5"></i>
{{ user.username }}
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
{% if user.is_staff %}
<li>
<a class="dropdown-item" href="{% url 'admin:index' %}">
<i class="fas fa-cog me-2 text-secondary"></i> Admin Panel
</a>
</li>
<li><hr class="dropdown-divider"></li>
{% endif %}
<li>
<form method="post" action="{% url 'logout' %}" class="px-3 py-2">
{% csrf_token %}
<button type="submit" class="btn btn-danger w-100 btn-sm">
<i class="fas fa-sign-out-alt me-1"></i> Logout
</button>
</form>
</li>
</ul>
</li> </li>
</ul> </ul>
</div> </div>
{% endif %}
</div> </div>
</nav> </nav>
{% if messages %}
<div class="container mt-4"> <div class="container mt-4">
{% for message in messages %} <!-- Messages Block -->
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert"> {% if messages %}
{{ message }} {% for message in messages %}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> <div class="alert alert-{{ message.tags }} alert-dismissible fade show shadow-sm" role="alert">
</div> {{ message }}
{% endfor %} <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
</div> </div>
{% endif %}
<!-- Main Content -->
<main> <main>
{% block content %}{% endblock %} {% block content %}
{% endblock %}
</main> </main>
<footer class="footer mt-auto"> <!-- Footer -->
<footer class="py-4 mt-auto border-top border-secondary">
<div class="container text-center"> <div class="container text-center">
<p>&copy; 2026 Fox Fitt Construction - Payroll Management</p> <p class="mb-0 small">&copy; {% now "Y" %} FoxFitt Construction. All rights reserved.</p>
</div> </div>
</footer> </footer>
<!-- Bootstrap 5 JS Bundle --> <!-- Bootstrap 5.3 JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmxc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
{% block scripts %}{% endblock %}
</body> </body>
</html> </html>

View File

@ -1,47 +1,53 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static %} {% load static %}
{% block title %}Log Work | FoxFitt{% endblock %}
{% block content %} {% block content %}
<section class="container py-5"> <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;">Log Daily Attendance</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="row justify-content-center"> <div class="row justify-content-center">
<div class="col-lg-8"> <div class="col-lg-8">
<div class="card shadow"> <div class="card shadow-sm border-0" style="border-radius: 12px;">
<div class="card-header bg-primary text-white text-center py-3"> <div class="card-body p-5">
<h2 class="h4 mb-0">Log Daily Attendance</h2>
</div>
<div class="card-body p-4">
<form method="POST" id="attendanceForm"> <form method="POST" id="attendanceForm">
{% csrf_token %} {% csrf_token %}
<div class="mb-4"> <div class="mb-4">
<label class="form-label fw-bold">{{ form.date.label }}</label> <label class="form-label" style="font-weight: 600;">{{ form.date.label }}</label>
{{ form.date }} {{ form.date }}
</div> </div>
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-bold">{{ form.project.label }}</label> <label class="form-label" style="font-weight: 600;">{{ form.project.label }}</label>
{{ form.project }} {{ form.project }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-bold">{{ form.supervisor.label }}</label> <label class="form-label" style="font-weight: 600;">{{ form.supervisor.label }}</label>
{{ form.supervisor }} {{ form.supervisor }}
</div> </div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="form-label fw-bold">{{ form.team.label }} (Optional - selection will auto-check workers)</label> <label class="form-label" style="font-weight: 600;">{{ form.team.label }} <span class="text-muted fw-normal">(Optional)</span></label>
{{ form.team }} {{ form.team }}
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="form-label fw-bold d-block mb-3">Workers Present</label> <label class="form-label d-block mb-3" style="font-weight: 600;">Workers Present</label>
<div class="worker-selection border rounded p-3 bg-light" style="max-height: 300px; overflow-y: auto;"> <div class="worker-selection border rounded p-3" style="max-height: 300px; overflow-y: auto; background-color: #f8fafc; border-color: #e2e8f0 !important;">
<div class="row"> <div class="row">
{% for worker in form.workers %} {% for worker in form.workers %}
<div class="col-md-6 mb-2"> <div class="col-md-6 mb-2">
<div class="form-check"> <div class="form-check">
{{ worker.tag }} {{ worker.tag }}
<label class="form-check-label" for="{{ worker.id_for_label }}"> <label class="form-check-label ms-1" for="{{ worker.id_for_label }}">
{{ worker.choice_label }} {{ worker.choice_label }}
</label> </label>
</div> </div>
@ -53,30 +59,23 @@
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-bold">{{ form.overtime_amount.label }}</label> <label class="form-label" style="font-weight: 600;">{{ form.overtime_amount.label }}</label>
{{ form.overtime_amount }} {{ form.overtime_amount }}
</div> </div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="form-label fw-bold">{{ form.notes.label }}</label> <label class="form-label" style="font-weight: 600;">{{ form.notes.label }}</label>
{{ form.notes }} {{ form.notes }}
</div> </div>
<div class="d-grid"> <div class="d-grid mt-5">
<button type="submit" class="btn btn-primary btn-lg shadow">Save Attendance Log</button> <button type="submit" class="btn btn-lg text-white shadow-sm" style="background-color: #10b981; border: none; font-weight: 600; border-radius: 8px;">Save Attendance Log</button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </div>
{% endblock %}
{% block scripts %}
<script>
// Simple team selection auto-checking logic could be added here
// For this MVP, we'll keep it as a standard multi-select for workers.
</script>
{% endblock %} {% endblock %}

View File

@ -1,66 +1,86 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static %} {% load static %}
{% block title %}Dashboard | FoxFitt{% endblock %}
{% block content %} {% block content %}
<section class="hero-section text-center"> <div class="container py-4">
<div class="container"> <div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="display-4 mb-4">Construction Payroll & Attendance</h1> <h1 class="h3 mb-0 text-gray-800">Welcome back, {{ user.first_name|default:user.username }}!</h1>
<p class="lead mb-5">Efficiently track workers, projects, and payroll for Fox Fitt Construction.</p> <a href="{% url 'attendance_log' %}" class="btn text-white shadow-sm" style="background-color: #10b981;">
<div class="d-flex justify-content-center gap-3"> <i class="fas fa-plus fa-sm text-white-50 me-1"></i> Log Work
<a href="{% url 'attendance_log' %}" class="btn btn-primary btn-lg px-5 py-3">Log Attendance</a> </a>
<a href="/admin/core/worker/" class="btn btn-outline-light btn-lg px-5 py-3">Manage Workers</a>
</div>
</div> </div>
</section>
<section class="container py-5"> <!-- Stats Row -->
<div class="row g-4 text-center"> <div class="row g-4 mb-4">
<div class="col-md-4"> <!-- Active Workers Card -->
<div class="card h-100 p-4"> <div class="col-xl-4 col-md-6">
<div class="card border-0 shadow-sm h-100 py-2" style="border-left: 4px solid #10b981 !important;">
<div class="card-body"> <div class="card-body">
<h3 class="h1 text-primary mb-3">{{ total_workers|default:"0" }}</h3> <div class="row no-gutters align-items-center">
<h5 class="card-title">Active Workers</h5> <div class="col me-2">
<p class="card-text text-muted">Field workers registered in the system.</p> <div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #10b981;">
Active Workers</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ total_workers|default:"0" }}</div>
</div>
<div class="col-auto">
<i class="fas fa-users fa-2x text-secondary opacity-50"></i>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-4">
<div class="card h-100 p-4">
<div class="card-body">
<h3 class="h1 text-primary mb-3">{{ total_projects|default:"0" }}</h3>
<h5 class="card-title">Projects</h5>
<p class="card-text text-muted">Ongoing solar farm foundation projects.</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 p-4">
<div class="card-body">
<h3 class="h1 text-primary mb-3">{{ today_attendance|default:"0" }}</h3>
<h5 class="card-title">Today's Attendance</h5>
<p class="card-text text-muted">Workers logged for today across all sites.</p>
</div>
</div>
</div>
</div>
</section>
<section class="container py-5 bg-white rounded-3 shadow-sm mb-5"> <!-- Projects Card -->
<div class="row align-items-center"> <div class="col-xl-4 col-md-6">
<div class="col-lg-6"> <div class="card border-0 shadow-sm h-100 py-2" style="border-left: 4px solid #3b82f6 !important;">
<h2 class="mb-4">Streamlined Attendance Tracking</h2> <div class="card-body">
<p class="text-muted mb-4">Supervisors can now quickly log attendance directly from their mobile devices while on-site. Select a whole team with one click or pick individual workers for each project.</p> <div class="row no-gutters align-items-center">
<ul class="list-unstyled mb-4"> <div class="col me-2">
<li class="mb-2">✅ Real-time attendance logging</li> <div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #3b82f6;">
<li class="mb-2">✅ Integrated overtime calculations</li> Active Projects</div>
<li class="mb-2">✅ Instant team selection</li> <div class="h5 mb-0 font-weight-bold text-gray-800">{{ total_projects|default:"0" }}</div>
<li class="mb-2">✅ Offline-first field record management</li> </div>
</ul> <div class="col-auto">
<a href="{% url 'attendance_log' %}" class="btn btn-primary px-4 py-2">Get Started</a> <i class="fas fa-hard-hat fa-2x text-secondary opacity-50"></i>
</div>
</div>
</div>
</div>
</div> </div>
<div class="col-lg-6">
<img src="https://images.pexels.com/photos/159306/construction-site-build-construction-worker-159306.jpeg?auto=compress&cs=tinysrgb&w=800" alt="Construction Worker" class="img-fluid rounded-3 shadow"> <!-- 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="row no-gutters align-items-center">
<div class="col me-2">
<div class="text-xs font-weight-bold text-uppercase mb-1" style="color: #f59e0b;">
Today's Logs</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ today_attendance|default:"0" }}</div>
</div>
<div class="col-auto">
<i class="fas fa-clipboard-check fa-2x text-secondary opacity-50"></i>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</section>
<!-- Content Row -->
<div class="row">
<div class="col-lg-12 mb-4">
<div class="card shadow-sm border-0 mb-4">
<div class="card-header py-3 bg-white d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold" style="color: #0f172a;">Recent Activity</h6>
</div>
<div class="card-body">
<p class="text-muted mb-0">No recent activity to show yet. Start by logging today's attendance!</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block content %}
<div class="container d-flex justify-content-center align-items-center min-vh-100">
<div class="card shadow-sm" style="width: 100%; max-width: 400px; border-radius: 12px; border: none;">
<div class="card-body p-5">
<h2 class="text-center mb-4" style="font-family: 'Poppins', sans-serif; font-weight: 700;">
<span style="color: #10b981;">Fox</span>Fitt
</h2>
{% if form.errors %}
<div class="alert alert-danger" role="alert">
Your username and password didn't match. Please try again.
</div>
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<div class="mb-3">
<label for="id_username" class="form-label" style="font-family: 'Inter', sans-serif; font-weight: 500;">Username</label>
<input type="text" name="username" class="form-control form-control-lg" id="id_username" required autofocus style="border-radius: 8px;">
</div>
<div class="mb-4">
<label for="id_password" class="form-label" style="font-family: 'Inter', sans-serif; font-weight: 500;">Password</label>
<input type="password" name="password" class="form-control form-control-lg" id="id_password" required style="border-radius: 8px;">
</div>
<button type="submit" class="btn btn-lg w-100 text-white" style="background-color: #10b981; border: none; border-radius: 8px; font-weight: 600;">Login</button>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -2,6 +2,6 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('', views.index, name='index'), path('', views.index, name='home'),
path('attendance/log/', views.attendance_log, name='attendance_log'), path('attendance/log/', views.attendance_log, name='attendance_log'),
] ]

View File

@ -3,7 +3,10 @@ from django.utils import timezone
from .models import Worker, Project, WorkLog, Team from .models import Worker, Project, WorkLog, Team
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
# Home view for the dashboard
@login_required
def index(request): def index(request):
total_workers = Worker.objects.filter(active=True).count() total_workers = Worker.objects.filter(active=True).count()
total_projects = Project.objects.filter(active=True).count() total_projects = Project.objects.filter(active=True).count()
@ -16,13 +19,15 @@ def index(request):
} }
return render(request, 'core/index.html', context) return render(request, 'core/index.html', context)
# View for logging attendance
@login_required
def attendance_log(request): def attendance_log(request):
if request.method == 'POST': if request.method == 'POST':
form = AttendanceLogForm(request.POST) form = AttendanceLogForm(request.POST)
if form.is_valid(): if form.is_valid():
form.save() form.save()
messages.success(request, 'Attendance logged successfully!') messages.success(request, 'Attendance logged successfully!')
return redirect('index') return redirect('home')
else: else:
form = AttendanceLogForm(initial={'date': timezone.now().date()}) form = AttendanceLogForm(initial={'date': timezone.now().date()})