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
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 = [
path("admin/", admin.site.urls),
path("accounts/", include("django.contrib.auth.urls")),
path("", include("core.urls")),
]

Binary file not shown.

View File

@ -4,65 +4,128 @@
<head>
<meta charset="UTF-8">
<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 -->
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@700&family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Poppins:wght@500;600;700&display=swap" 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 -->
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ request.timestamp|default:'1.0' }}">
<style>
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>
<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">
<a class="navbar-brand fw-bold" href="{% url 'index' %}">FOX FITT</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<a class="navbar-brand d-flex align-items-center" href="{% url 'home' %}">
<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>
</button>
<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">
<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 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 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>
</ul>
</div>
{% endif %}
</div>
</nav>
{% if messages %}
<div class="container mt-4">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
<!-- Messages Block -->
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show shadow-sm" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
</div>
{% endif %}
<!-- Main Content -->
<main>
{% block content %}{% endblock %}
{% block content %}
{% endblock %}
</main>
<footer class="footer mt-auto">
<!-- Footer -->
<footer class="py-4 mt-auto border-top border-secondary">
<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>
</footer>
<!-- Bootstrap 5 JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}{% endblock %}
<!-- Bootstrap 5.3 JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmxc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>
</html>

View File

@ -1,47 +1,53 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Log Work | FoxFitt{% endblock %}
{% 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="col-lg-8">
<div class="card shadow">
<div class="card-header bg-primary text-white text-center py-3">
<h2 class="h4 mb-0">Log Daily Attendance</h2>
</div>
<div class="card-body p-4">
<div class="card shadow-sm border-0" style="border-radius: 12px;">
<div class="card-body p-5">
<form method="POST" id="attendanceForm">
{% csrf_token %}
<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 }}
</div>
<div class="row g-3 mb-4">
<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 }}
</div>
<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 }}
</div>
</div>
<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 }}
</div>
<div class="mb-4">
<label class="form-label fw-bold d-block mb-3">Workers Present</label>
<div class="worker-selection border rounded p-3 bg-light" style="max-height: 300px; overflow-y: auto;">
<label class="form-label d-block mb-3" style="font-weight: 600;">Workers Present</label>
<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">
{% for worker in form.workers %}
<div class="col-md-6 mb-2">
<div class="form-check">
{{ 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 }}
</label>
</div>
@ -53,30 +59,23 @@
<div class="row g-3 mb-4">
<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 }}
</div>
</div>
<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 }}
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg shadow">Save Attendance Log</button>
<div class="d-grid mt-5">
<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>
</form>
</div>
</div>
</div>
</div>
</section>
{% 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 %}
</div>
{% endblock %}

View File

@ -1,66 +1,86 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Dashboard | FoxFitt{% endblock %}
{% block content %}
<section class="hero-section text-center">
<div class="container">
<h1 class="display-4 mb-4">Construction Payroll & Attendance</h1>
<p class="lead mb-5">Efficiently track workers, projects, and payroll for Fox Fitt Construction.</p>
<div class="d-flex justify-content-center gap-3">
<a href="{% url 'attendance_log' %}" class="btn btn-primary btn-lg px-5 py-3">Log Attendance</a>
<a href="/admin/core/worker/" class="btn btn-outline-light btn-lg px-5 py-3">Manage Workers</a>
</div>
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0 text-gray-800">Welcome back, {{ user.first_name|default:user.username }}!</h1>
<a href="{% url 'attendance_log' %}" class="btn text-white shadow-sm" style="background-color: #10b981;">
<i class="fas fa-plus fa-sm text-white-50 me-1"></i> Log Work
</a>
</div>
</section>
<section class="container py-5">
<div class="row g-4 text-center">
<div class="col-md-4">
<div class="card h-100 p-4">
<!-- Stats Row -->
<div class="row g-4 mb-4">
<!-- Active Workers 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 #10b981 !important;">
<div class="card-body">
<h3 class="h1 text-primary mb-3">{{ total_workers|default:"0" }}</h3>
<h5 class="card-title">Active Workers</h5>
<p class="card-text text-muted">Field workers registered in the system.</p>
<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: #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 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">
<div class="row align-items-center">
<div class="col-lg-6">
<h2 class="mb-4">Streamlined Attendance Tracking</h2>
<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>
<ul class="list-unstyled mb-4">
<li class="mb-2">✅ Real-time attendance logging</li>
<li class="mb-2">✅ Integrated overtime calculations</li>
<li class="mb-2">✅ Instant team selection</li>
<li class="mb-2">✅ Offline-first field record management</li>
</ul>
<a href="{% url 'attendance_log' %}" class="btn btn-primary px-4 py-2">Get Started</a>
<!-- Projects 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 #3b82f6 !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: #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>
<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>
</section>
{% endblock %}
<!-- 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 %}

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