This commit is contained in:
Flatlogic Bot 2026-01-23 13:50:58 +00:00
parent ef5454da33
commit 80485899cb
11 changed files with 457 additions and 279 deletions

View File

@ -115,4 +115,13 @@ class BidForm(forms.ModelForm):
# 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.")
self.fields['truck'].help_text = _("You must have an approved truck to place a bid.")
class ShipperOfferForm(forms.Form):
description = forms.CharField(label=_('Goods Description'), widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3}))
weight = forms.CharField(label=_('Weight/Volume'), widget=forms.TextInput(attrs={'class': 'form-control'}))
origin = forms.CharField(label=_('Origin'), widget=forms.TextInput(attrs={'class': 'form-control'}))
destination = forms.CharField(label=_('Destination'), widget=forms.TextInput(attrs={'class': 'form-control'}))
delivery_date = forms.DateField(label=_('Requested Delivery Date'), widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}))
amount = forms.DecimalField(label=_('Offer Amount'), max_digits=10, decimal_places=2, widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}))
comments = forms.CharField(label=_('Comments'), required=False, widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 2}))

View File

@ -1,39 +1,50 @@
{% extends "base.html" %}
{% load i18n %}
{% load static %}
{% block content %}
<div class="container py-5">
<h2 class="mb-4">{% trans "Shipment Marketplace" %}</h2>
<h2 class="mb-4">{% trans "Truck Marketplace" %}</h2>
<p class="text-muted mb-4">{% trans "Browse available trucks and send your shipping offers directly to truck owners." %}</p>
<div class="row">
{% for shipment in shipments %}
<div class="col-md-6 mb-4">
<div class="card shadow-sm h-100 border-0">
{% for truck in trucks %}
<div class="col-md-6 col-lg-4 mb-4">
<div class="card shadow-sm h-100 border-0 overflow-hidden">
{% if truck.truck_picture %}
<img src="{{ truck.truck_picture.url }}" class="card-img-top" alt="{{ truck.display_truck_type }}" style="height: 200px; object-fit: cover;">
{% else %}
<div class="bg-light d-flex align-items-center justify-content-center" style="height: 200px;">
<i class="fa-solid fa-truck fa-4x text-muted opacity-25"></i>
</div>
{% endif %}
<div class="card-body">
<div class="d-flex justify-content-between mb-2">
<span class="badge bg-primary">{% trans "Open for Bids" %}</span>
<small class="text-muted">{{ shipment.created_at|timesince }} {% trans "ago" %}</small>
<span class="badge bg-success">{% trans "Available" %}</span>
<small class="text-muted">{% trans "Plate" %}: {{ truck.plate_no }}</small>
</div>
<h5 class="card-title">{{ shipment.origin }} <i class="fa-solid fa-arrow-right mx-2"></i> {{ shipment.destination }}</h5>
<p class="card-text text-muted">{{ shipment.description|truncatechars:100 }}</p>
<div class="d-flex gap-4 mb-3">
<div>
<small class="text-muted d-block">{% trans "Weight" %}</small>
<strong>{{ shipment.weight }}</strong>
</div>
<div>
<small class="text-muted d-block">{% trans "Delivery Date" %}</small>
<strong>{{ shipment.delivery_date }}</strong>
</div>
<h5 class="card-title">{{ truck.display_truck_type }} - {{ truck.display_model }}</h5>
<div class="mb-3">
<span class="badge bg-light text-dark">{% trans "Year" %}: {{ truck.year }}</span>
<span class="badge bg-light text-dark">{% trans "Capacity" %}: {{ truck.display_load_capacity }}</span>
</div>
<a href="{% url 'place_bid' shipment.id %}" class="btn btn-outline-primary w-100">{% trans "Place an Offer" %}</a>
<p class="card-text">
<small class="text-muted d-block">{% trans "Owner" %}: {{ truck.owner.username }}</small>
</p>
<a href="{% url 'place_bid' truck.id %}" class="btn btn-primary w-100">
<i class="fa-solid fa-paper-plane me-2"></i>{% trans "Send Shipping Offer" %}
</a>
</div>
</div>
</div>
{% empty %}
<div class="col-12 text-center py-5">
<p class="lead text-muted">{% trans "No shipments available at the moment." %}</p>
<div class="py-5">
<i class="fa-solid fa-truck-slash fa-4x text-muted mb-3"></i>
<p class="lead text-muted">{% trans "No approved trucks are currently available." %}</p>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -4,54 +4,94 @@
{% block content %}
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="col-md-8 col-lg-6">
<div class="card shadow-lg border-0">
<div class="card-body p-5">
<h2 class="mb-4">{% trans "Place an Offer" %}</h2>
<div class="alert alert-info mb-4">
<strong>{% trans "Shipment:" %}</strong> {{ shipment.origin }} to {{ shipment.destination }}<br>
<strong>{% trans "Goods:" %}</strong> {{ shipment.description }}
<h2 class="mb-4 text-center">{% trans "Send Shipping Offer" %}</h2>
<div class="card bg-light border-0 mb-4">
<div class="card-body">
<h6 class="text-primary mb-2">{% trans "Target Truck" %}</h6>
<div class="d-flex align-items-center">
{% if truck.truck_picture %}
<img src="{{ truck.truck_picture.url }}" class="rounded me-3" style="width: 60px; height: 60px; object-fit: cover;">
{% else %}
<div class="bg-secondary bg-opacity-10 rounded me-3 d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
<i class="fa-solid fa-truck text-muted"></i>
</div>
{% endif %}
<div>
<div class="fw-bold">{{ truck.display_truck_type }} - {{ truck.display_model }}</div>
<small class="text-muted">{% trans "Owner" %}: {{ truck.owner.username }} | {% trans "Plate" %}: {{ truck.plate_no }}</small>
</div>
</div>
</div>
</div>
{% if form.errors %}
<div class="alert alert-danger">
{% trans "Please correct the errors below." %}
{{ form.non_field_errors }}
</div>
{% endif %}
{% if form.fields.truck.queryset.exists %}
<form method="post">
<form method="post" novalidate>
{% csrf_token %}
<h6 class="border-bottom pb-2 mb-3">{% trans "Shipment Details" %}</h6>
<div class="mb-3">
<label class="form-label">{% trans "Select Truck" %}</label>
{{ form.truck }}
{{ form.truck.errors }}
<label class="form-label">{{ form.description.label }}</label>
{{ form.description }}
{{ form.description.errors }}
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">{{ form.origin.label }}</label>
{{ form.origin }}
{{ form.origin.errors }}
</div>
<div class="col-md-6">
<label class="form-label">{{ form.destination.label }}</label>
{{ form.destination }}
{{ form.destination.errors }}
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">{{ form.weight.label }}</label>
{{ form.weight }}
{{ form.weight.errors }}
</div>
<div class="col-md-6">
<label class="form-label">{{ form.delivery_date.label }}</label>
{{ form.delivery_date }}
{{ form.delivery_date.errors }}
</div>
</div>
<h6 class="border-bottom pb-2 mb-3 mt-4">{% trans "Pricing & Terms" %}</h6>
<div class="mb-3">
<label class="form-label">{% trans "Your Offer Amount" %}</label>
<label class="form-label">{{ form.amount.label }}</label>
<div class="input-group">
<span class="input-group-text">$</span>
{{ form.amount }}
</div>
{{ form.amount.errors }}
</div>
<div class="mb-3">
<label class="form-label">{% trans "Comments/Conditions" %}</label>
<div class="mb-4">
<label class="form-label">{{ form.comments.label }}</label>
{{ form.comments }}
{{ form.comments.errors }}
</div>
<button type="submit" class="btn btn-primary w-100 py-3">{% trans "Submit Offer" %}</button>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary btn-lg py-3">
<i class="fa-solid fa-paper-plane me-2"></i>{% trans "Send Offer to Truck Owner" %}
</button>
<a href="{% url 'marketplace' %}" class="btn btn-link text-muted">{% trans "Cancel" %}</a>
</div>
</form>
{% else %}
<div class="text-center py-4">
<p class="text-danger">{% trans "You must register a truck before placing a bid." %}</p>
<a href="{% url 'truck_register' %}" class="btn btn-success">{% trans "Register Truck Now" %}</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -14,50 +14,61 @@
</span>
</div>
<hr>
<h5>{% trans "Details" %}</h5>
<p>{{ shipment.description }}</p>
<h5>{% trans "Shipment Details" %}</h5>
<p class="lead">{{ shipment.description }}</p>
<div class="row">
<div class="col-6 mb-3">
<small class="text-muted d-block">{% trans "Weight" %}</small>
<strong>{{ shipment.weight }}</strong>
</div>
<div class="col-6 mb-3">
<small class="text-muted d-block">{% trans "Delivery Date" %}</small>
<small class="text-muted d-block">{% trans "Requested Delivery Date" %}</small>
<strong>{{ shipment.delivery_date }}</strong>
</div>
</div>
</div>
</div>
{% if user == shipment.shipper and shipment.status == 'OPEN' %}
<div class="card shadow-sm">
<!-- Offers related to this shipment -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-white">
<h5 class="mb-0">{% trans "Received Bids" %}</h5>
<h5 class="mb-0">{% trans "Offer Status" %}</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>{% trans "Truck Owner" %}</th>
<th>{% trans "Truck" %}</th>
<th>{% trans "Amount" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for bid in bids %}
<tr>
<td>{{ bid.truck_owner.username }}</td>
<td>{{ bid.truck.display_truck_type }}</td>
<td>{{ bid.truck.display_truck_type }} ({{ bid.truck.plate_no }})</td>
<td>${{ bid.amount }}</td>
<td>
<a href="{% url 'accept_bid' bid.id %}" class="btn btn-sm btn-success">{% trans "Accept" %}</a>
<span class="badge {% if bid.status == 'PENDING' %}bg-info{% elif bid.status == 'ACCEPTED' %}bg-success{% else %}bg-secondary{% endif %}">
{{ bid.get_status_display }}
</span>
</td>
<td>
{% if bid.status == 'PENDING' and user == bid.truck_owner %}
<div class="btn-group btn-group-sm">
<a href="{% url 'accept_bid' bid.id %}" class="btn btn-success">{% trans "Accept" %}</a>
<a href="{% url 'reject_bid' bid.id %}" class="btn btn-outline-danger">{% trans "Reject" %}</a>
</div>
{% else %}
<span class="text-muted small">{% trans "No action available" %}</span>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center py-4 text-muted">{% trans "No bids received yet." %}</td>
<td colspan="4" class="text-center py-4 text-muted">{% trans "No offers for this shipment." %}</td>
</tr>
{% endfor %}
</tbody>
@ -65,37 +76,49 @@
</div>
</div>
</div>
{% endif %}
{% if shipment.status == 'IN_PROGRESS' %}
<div class="alert alert-success d-flex align-items-center">
<i class="fa-solid fa-truck-moving fa-2x me-3"></i>
<div class="alert alert-success d-flex align-items-center p-4">
<i class="fa-solid fa-truck-moving fa-3x me-4 text-success opacity-50"></i>
<div>
<strong>{% trans "Shipment in progress!" %}</strong><br>
{% trans "Assigned Truck:" %} {{ shipment.assigned_truck.display_truck_type }} ({{ shipment.assigned_truck.plate_no }})
<h5 class="alert-heading">{% trans "Shipment is IN PROGRESS" %}</h5>
<p class="mb-0">{% trans "Assigned Truck:" %} <strong>{{ shipment.assigned_truck.display_truck_type }} ({{ shipment.assigned_truck.plate_no }})</strong></p>
</div>
</div>
<div class="mt-4">
<a href="https://wa.me/{{ shipment.assigned_truck.owner.profile.phone_number }}" class="btn btn-success btn-lg">
<i class="fa-brands fa-whatsapp me-2"></i> {% trans "Contact Driver on WhatsApp" %}
</a>
</div>
{% endif %}
</div>
<div class="col-md-4">
<div class="card shadow-sm">
<div class="card shadow-sm mb-4">
<div class="card-body">
<h5>{% trans "Contact Information" %}</h5>
<p>
<strong>{% trans "Shipper:" %}</strong> {{ shipment.shipper.username }}<br>
{% if shipment.status == 'IN_PROGRESS' %}
<strong>{% trans "Phone:" %}</strong> {{ shipment.shipper.profile.phone_number }}
{% endif %}
</p>
<h5>{% trans "Stakeholders" %}</h5>
<div class="mb-3">
<small class="text-muted d-block">{% trans "Shipper" %}</small>
<strong>{{ shipment.shipper.username }}</strong>
</div>
{% if shipment.assigned_truck %}
<div class="mb-3">
<small class="text-muted d-block">{% trans "Truck Owner" %}</small>
<strong>{{ shipment.assigned_truck.owner.username }}</strong>
</div>
{% endif %}
</div>
</div>
{% if shipment.status == 'IN_PROGRESS' %}
<div class="d-grid gap-2">
{% if user == shipment.shipper %}
<a href="https://wa.me/{{ shipment.assigned_truck.owner.profile.full_phone_number }}" target="_blank" class="btn btn-success btn-lg">
<i class="fa-brands fa-whatsapp me-2"></i> {% trans "Message Driver" %}
</a>
{% elif user == shipment.assigned_truck.owner %}
<a href="https://wa.me/{{ shipment.shipper.profile.full_phone_number }}" target="_blank" class="btn btn-success btn-lg">
<i class="fa-brands fa-whatsapp me-2"></i> {% trans "Message Shipper" %}
</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -4,58 +4,126 @@
{% block content %}
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>{% trans "Shipper Dashboard" %}</h2>
<a href="{% url 'post_shipment' %}" class="btn btn-success">
<i class="fa-solid fa-plus me-2"></i> {% trans "Post New Shipment" %}
<div>
<h2 class="mb-1">{% trans "Shipper Dashboard" %}</h2>
<p class="text-muted">{% trans "Manage your shipping offers and active shipments." %}</p>
</div>
<a href="{% url 'marketplace' %}" class="btn btn-primary btn-lg">
<i class="fa-solid fa-search me-2"></i> {% trans "Browse Trucks" %}
</a>
</div>
<div class="row">
<div class="col-md-12">
<div class="card shadow-sm">
<div class="card-header bg-white">
<h5 class="mb-0">{% trans "My Shipments" %}</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>{% trans "Description" %}</th>
<th>{% trans "Route" %}</th>
<th>{% trans "Delivery Date" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Bids" %}</th>
<th>{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for shipment in shipments %}
<tr>
<td>{{ shipment.description|truncatechars:30 }}</td>
<td>{{ shipment.origin }} <i class="fa-solid fa-arrow-right mx-1"></i> {{ shipment.destination }}</td>
<td>{{ shipment.delivery_date }}</td>
<td>
<span class="badge {% if shipment.status == 'OPEN' %}bg-primary{% elif shipment.status == 'IN_PROGRESS' %}bg-warning{% else %}bg-success{% endif %}">
{{ shipment.get_status_display }}
</span>
</td>
<td>{{ shipment.bids.count }}</td>
<td>
<a href="{% url 'shipment_detail' shipment.id %}" class="btn btn-sm btn-outline-primary">{% trans "View Details" %}</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center py-4">{% trans "No shipments posted yet." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Active Offers -->
<div class="card shadow-sm border-0 mb-5">
<div class="card-header bg-white py-3">
<h5 class="mb-0 text-primary">{% trans "My Shipping Offers" %}</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>{% trans "Truck" %}</th>
<th>{% trans "Route" %}</th>
<th>{% trans "Amount" %}</th>
<th>{% trans "Date Sent" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for bid in bids %}
<tr>
<td>
<div><strong>{{ bid.truck.display_truck_type }}</strong></div>
<small class="text-muted">{{ bid.truck.plate_no }}</small>
</td>
<td>
<span>{{ bid.shipment.origin }}</span>
<i class="fa-solid fa-arrow-right mx-1 text-muted small"></i>
<span>{{ bid.shipment.destination }}</span>
</td>
<td class="fw-bold">${{ bid.amount }}</td>
<td>{{ bid.created_at|date:"d M Y" }}</td>
<td>
{% if bid.status == 'PENDING' %}
<span class="badge bg-info">{% trans "Pending" %}</span>
{% elif bid.status == 'ACCEPTED' %}
<span class="badge bg-success">{% trans "Accepted" %}</span>
{% else %}
<span class="badge bg-danger">{% trans "Rejected" %}</span>
{% endif %}
</td>
<td>
<a href="{% url 'shipment_detail' bid.shipment.id %}" class="btn btn-sm btn-outline-secondary">{% trans "Details" %}</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center py-5 text-muted">
<i class="fa-solid fa-paper-plane fa-2x mb-3 opacity-25"></i>
<p>{% trans "You haven't sent any offers yet." %}</p>
<a href="{% url 'marketplace' %}" class="btn btn-sm btn-primary">{% trans "Find a Truck" %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Active Shipments -->
<div class="card shadow-sm border-0">
<div class="card-header bg-white py-3">
<h5 class="mb-0">{% trans "Active Shipments" %}</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>{% trans "Description" %}</th>
<th>{% trans "Route" %}</th>
<th>{% trans "Delivery Date" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Assigned Truck" %}</th>
<th>{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for shipment in shipments %}
{% if shipment.status != 'OPEN' or not shipment.bids.exists %}
<tr>
<td>{{ shipment.description|truncatechars:30 }}</td>
<td>{{ shipment.origin }} <i class="fa-solid fa-arrow-right mx-1 text-muted small"></i> {{ shipment.destination }}</td>
<td>{{ shipment.delivery_date }}</td>
<td>
<span class="badge {% if shipment.status == 'IN_PROGRESS' %}bg-warning{% elif shipment.status == 'COMPLETED' %}bg-success{% else %}bg-secondary{% endif %}">
{{ shipment.get_status_display }}
</span>
</td>
<td>
{% if shipment.assigned_truck %}
{{ shipment.assigned_truck.plate_no }}
{% else %}
<span class="text-muted italic">{% trans "None" %}</span>
{% endif %}
</td>
<td>
<a href="{% url 'shipment_detail' shipment.id %}" class="btn btn-sm btn-outline-primary">{% trans "Track" %}</a>
</td>
</tr>
{% endif %}
{% empty %}
<tr>
<td colspan="6" class="text-center py-4 text-muted">{% trans "No active shipments." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -7,121 +7,119 @@
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="mb-1">{% trans "Truck Owner Dashboard" %}</h2>
<p class="text-muted">{% trans "Manage your fleet and active bids." %}</p>
<p class="text-muted">{% trans "Manage your fleet and incoming shipping offers." %}</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" %}
</a>
<a href="{% url 'truck_register' %}" class="btn btn-success">
<i class="fa-solid fa-plus me-2"></i> {% trans "Register Truck" %}
<a href="{% url 'truck_register' %}" class="btn btn-success btn-lg">
<i class="fa-solid fa-plus me-2"></i> {% trans "Add New Truck" %}
</a>
</div>
</div>
<!-- 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.display_truck_type }}" style="height: 200px; object-fit: cover;">
{% else %}
<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.display_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.display_model }} ({{ truck.year }})</p>
<p class="card-text mb-1"><strong>{% trans "Capacity:" %}</strong> {{ truck.display_load_capacity }}</p>
{% if truck.registration_expiry_date %}
<p class="card-text mb-1"><strong>{% trans "Expiry Date:" %}</strong> {{ truck.registration_expiry_date }}</p>
{% endif %}
<a href="{% url 'edit_truck' truck.id %}" class="btn btn-outline-primary btn-sm mt-3">
<i class="fa-solid fa-edit me-1"></i> {% trans "Edit Details" %}
</a>
</div>
<div class="card-footer bg-white border-0">
<span class="badge bg-success w-100 py-2">{% trans "Approved" %}</span>
</div>
</div>
<!-- Incoming Offers Section -->
<div class="card shadow-sm border-0 mb-5">
<div class="card-header bg-white py-3">
<h5 class="mb-0 text-primary">{% trans "Received Shipping Offers" %}</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>{% trans "Route" %}</th>
<th>{% trans "Truck" %}</th>
<th>{% trans "Shipper" %}</th>
<th>{% trans "Offer Amount" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for bid in bids %}
<tr>
<td>
<div class="fw-bold">{{ bid.shipment.origin }} <i class="fa-solid fa-arrow-right mx-1 text-muted small"></i> {{ bid.shipment.destination }}</div>
<small class="text-muted">{{ bid.shipment.delivery_date }}</small>
</td>
<td>{{ bid.truck.plate_no }}</td>
<td>{{ bid.shipment.shipper.username }}</td>
<td class="fw-bold text-success">${{ bid.amount }}</td>
<td>
{% if bid.status == 'PENDING' %}
<span class="badge bg-info">{% trans "Pending" %}</span>
{% elif bid.status == 'ACCEPTED' %}
<span class="badge bg-success">{% trans "Accepted" %}</span>
{% else %}
<span class="badge bg-secondary">{% trans "Rejected" %}</span>
{% endif %}
</td>
<td>
{% if bid.status == 'PENDING' %}
<div class="btn-group btn-group-sm">
<a href="{% url 'accept_bid' bid.id %}" class="btn btn-success">{% trans "Accept" %}</a>
<a href="{% url 'reject_bid' bid.id %}" class="btn btn-outline-danger">{% trans "Reject" %}</a>
</div>
{% else %}
<a href="{% url 'shipment_detail' bid.shipment.id %}" class="btn btn-sm btn-outline-primary">{% trans "View Details" %}</a>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center py-5 text-muted">
<i class="fa-solid fa-inbox fa-2x mb-3 opacity-25"></i>
<p>{% trans "No offers received yet." %}</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endfor %}
{% else %}
<div class="col-12 text-center py-4">
<p class="text-muted">{% trans "No approved trucks yet." %}</p>
</div>
{% endif %}
</div>
</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 %}
<!-- My Fleet -->
<h4 class="mb-3">{% trans "My Fleet" %}</h4>
<div class="row">
{% for truck in trucks %}
<div class="col-md-4 mb-4">
<div class="card h-100 shadow-sm border-0 opacity-75">
<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.display_truck_type }}" style="height: 200px; object-fit: cover;">
{% else %}
<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 opacity-25"></i>
</div>
{% endif %}
<div class="card-body">
<h5 class="card-title text-muted">{{ truck.display_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>
<a href="{% url 'edit_truck' truck.id %}" class="btn btn-outline-secondary btn-sm mt-3">
<i class="fa-solid fa-edit me-1"></i> {% trans "Edit Details" %}
<div class="d-flex justify-content-between align-items-start mb-2">
<h5 class="card-title mb-0">{{ truck.display_truck_type }}</h5>
<span class="badge bg-success">{% trans "Approved" %}</span>
</div>
<p class="card-text mb-1"><small class="text-muted">{% trans "Plate No:" %}</small> <strong>{{ truck.plate_no }}</strong></p>
<p class="card-text mb-1"><small class="text-muted">{% trans "Capacity:" %}</small> {{ truck.display_load_capacity }}</p>
<a href="{% url 'edit_truck' truck.id %}" class="btn btn-outline-primary btn-sm mt-3 w-100">
<i class="fa-solid fa-edit me-1"></i> {% trans "Edit Truck" %}
</a>
</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>
{% endfor %}
{% for truck in pending_trucks %}
<div class="col-md-4 mb-4">
<div class="card h-100 shadow-sm border-0 bg-light">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<h5 class="card-title mb-0 text-muted">{{ truck.display_truck_type }}</h5>
<span class="badge bg-warning text-dark">{% trans "Pending" %}</span>
</div>
<p class="card-text mb-1"><small class="text-muted">{% trans "Plate No:" %}</small> <strong>{{ truck.plate_no }}</strong></p>
<p class="card-text mt-3"><small class="text-muted italic">{% trans "Waiting for admin approval..." %}</small></p>
</div>
</div>
</div>
{% 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 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>
<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="4" class="text-center py-4">{% trans "No bids placed yet." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -17,6 +17,7 @@ urlpatterns = [
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"),
path("shipment/<int:shipment_id>/bid/", views.place_bid, name="place_bid"),
path("truck/<int:truck_id>/offer/", views.place_bid, name="place_bid"),
path("bid/<int:bid_id>/accept/", views.accept_bid, name="accept_bid"),
]
path("bid/<int:bid_id>/reject/", views.reject_bid, name="reject_bid"),
]

View File

@ -3,7 +3,7 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth import login, authenticate, logout
from django.utils import timezone
from .models import Profile, Truck, Shipment, Bid, Message, OTPCode
from .forms import TruckForm, ShipmentForm, BidForm, UserRegistrationForm, OTPVerifyForm
from .forms import TruckForm, ShipmentForm, BidForm, UserRegistrationForm, OTPVerifyForm, ShipperOfferForm
from django.contrib import messages
from django.utils.translation import gettext as _
from django.db.models import Q
@ -152,15 +152,21 @@ def dashboard(request):
profile, created = Profile.objects.get_or_create(user=request.user)
if profile.role == 'SHIPPER':
my_shipments = Shipment.objects.filter(shipper=request.user).order_by('-created_at')
return render(request, 'core/shipper_dashboard.html', {'shipments': my_shipments})
# In the new flow, Shippers place bids.
my_bids = Bid.objects.filter(shipment__shipper=request.user).order_by('-created_at')
return render(request, 'core/shipper_dashboard.html', {
'shipments': my_shipments,
'bids': my_bids
})
elif profile.role == 'TRUCK_OWNER':
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')
# Truck owners receive bids in the new flow
my_received_bids = Bid.objects.filter(truck_owner=request.user).order_by('-created_at')
return render(request, 'core/truck_owner_dashboard.html', {
'trucks': approved_trucks,
'pending_trucks': pending_trucks,
'bids': my_bids
'bids': my_received_bids
})
elif profile.role == 'ADMIN' or request.user.is_superuser:
pending_trucks = Truck.objects.filter(is_approved=False).order_by('-created_at')
@ -175,7 +181,6 @@ def dashboard(request):
}
return render(request, 'core/admin_dashboard.html', context)
else:
# Fallback for undefined roles
return redirect('/')
@login_required
@ -188,7 +193,7 @@ 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.is_approved = False
truck.save()
messages.success(request, _("Truck registered successfully! It will be visible after admin approval."))
return redirect('dashboard')
@ -207,7 +212,7 @@ def edit_truck(request, truck_id):
form = TruckForm(request.POST, request.FILES, instance=truck)
if form.is_valid():
truck = form.save(commit=False)
truck.is_approved = False # Reset approval status on update
truck.is_approved = False
truck.save()
messages.success(request, _("Truck data updated successfully! It will be reviewed by admin again."))
return redirect('dashboard')
@ -227,10 +232,9 @@ def approve_truck(request, truck_id):
truck.is_approved = True
truck.save()
# Notify Truck Owner via WhatsApp
owner_phone = getattr(truck.owner.profile, 'full_phone_number', None)
if owner_phone:
msg = f"Your truck ({truck.plate_no}) has been approved! You can now place bids on shipments."
msg = f"Your truck ({truck.plate_no}) has been approved! You can now receive offers for shipments."
send_whatsapp_message(owner_phone, msg)
messages.success(request, _("Truck approved successfully!"))
@ -249,6 +253,7 @@ def suspend_truck(request, truck_id):
@login_required
def post_shipment(request):
"""Note: This is now largely redundant but kept for compatibility or direct posting."""
if request.user.profile.role != 'SHIPPER':
return redirect('dashboard')
@ -269,80 +274,103 @@ def post_shipment(request):
@login_required
def marketplace(request):
if request.user.profile.role != 'TRUCK_OWNER':
"""Shippers browse available trucks here."""
if request.user.profile.role != 'SHIPPER':
return redirect('dashboard')
shipments = Shipment.objects.filter(status='OPEN').order_by('-created_at')
return render(request, 'core/marketplace.html', {'shipments': shipments})
trucks = Truck.objects.filter(is_approved=True).order_by('-created_at')
return render(request, 'core/marketplace.html', {'trucks': trucks})
@login_required
def place_bid(request, shipment_id):
shipment = get_object_or_404(Shipment, id=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."))
def place_bid(request, truck_id):
"""Shipper makes an offer to a specific truck."""
truck = get_object_or_404(Truck, id=truck_id, is_approved=True)
if request.user.profile.role != 'SHIPPER':
return redirect('dashboard')
if request.method == 'POST':
form = BidForm(request.POST, user=request.user)
form = ShipperOfferForm(request.POST)
if form.is_valid():
bid = form.save(commit=False)
bid.truck_owner = request.user
bid.shipment = shipment
bid.save()
# Create Shipment
shipment = Shipment.objects.create(
shipper=request.user,
description=form.cleaned_data['description'],
weight=form.cleaned_data['weight'],
origin=form.cleaned_data['origin'],
destination=form.cleaned_data['destination'],
delivery_date=form.cleaned_data['delivery_date'],
status='OPEN'
)
# Create Bid (Offer)
bid = Bid.objects.create(
shipment=shipment,
truck_owner=truck.owner,
truck=truck,
amount=form.cleaned_data['amount'],
comments=form.cleaned_data.get('comments', ''),
status='PENDING'
)
# Notify Shipper via WhatsApp
shipper_phone = getattr(shipment.shipper.profile, 'full_phone_number', None)
if shipper_phone:
msg = f"New bid placed on your shipment from {shipment.origin} to {shipment.destination}! Amount: {bid.amount}"
send_whatsapp_message(shipper_phone, msg)
# Notify Truck Owner via WhatsApp
owner_phone = getattr(truck.owner.profile, 'full_phone_number', None)
if owner_phone:
msg = f"New offer received for your truck ({truck.plate_no})! Route: {shipment.origin} to {shipment.destination}. Amount: {bid.amount}"
send_whatsapp_message(owner_phone, msg)
messages.success(request, _("Bid placed successfully!"))
return redirect('marketplace')
messages.success(request, _("Offer sent successfully!"))
return redirect('dashboard')
else:
messages.error(request, _("Error placing bid. Please check the form."))
messages.error(request, _("Error sending offer. Please check the form."))
else:
form = BidForm(user=request.user)
form = ShipperOfferForm()
return render(request, 'core/place_bid.html', {'form': form, 'shipment': shipment})
return render(request, 'core/place_bid.html', {'form': form, 'truck': truck})
@login_required
def shipment_detail(request, shipment_id):
shipment = get_object_or_404(Shipment, id=shipment_id)
# Security: check if user is shipper or a truck owner who bid
if shipment.shipper != request.user and not Bid.objects.filter(shipment=shipment, truck_owner=request.user).exists():
if request.user.profile.role != 'ADMIN' and not request.user.is_superuser:
return redirect('dashboard')
# Security: check if user is shipper, truck owner of a bid, or admin
is_involved = Bid.objects.filter(shipment=shipment, truck_owner=request.user).exists() or shipment.shipper == request.user
if not is_involved and not (request.user.profile.role == 'ADMIN' or request.user.is_superuser):
return redirect('dashboard')
bids = shipment.bids.all()
return render(request, 'core/shipment_detail.html', {'shipment': shipment, 'bids': bids})
@login_required
def accept_bid(request, bid_id):
"""Truck owner accepts an offer from a shipper."""
bid = get_object_or_404(Bid, id=bid_id)
if bid.shipment.shipper != request.user:
if bid.truck_owner != request.user:
messages.error(request, _("You are not authorized to accept this offer."))
return redirect('dashboard')
# Accept this bid
bid.status = 'ACCEPTED'
bid.save()
# Reject others
bid.shipment.bids.exclude(id=bid_id).update(status='REJECTED')
# Update shipment
bid.shipment.status = 'IN_PROGRESS'
bid.shipment.assigned_truck = bid.truck
bid.shipment.save()
# Notify Truck Owner via WhatsApp
owner_phone = getattr(bid.truck_owner.profile, 'full_phone_number', None)
if owner_phone:
msg = f"Congratulations! Your bid for the shipment from {bid.shipment.origin} to {bid.shipment.destination} has been accepted."
send_whatsapp_message(owner_phone, msg)
# Notify Shipper via WhatsApp
shipper_phone = getattr(bid.shipment.shipper.profile, 'full_phone_number', None)
if shipper_phone:
msg = f"Your offer for truck {bid.truck.plate_no} ({bid.shipment.origin} to {bid.shipment.destination}) has been accepted!"
send_whatsapp_message(shipper_phone, msg)
messages.success(request, _("Bid accepted! Shipment is now in progress."))
return redirect('shipment_detail', shipment_id=bid.shipment.id)
messages.success(request, _("Offer accepted! Shipment is now in progress."))
return redirect('dashboard')
@login_required
def reject_bid(request, bid_id):
"""Truck owner rejects an offer."""
bid = get_object_or_404(Bid, id=bid_id)
if bid.truck_owner != request.user:
return redirect('dashboard')
bid.status = 'REJECTED'
bid.save()
messages.info(request, _("Offer rejected."))
return redirect('dashboard')