dem5
This commit is contained in:
parent
60816d1fde
commit
5976b14df7
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -77,4 +77,7 @@ class BidForm(forms.ModelForm):
|
||||
user = kwargs.pop('user', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
if user:
|
||||
self.fields['truck'].queryset = Truck.objects.filter(owner=user)
|
||||
# Only allow bidding with approved trucks
|
||||
self.fields['truck'].queryset = Truck.objects.filter(owner=user, is_approved=True)
|
||||
if not self.fields['truck'].queryset.exists():
|
||||
self.fields['truck'].help_text = _("You must have an approved truck to place a bid.")
|
||||
|
||||
18
core/migrations/0003_truck_is_approved.py
Normal file
18
core/migrations/0003_truck_is_approved.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2026-01-23 10:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0002_shipment_message_truck_shipment_assigned_truck_bid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='truck',
|
||||
name='is_approved',
|
||||
field=models.BooleanField(default=False, verbose_name='Is Approved'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -32,6 +32,7 @@ class Truck(models.Model):
|
||||
registration_back = models.ImageField(_('Registration Back'), upload_to='docs/', blank=True, null=True)
|
||||
driver_license = models.ImageField(_('Driver License'), upload_to='docs/', blank=True, null=True)
|
||||
|
||||
is_approved = models.BooleanField(_('Is Approved'), default=False)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
@ -96,4 +97,4 @@ def save_user_profile(sender, instance, **kwargs):
|
||||
if hasattr(instance, 'profile'):
|
||||
instance.profile.save()
|
||||
else:
|
||||
Profile.objects.create(user=instance)
|
||||
Profile.objects.create(user=instance)
|
||||
@ -17,7 +17,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-sm text-center p-4">
|
||||
<div class="display-5 text-primary mb-2"><i class="fa-solid fa-users"></i></div>
|
||||
@ -48,6 +48,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pending Truck Approvals Section -->
|
||||
<div class="card border-0 shadow-sm mb-5">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="fw-bold mb-0">{% trans "Pending Truck Approvals" %}</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th>{% trans "Owner" %}</th>
|
||||
<th>{% trans "Truck Details" %}</th>
|
||||
<th>{% trans "Plate No" %}</th>
|
||||
<th>{% trans "Documents" %}</th>
|
||||
<th class="text-end">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for truck in pending_trucks %}
|
||||
<tr>
|
||||
<td>{{ truck.owner.username }}<br><small class="text-muted">{{ truck.owner.email }}</small></td>
|
||||
<td>
|
||||
<strong>{{ truck.truck_type }}</strong><br>
|
||||
<small>{{ truck.model }} ({{ truck.year }}) - {{ truck.color }}</small>
|
||||
</td>
|
||||
<td>{{ truck.plate_no }}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
{% if truck.truck_picture %}
|
||||
<a href="{{ truck.truck_picture.url }}" target="_blank" class="btn btn-outline-secondary">{% trans "Photo" %}</a>
|
||||
{% endif %}
|
||||
{% if truck.registration_front %}
|
||||
<a href="{{ truck.registration_front.url }}" target="_blank" class="btn btn-outline-secondary">{% trans "Reg. Front" %}</a>
|
||||
{% endif %}
|
||||
{% if truck.registration_back %}
|
||||
<a href="{{ truck.registration_back.url }}" target="_blank" class="btn btn-outline-secondary">{% trans "Reg. Back" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<a href="{% url 'approve_truck' truck.id %}" class="btn btn-success btn-sm">
|
||||
<i class="fa-solid fa-check me-1"></i> {% trans "Approve" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-4 text-muted">{% trans "No trucks awaiting approval." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<h4 class="fw-bold mb-4">{% trans "Quick Actions" %}</h4>
|
||||
<div class="row g-3">
|
||||
@ -101,4 +157,4 @@
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -1,10 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>{% trans "Truck Owner Dashboard" %}</h2>
|
||||
<div>
|
||||
<h2 class="mb-1">{% trans "Truck Owner Dashboard" %}</h2>
|
||||
<p class="text-muted">{% trans "Manage your fleet and active bids." %}</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{% url 'marketplace' %}" class="btn btn-primary me-2">
|
||||
<i class="fa-solid fa-search me-2"></i> {% trans "Find Shipments" %}
|
||||
@ -15,57 +19,92 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header bg-white">
|
||||
<h5 class="mb-0">{% trans "My Trucks" %}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if trucks %}
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for truck in trucks %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{ truck.truck_type }} - {{ truck.plate_no }}
|
||||
<span class="badge bg-secondary">{{ truck.load_capacity }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- Approved Trucks Section -->
|
||||
<h4 class="mb-3">{% trans "My Approved Trucks" %}</h4>
|
||||
<div class="row mb-5">
|
||||
{% if trucks %}
|
||||
{% for truck in trucks %}
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
{% if truck.truck_picture %}
|
||||
<img src="{{ truck.truck_picture.url }}" class="card-img-top" alt="{{ truck.truck_type }}" style="height: 200px; object-fit: cover;">
|
||||
{% else %}
|
||||
<p class="text-center py-3">{% trans "No trucks registered." %}</p>
|
||||
<div class="bg-light d-flex align-items-center justify-content-center" style="height: 200px;">
|
||||
<i class="fa-solid fa-truck fa-4x text-secondary"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ truck.truck_type }}</h5>
|
||||
<p class="card-text mb-1"><strong>{% trans "Plate No:" %}</strong> {{ truck.plate_no }}</p>
|
||||
<p class="card-text mb-1"><strong>{% trans "Model:" %}</strong> {{ truck.model }} ({{ truck.year }})</p>
|
||||
<p class="card-text mb-1"><strong>{% trans "Capacity:" %}</strong> {{ truck.load_capacity }}</p>
|
||||
</div>
|
||||
<div class="card-footer bg-white border-0">
|
||||
<span class="badge bg-success w-100 py-2">{% trans "Approved" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12 text-center py-4">
|
||||
<p class="text-muted">{% trans "No approved trucks yet." %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Pending Trucks Section -->
|
||||
{% if pending_trucks %}
|
||||
<h4 class="mb-3">{% trans "Pending Approval" %}</h4>
|
||||
<div class="row mb-5">
|
||||
{% for truck in pending_trucks %}
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100 shadow-sm border-0 opacity-75">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-muted">{{ truck.truck_type }}</h5>
|
||||
<p class="card-text mb-1"><strong>{% trans "Plate No:" %}</strong> {{ truck.plate_no }}</p>
|
||||
<p class="card-text">{% trans "Submitted on:" %} {{ truck.created_at|date }}</p>
|
||||
</div>
|
||||
<div class="card-footer bg-white border-0">
|
||||
<span class="badge bg-warning text-dark w-100 py-2">{% trans "Waiting for Admin Review" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card shadow-sm h-100">
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white">
|
||||
<h5 class="mb-0">{% trans "My Active Bids" %}</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th>{% trans "Shipment" %}</th>
|
||||
<th>{% trans "Amount" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Date" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for bid in bids %}
|
||||
<tr>
|
||||
<td>{{ bid.shipment.origin }} - {{ bid.shipment.destination }}</td>
|
||||
<td>{{ bid.amount }}</td>
|
||||
<td>${{ bid.amount }}</td>
|
||||
<td>
|
||||
<span class="badge {% if bid.status == 'PENDING' %}bg-warning{% elif bid.status == 'ACCEPTED' %}bg-success{% else %}bg-danger{% endif %}">
|
||||
<span class="badge {% if bid.status == 'PENDING' %}bg-warning text-dark{% elif bid.status == 'ACCEPTED' %}bg-success{% else %}bg-danger{% endif %}">
|
||||
{{ bid.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ bid.created_at|date }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="3" class="text-center py-3">{% trans "No bids placed." %}</td>
|
||||
<td colspan="4" class="text-center py-4">{% trans "No bids placed yet." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@ -76,4 +115,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -9,6 +9,7 @@ urlpatterns = [
|
||||
path("logout/", auth_views.LogoutView.as_view(), name="logout"),
|
||||
path("dashboard/", views.dashboard, name="dashboard"),
|
||||
path("truck/register/", views.truck_register, name="truck_register"),
|
||||
path("truck/<int:truck_id>/approve/", views.approve_truck, name="approve_truck"),
|
||||
path("shipment/post/", views.post_shipment, name="post_shipment"),
|
||||
path("marketplace/", views.marketplace, name="marketplace"),
|
||||
path("shipment/<int:shipment_id>/", views.shipment_detail, name="shipment_detail"),
|
||||
|
||||
@ -41,18 +41,22 @@ def dashboard(request):
|
||||
my_shipments = Shipment.objects.filter(shipper=request.user).order_by('-created_at')
|
||||
return render(request, 'core/shipper_dashboard.html', {'shipments': my_shipments})
|
||||
elif profile.role == 'TRUCK_OWNER':
|
||||
my_trucks = Truck.objects.filter(owner=request.user)
|
||||
approved_trucks = Truck.objects.filter(owner=request.user, is_approved=True)
|
||||
pending_trucks = Truck.objects.filter(owner=request.user, is_approved=False)
|
||||
my_bids = Bid.objects.filter(truck_owner=request.user).order_by('-created_at')
|
||||
return render(request, 'core/truck_owner_dashboard.html', {
|
||||
'trucks': my_trucks,
|
||||
'trucks': approved_trucks,
|
||||
'pending_trucks': pending_trucks,
|
||||
'bids': my_bids
|
||||
})
|
||||
elif profile.role == 'ADMIN' or request.user.is_superuser:
|
||||
pending_trucks = Truck.objects.filter(is_approved=False).order_by('-created_at')
|
||||
context = {
|
||||
'total_users': User.objects.count(),
|
||||
'total_trucks': Truck.objects.count(),
|
||||
'total_shipments': Shipment.objects.count(),
|
||||
'total_bids': Bid.objects.count(),
|
||||
'pending_trucks': pending_trucks,
|
||||
}
|
||||
return render(request, 'core/admin_dashboard.html', context)
|
||||
else:
|
||||
@ -69,8 +73,9 @@ def truck_register(request):
|
||||
if form.is_valid():
|
||||
truck = form.save(commit=False)
|
||||
truck.owner = request.user
|
||||
truck.is_approved = False # Ensure it stays false on new reg
|
||||
truck.save()
|
||||
messages.success(request, _("Truck registered successfully!"))
|
||||
messages.success(request, _("Truck registered successfully! It will be visible after admin approval."))
|
||||
return redirect('dashboard')
|
||||
else:
|
||||
messages.error(request, _("There was an error in your registration. Please check the form."))
|
||||
@ -79,6 +84,17 @@ def truck_register(request):
|
||||
|
||||
return render(request, 'core/truck_register.html', {'form': form})
|
||||
|
||||
@login_required
|
||||
def approve_truck(request, truck_id):
|
||||
if not (request.user.profile.role == 'ADMIN' or request.user.is_superuser):
|
||||
return redirect('dashboard')
|
||||
|
||||
truck = get_object_or_404(Truck, id=truck_id)
|
||||
truck.is_approved = True
|
||||
truck.save()
|
||||
messages.success(request, _("Truck approved successfully!"))
|
||||
return redirect('dashboard')
|
||||
|
||||
@login_required
|
||||
def post_shipment(request):
|
||||
if request.user.profile.role != 'SHIPPER':
|
||||
@ -113,6 +129,11 @@ def place_bid(request, shipment_id):
|
||||
if request.user.profile.role != 'TRUCK_OWNER':
|
||||
return redirect('dashboard')
|
||||
|
||||
# Optional: Only allow bidding if user has at least one approved truck
|
||||
if not Truck.objects.filter(owner=request.user, is_approved=True).exists():
|
||||
messages.warning(request, _("You must have at least one approved truck to place a bid."))
|
||||
return redirect('dashboard')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = BidForm(request.POST, user=request.user)
|
||||
if form.is_valid():
|
||||
@ -159,4 +180,4 @@ def accept_bid(request, bid_id):
|
||||
bid.shipment.save()
|
||||
|
||||
messages.success(request, _("Bid accepted! Shipment is now in progress."))
|
||||
return redirect('shipment_detail', shipment_id=bid.shipment.id)
|
||||
return redirect('shipment_detail', shipment_id=bid.shipment.id)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user