adding some features
This commit is contained in:
parent
856be645ab
commit
e1e791abf6
BIN
assets/pasted-20260201-075032-c186b500.png
Normal file
BIN
assets/pasted-20260201-075032-c186b500.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/pasted-20260201-081838-f3ed2987.png
Normal file
BIN
assets/pasted-20260201-081838-f3ed2987.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -24,19 +24,26 @@ class ProfileInline(admin.StackedInline):
|
|||||||
verbose_name_plural = _('Profiles')
|
verbose_name_plural = _('Profiles')
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {'fields': ('role', 'is_approved', 'phone_number', 'profile_picture', 'address')}),
|
(None, {'fields': ('role', 'is_approved', 'phone_number', 'profile_picture', 'address')}),
|
||||||
|
(_('Driver Assessment'), {'fields': ('driver_grade', 'is_recommended')}),
|
||||||
(_('Driver Info'), {'fields': ('license_front_image', 'license_back_image', 'car_plate_number', 'bank_account_number'), 'classes': ('collapse',)}),
|
(_('Driver Info'), {'fields': ('license_front_image', 'license_back_image', 'car_plate_number', 'bank_account_number'), 'classes': ('collapse',)}),
|
||||||
(_('Location'), {'fields': ('country', 'governate', 'city'), 'classes': ('collapse',)}),
|
(_('Location'), {'fields': ('country', 'governate', 'city'), 'classes': ('collapse',)}),
|
||||||
)
|
)
|
||||||
|
|
||||||
class CustomUserAdmin(UserAdmin):
|
class CustomUserAdmin(UserAdmin):
|
||||||
inlines = (ProfileInline,)
|
inlines = (ProfileInline,)
|
||||||
list_display = ('username', 'email', 'get_role', 'get_approval_status', 'is_active', 'is_staff', 'send_whatsapp_link')
|
list_display = ('username', 'email', 'get_role', 'get_driver_grade', 'get_approval_status', 'is_active', 'is_staff', 'send_whatsapp_link')
|
||||||
list_filter = ('is_active', 'is_staff', 'profile__role', 'profile__is_approved')
|
list_filter = ('is_active', 'is_staff', 'profile__role', 'profile__is_approved', 'profile__driver_grade')
|
||||||
|
|
||||||
def get_role(self, obj):
|
def get_role(self, obj):
|
||||||
return obj.profile.get_role_display()
|
return obj.profile.get_role_display()
|
||||||
get_role.short_description = _('Role')
|
get_role.short_description = _('Role')
|
||||||
|
|
||||||
|
def get_driver_grade(self, obj):
|
||||||
|
if obj.profile.role == 'car_owner':
|
||||||
|
return obj.profile.get_driver_grade_display()
|
||||||
|
return "-"
|
||||||
|
get_driver_grade.short_description = _('Grade')
|
||||||
|
|
||||||
def get_approval_status(self, obj):
|
def get_approval_status(self, obj):
|
||||||
return obj.profile.is_approved
|
return obj.profile.is_approved
|
||||||
get_approval_status.short_description = _('Approved')
|
get_approval_status.short_description = _('Approved')
|
||||||
@ -192,6 +199,10 @@ class PlatformProfileAdmin(admin.ModelAdmin):
|
|||||||
(_('Financial Configuration'), {
|
(_('Financial Configuration'), {
|
||||||
'fields': ('platform_fee_percentage', 'enable_payment')
|
'fields': ('platform_fee_percentage', 'enable_payment')
|
||||||
}),
|
}),
|
||||||
|
(_('Maintenance / Availability'), {
|
||||||
|
'fields': ('accepting_shipments', 'maintenance_message_en', 'maintenance_message_ar'),
|
||||||
|
'description': _('Toggle to allow or stop receiving new parcel shipments. If stopped, buttons will turn red and an alert will be shown.')
|
||||||
|
}),
|
||||||
(_('Testing / Development'), {
|
(_('Testing / Development'), {
|
||||||
'fields': ('auto_mark_paid',),
|
'fields': ('auto_mark_paid',),
|
||||||
'description': _('Enable this to automatically mark NEW parcels as "Paid" (useful for testing so drivers can see them immediately).')
|
'description': _('Enable this to automatically mark NEW parcels as "Paid" (useful for testing so drivers can see them immediately).')
|
||||||
|
|||||||
@ -47,6 +47,10 @@ class ParcelListCreateView(generics.ListCreateAPIView):
|
|||||||
return Parcel.objects.none()
|
return Parcel.objects.none()
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
|
from .models import PlatformProfile
|
||||||
|
platform_profile = PlatformProfile.objects.first()
|
||||||
|
if platform_profile and not platform_profile.accepting_shipments:
|
||||||
|
raise permissions.PermissionDenied(platform_profile.maintenance_message or "The platform is currently not accepting new shipments.")
|
||||||
# Only shippers can create
|
# Only shippers can create
|
||||||
if self.request.user.profile.role != 'shipper':
|
if self.request.user.profile.role != 'shipper':
|
||||||
raise permissions.PermissionDenied("Only shippers can create parcels.")
|
raise permissions.PermissionDenied("Only shippers can create parcels.")
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-01 12:51
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0026_profile_bank_account_number'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='profile',
|
||||||
|
name='driver_grade',
|
||||||
|
field=models.CharField(choices=[('none', 'No Grade'), ('bronze_3', 'Bronze III'), ('bronze_2', 'Bronze II'), ('bronze_1', 'Bronze I'), ('silver', 'Silver'), ('gold', 'Gold')], default='none', max_length=20, verbose_name='Driver Grade'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='profile',
|
||||||
|
name='is_recommended',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Recommended by Shippers'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-01 13:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0027_profile_driver_grade_profile_is_recommended'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='platformprofile',
|
||||||
|
name='accepting_shipments',
|
||||||
|
field=models.BooleanField(default=True, help_text='Toggle to allow or stop receiving new parcel shipments.', verbose_name='Accepting Shipments'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='platformprofile',
|
||||||
|
name='maintenance_message_ar',
|
||||||
|
field=models.TextField(blank=True, help_text='Message to show when shipments are stopped.', verbose_name='Maintenance Message (Arabic)'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='platformprofile',
|
||||||
|
name='maintenance_message_en',
|
||||||
|
field=models.TextField(blank=True, help_text='Message to show when shipments are stopped.', verbose_name='Maintenance Message (English)'),
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
Binary file not shown.
@ -65,8 +65,16 @@ class City(models.Model):
|
|||||||
|
|
||||||
class Profile(models.Model):
|
class Profile(models.Model):
|
||||||
ROLE_CHOICES = (
|
ROLE_CHOICES = (
|
||||||
('shipper', _('Shipper')),
|
("shipper", _("Shipper")),
|
||||||
('car_owner', _('Car Owner')),
|
("car_owner", _("Car Owner")),
|
||||||
|
)
|
||||||
|
DRIVER_GRADE_CHOICES = (
|
||||||
|
("none", _("No Grade")),
|
||||||
|
("bronze_3", _("Bronze III")),
|
||||||
|
("bronze_2", _("Bronze II")),
|
||||||
|
("bronze_1", _("Bronze I")),
|
||||||
|
("silver", _("Silver")),
|
||||||
|
("gold", _("Gold")),
|
||||||
)
|
)
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name=_('User'))
|
user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name=_('User'))
|
||||||
role = models.CharField(_('Role'), max_length=20, choices=ROLE_CHOICES, default='shipper')
|
role = models.CharField(_('Role'), max_length=20, choices=ROLE_CHOICES, default='shipper')
|
||||||
@ -85,6 +93,10 @@ class Profile(models.Model):
|
|||||||
city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('City'))
|
city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('City'))
|
||||||
|
|
||||||
# Approval Status
|
# Approval Status
|
||||||
|
# Driver Assessment
|
||||||
|
driver_grade = models.CharField(_("Driver Grade"), max_length=20, choices=DRIVER_GRADE_CHOICES, default="none")
|
||||||
|
is_recommended = models.BooleanField(_("Recommended by Shippers"), default=False)
|
||||||
|
|
||||||
is_approved = models.BooleanField(_('Approved'), default=False, help_text=_("Designates whether this user is approved to use the platform (mainly for drivers)."))
|
is_approved = models.BooleanField(_('Approved'), default=False, help_text=_("Designates whether this user is approved to use the platform (mainly for drivers)."))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -166,6 +178,17 @@ class PlatformProfile(models.Model):
|
|||||||
# Testing / Development
|
# Testing / Development
|
||||||
auto_mark_paid = models.BooleanField(_('Test Mode: Auto-Paid'), default=False, help_text=_("If enabled, newly created parcels will automatically be marked as 'Paid' for testing."))
|
auto_mark_paid = models.BooleanField(_('Test Mode: Auto-Paid'), default=False, help_text=_("If enabled, newly created parcels will automatically be marked as 'Paid' for testing."))
|
||||||
|
|
||||||
|
# Maintenance / Availability
|
||||||
|
accepting_shipments = models.BooleanField(_("Accepting Shipments"), default=True, help_text=_("Toggle to allow or stop receiving new parcel shipments."))
|
||||||
|
maintenance_message_en = models.TextField(_("Maintenance Message (English)"), blank=True, help_text=_("Message to show when shipments are stopped."))
|
||||||
|
maintenance_message_ar = models.TextField(_("Maintenance Message (Arabic)"), blank=True, help_text=_("Message to show when shipments are stopped."))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def maintenance_message(self):
|
||||||
|
if get_language() == "ar":
|
||||||
|
return self.maintenance_message_ar or _("Service is temporarily suspended. Please try again later.")
|
||||||
|
return self.maintenance_message_en or _("Service is temporarily suspended. Please try again later.")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def privacy_policy(self):
|
def privacy_policy(self):
|
||||||
if get_language() == 'ar':
|
if get_language() == 'ar':
|
||||||
|
|||||||
@ -170,7 +170,7 @@
|
|||||||
|
|
||||||
{% if not user.is_authenticated %}
|
{% if not user.is_authenticated %}
|
||||||
<li class="nav-item ms-lg-3 d-none d-lg-block">
|
<li class="nav-item ms-lg-3 d-none d-lg-block">
|
||||||
<a href="{% url 'shipment_request' %}" class="btn btn-masarx-primary btn-sm rounded-pill px-4">{% trans "Start Shipping" %}</a>
|
{% if platform_profile.accepting_shipments %}<a href="{% url 'shipment_request' %}" class="btn btn-masarx-active btn-sm rounded-pill px-4">{% trans "Start Shipping" %}</a>{% else %}<button class="btn btn-masarx-stopped btn-sm rounded-pill px-4" disabled title="{{ platform_profile.maintenance_message }}">{% trans "Stopped" %}</button>{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -2,6 +2,14 @@
|
|||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bg-bronze { background-color: #cd7f32; }
|
||||||
|
.bg-silver { background-color: #c0c0c0; }
|
||||||
|
.bg-gold { background-color: #ffd700; }
|
||||||
|
.text-bronze { color: #cd7f32; }
|
||||||
|
</style>
|
||||||
|
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<section class="hero-section">
|
<section class="hero-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -9,8 +17,13 @@
|
|||||||
<div class="col-lg-6 mb-5 mb-lg-0">
|
<div class="col-lg-6 mb-5 mb-lg-0">
|
||||||
<h1 class="display-3 mb-4">{% trans "Small Shipments," %}<br><span style="color: var(--accent-orange)">{% trans "Smart Delivery." %}</span></h1>
|
<h1 class="display-3 mb-4">{% trans "Small Shipments," %}<br><span style="color: var(--accent-orange)">{% trans "Smart Delivery." %}</span></h1>
|
||||||
<p class="lead mb-5 opacity-75">{% trans "masarX connects shippers with local car owners for fast, reliable, and trackable deliveries. Your cargo, our priority." %}</p>
|
<p class="lead mb-5 opacity-75">{% trans "masarX connects shippers with local car owners for fast, reliable, and trackable deliveries. Your cargo, our priority." %}</p>
|
||||||
|
{% if not platform_profile.accepting_shipments and platform_profile.maintenance_message %}
|
||||||
|
<div class="alert alert-danger mb-4 py-2 px-3 small border-0 shadow-sm" style="border-radius: 12px; max-width: 500px;">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill me-2"></i> {{ platform_profile.maintenance_message }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="d-flex gap-3">
|
<div class="d-flex gap-3">
|
||||||
<a href="{% url 'shipment_request' %}" class="btn btn-masarx-primary">{% trans "Start Shipping" %}</a>
|
{% if platform_profile.accepting_shipments %}<a href="{% url 'shipment_request' %}" class="btn btn-masarx-active">{% trans "Start Shipping" %}</a>{% else %}<button class="btn btn-masarx-stopped" disabled style="cursor: not-allowed;">{% trans "Service Stopped" %}</button>{% endif %}
|
||||||
<a href="#how-it-works" class="btn btn-outline-light border-2 px-4 py-2" style="border-radius: 12px;">{% trans "Learn More" %}</a>
|
<a href="#how-it-works" class="btn btn-outline-light border-2 px-4 py-2" style="border-radius: 12px;">{% trans "Learn More" %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -94,7 +107,21 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<h6 class="mb-0">{{ driver.user.first_name }} {{ driver.user.last_name|first }}.</h6>
|
<h6 class="mb-0">
|
||||||
|
{{ driver.user.first_name }} {{ driver.user.last_name|first }}.
|
||||||
|
{% if driver.driver_grade != "none" %}
|
||||||
|
{% if "bronze" in driver.driver_grade %}
|
||||||
|
<span class="badge bg-bronze text-white small" title="{{ driver.get_driver_grade_display }}"><i class="bi bi-award"></i> {{ driver.get_driver_grade_display }}</span>
|
||||||
|
{% elif driver.driver_grade == "silver" %}
|
||||||
|
<span class="badge bg-silver text-dark small" title="Silver"><i class="bi bi-award-fill"></i> Silver</span>
|
||||||
|
{% elif driver.driver_grade == "gold" %}
|
||||||
|
<span class="badge bg-gold text-dark small" title="Gold"><i class="bi bi-award-fill"></i> Gold</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if driver.is_recommended %}
|
||||||
|
<span class="badge bg-success small" title="{% trans "Recommended by Shippers" %}"><i class="bi bi-hand-thumbs-up"></i></span>
|
||||||
|
{% endif %}
|
||||||
|
</h6>
|
||||||
<small class="text-muted">{% trans "Driver" %}</small>
|
<small class="text-muted">{% trans "Driver" %}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-end">
|
<div class="text-end">
|
||||||
|
|||||||
@ -4,6 +4,13 @@
|
|||||||
{% block title %}{% trans "My Profile" %} | masarX{% endblock %}
|
{% block title %}{% trans "My Profile" %} | masarX{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bg-bronze { background-color: #cd7f32; }
|
||||||
|
.bg-silver { background-color: #c0c0c0; }
|
||||||
|
.bg-gold { background-color: #ffd700; }
|
||||||
|
</style>
|
||||||
|
|
||||||
<section class="py-5 bg-light" style="min-height: 80vh;">
|
<section class="py-5 bg-light" style="min-height: 80vh;">
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
@ -32,7 +39,21 @@
|
|||||||
<span class="text-white fs-1">{{ profile.user.first_name|first|upper }}</span>
|
<span class="text-white fs-1">{{ profile.user.first_name|first|upper }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h3 class="mt-3">{{ profile.user.get_full_name }}</h3>
|
<h3 class="mt-3">
|
||||||
|
{{ profile.user.get_full_name }}
|
||||||
|
{% if profile.driver_grade != "none" %}
|
||||||
|
{% if "bronze" in profile.driver_grade %}
|
||||||
|
<span class="badge bg-bronze text-white fs-6 ms-2" title="{{ profile.get_driver_grade_display }}"><i class="bi bi-award"></i> {{ profile.get_driver_grade_display }}</span>
|
||||||
|
{% elif profile.driver_grade == "silver" %}
|
||||||
|
<span class="badge bg-silver text-dark fs-6 ms-2" title="Silver"><i class="bi bi-award-fill"></i> Silver</span>
|
||||||
|
{% elif profile.driver_grade == "gold" %}
|
||||||
|
<span class="badge bg-gold text-dark fs-6 ms-2" title="Gold"><i class="bi bi-award-fill"></i> Gold</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if profile.is_recommended %}
|
||||||
|
<span class="badge bg-success fs-6 ms-2" title="{% trans "Recommended by Shippers" %}"><i class="bi bi-hand-thumbs-up"></i></span>
|
||||||
|
{% endif %}
|
||||||
|
</h3>
|
||||||
<p class="text-muted">{{ profile.get_role_display }}</p>
|
<p class="text-muted">{{ profile.get_role_display }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -2,10 +2,26 @@
|
|||||||
{% load i18n core_tags %}
|
{% load i18n core_tags %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bg-bronze { background-color: #cd7f32; }
|
||||||
|
.bg-silver { background-color: #c0c0c0; }
|
||||||
|
.bg-gold { background-color: #ffd700; }
|
||||||
|
</style>
|
||||||
|
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
|
{% if not platform_profile.accepting_shipments and platform_profile.maintenance_message %}
|
||||||
|
<div class="alert alert-danger mb-4 py-3 px-4 shadow-sm border-0 d-flex align-items-center" style="border-radius: 15px;">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill fs-4 me-3"></i>
|
||||||
|
<div>
|
||||||
|
<h5 class="alert-heading mb-1">{% trans "Service Notice" %}</h5>
|
||||||
|
<p class="mb-0 opacity-75">{{ platform_profile.maintenance_message }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h1>{% trans "My Shipments" %}</h1>
|
<h1>{% trans "My Shipments" %}</h1>
|
||||||
<a href="{% url 'shipment_request' %}" class="btn btn-masarx-primary">{% trans "New Shipment" %}</a>
|
{% if platform_profile.accepting_shipments %}<a href="{% url 'shipment_request' %}" class="btn btn-masarx-active">{% trans "New Shipment" %}</a>{% else %}<button class="btn btn-masarx-stopped" disabled style="cursor: not-allowed;">{% trans "Service Stopped" %}</button>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
@ -97,7 +113,24 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<p class="card-text small mb-0"><strong>{% trans "Receiver" %}:</strong> {{ parcel.receiver_name }}</p>
|
<p class="card-text small mb-0"><strong>{% trans "Receiver" %}:</strong> {{ parcel.receiver_name }}</p>
|
||||||
<p class="card-text small"><strong>{% trans "Carrier" %}:</strong> {% if parcel.carrier %}{{ parcel.carrier.get_full_name|default:parcel.carrier.username }}{% else %}{% trans "Waiting for pickup" %}{% endif %}</p>
|
<p class="card-text small"><strong>{% trans "Carrier" %}:</strong>
|
||||||
|
{% if parcel.carrier %}
|
||||||
|
{{ parcel.carrier.get_full_name|default:parcel.carrier.username }}
|
||||||
|
{% if parcel.carrier.profile.driver_grade != "none" %}
|
||||||
|
{% if "bronze" in parcel.carrier.profile.driver_grade %}
|
||||||
|
<span class="badge bg-bronze text-white small ms-1" title="{{ parcel.carrier.profile.get_driver_grade_display }}}"><i class="bi bi-award"></i></span>
|
||||||
|
{% elif parcel.carrier.profile.driver_grade == "silver" %}
|
||||||
|
<span class="badge bg-silver text-dark small ms-1" title="Silver"><i class="bi bi-award-fill"></i></span>
|
||||||
|
{% elif parcel.carrier.profile.driver_grade == "gold" %}
|
||||||
|
<span class="badge bg-gold text-dark small ms-1" title="Gold"><i class="bi bi-award-fill"></i></span>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if parcel.carrier.profile.is_recommended %}
|
||||||
|
<span class="badge bg-success small ms-1" title="{% trans "Recommended by Shippers" %}"><i class="bi bi-hand-thumbs-up"></i></span>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Waiting for pickup" %}
|
||||||
|
{% endif %}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -130,7 +163,23 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="d-flex align-items-center gap-1">
|
<span class="d-flex align-items-center gap-1">
|
||||||
<i class="bi bi-truck"></i>
|
<i class="bi bi-truck"></i>
|
||||||
{% if parcel.carrier %}{{ parcel.carrier.get_full_name|default:parcel.carrier.username }}{% else %}{% trans "Waiting" %}{% endif %}
|
{% if parcel.carrier %}
|
||||||
|
{{ parcel.carrier.get_full_name|default:parcel.carrier.username }}
|
||||||
|
{% if parcel.carrier.profile.driver_grade != "none" %}
|
||||||
|
{% if "bronze" in parcel.carrier.profile.driver_grade %}
|
||||||
|
<span class="badge bg-bronze text-white small ms-1" title="{{ parcel.carrier.profile.get_driver_grade_display }}}"><i class="bi bi-award"></i></span>
|
||||||
|
{% elif parcel.carrier.profile.driver_grade == "silver" %}
|
||||||
|
<span class="badge bg-silver text-dark small ms-1" title="Silver"><i class="bi bi-award-fill"></i></span>
|
||||||
|
{% elif parcel.carrier.profile.driver_grade == "gold" %}
|
||||||
|
<span class="badge bg-gold text-dark small ms-1" title="Gold"><i class="bi bi-award-fill"></i></span>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if parcel.carrier.profile.is_recommended %}
|
||||||
|
<span class="badge bg-success small ms-1" title="{% trans "Recommended by Shippers" %}"><i class="bi bi-hand-thumbs-up"></i></span>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Waiting" %}
|
||||||
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -223,7 +272,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-5">
|
<div class="text-center py-5">
|
||||||
<p class="lead">{% trans "You have no active shipments." %}</p>
|
<p class="lead">{% trans "You have no active shipments." %}</p>
|
||||||
<a href="{% url 'shipment_request' %}" class="btn btn-masarx-primary">{% trans "Send your first shipment" %}</a>
|
{% if platform_profile.accepting_shipments %}<a href="{% url 'shipment_request' %}" class="btn btn-masarx-active">{% trans "Send your first shipment" %}</a>{% else %}<button class="btn btn-masarx-stopped" disabled style="cursor: not-allowed;">{% trans "Service Stopped" %}</button>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -286,6 +286,11 @@ def dashboard(request):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def shipment_request(request):
|
def shipment_request(request):
|
||||||
|
from .models import PlatformProfile
|
||||||
|
platform_profile = PlatformProfile.objects.first()
|
||||||
|
if platform_profile and not platform_profile.accepting_shipments:
|
||||||
|
messages.warning(request, platform_profile.maintenance_message or _("The platform is currently not accepting new shipments."))
|
||||||
|
return redirect("dashboard")
|
||||||
profile, created = Profile.objects.get_or_create(user=request.user)
|
profile, created = Profile.objects.get_or_create(user=request.user)
|
||||||
if profile.role != 'shipper':
|
if profile.role != 'shipper':
|
||||||
messages.error(request, _("Only shippers can request shipments."))
|
messages.error(request, _("Only shippers can request shipments."))
|
||||||
|
|||||||
@ -239,3 +239,30 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
[dir="rtl"] .text-left {
|
[dir="rtl"] .text-left {
|
||||||
text-align: right !important;
|
text-align: right !important;
|
||||||
}
|
}
|
||||||
|
/* Shipment Status Buttons */
|
||||||
|
.btn-masarx-active {
|
||||||
|
background-color: #2fb344 !important;
|
||||||
|
color: white !important;
|
||||||
|
border: none !important;
|
||||||
|
padding: 12px 30px !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
.btn-masarx-active:hover {
|
||||||
|
background-color: #248a35 !important;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(47, 179, 68, 0.2);
|
||||||
|
}
|
||||||
|
.btn-masarx-stopped {
|
||||||
|
background-color: #d63939 !important;
|
||||||
|
color: white !important;
|
||||||
|
border: none !important;
|
||||||
|
padding: 12px 30px !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
.btn-masarx-stopped:hover {
|
||||||
|
background-color: #b02a2a !important;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(214, 57, 57, 0.2);
|
||||||
|
}
|
||||||
|
|||||||
@ -26,11 +26,12 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
flex-direction: row !important;
|
flex-direction: row !important;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
justify-content: space-between !important;
|
justify-content: space-between !important;
|
||||||
flex-wrap: nowrap !important; /* Ensure they stay on one line */
|
flex-wrap: nowrap !important;
|
||||||
gap: 2px !important;
|
gap: 2px !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
box-sizing: border-box !important;
|
box-sizing: border-box !important;
|
||||||
/* overflow: hidden; Removed to allow dropdown list to show */
|
position: relative !important;
|
||||||
|
z-index: 100 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide any stray shortcuts that might have survived JS cleanup */
|
/* Hide any stray shortcuts that might have survived JS cleanup */
|
||||||
@ -39,14 +40,21 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.masar-date-filter-row select {
|
.masar-date-filter-row select {
|
||||||
|
appearance: auto !important; /* Force native dropdown appearance */
|
||||||
|
-webkit-appearance: auto !important;
|
||||||
|
-moz-appearance: auto !important;
|
||||||
|
|
||||||
width: 32% !important;
|
width: 32% !important;
|
||||||
min-width: 0 !important;
|
min-width: 0 !important;
|
||||||
font-size: 11px !important;
|
font-size: 11px !important;
|
||||||
padding: 0 2px !important;
|
padding: 2px !important; /* Relaxed padding */
|
||||||
height: 28px !important;
|
height: 28px !important;
|
||||||
line-height: 1 !important;
|
|
||||||
box-sizing: border-box !important;
|
box-sizing: border-box !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
|
background-color: white !important;
|
||||||
|
border: 1px solid #ced4da !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
color: #495057 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.masar-date-filter-row input {
|
.masar-date-filter-row input {
|
||||||
@ -57,22 +65,23 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
height: 28px !important;
|
height: 28px !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
box-sizing: border-box !important;
|
box-sizing: border-box !important;
|
||||||
|
background-color: white !important;
|
||||||
|
border: 1px solid #ced4da !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Specific fix for date inputs to ensure they look clean */
|
/* Specific fix for date inputs to ensure they look clean */
|
||||||
.masar-date-filter-row input[type="date"] {
|
.masar-date-filter-row input[type="date"] {
|
||||||
-webkit-appearance: none; /* Remove some browser defaults */
|
|
||||||
appearance: none;
|
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
}
|
}
|
||||||
.masar-date-filter-row input[type="date"]::-webkit-inner-spin-button,
|
.masar-date-filter-row input[type="date"]::-webkit-inner-spin-button,
|
||||||
.masar-date-filter-row input[type="date"]::-webkit-calendar-picker-indicator {
|
.masar-date-filter-row input[type="date"]::-webkit-calendar-picker-indicator {
|
||||||
/* Make the calendar icon smaller and fit */
|
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -230,3 +239,30 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
[dir="rtl"] .text-left {
|
[dir="rtl"] .text-left {
|
||||||
text-align: right !important;
|
text-align: right !important;
|
||||||
}
|
}
|
||||||
|
/* Shipment Status Buttons */
|
||||||
|
.btn-masarx-active {
|
||||||
|
background-color: #2fb344 !important;
|
||||||
|
color: white !important;
|
||||||
|
border: none !important;
|
||||||
|
padding: 12px 30px !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
.btn-masarx-active:hover {
|
||||||
|
background-color: #248a35 !important;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(47, 179, 68, 0.2);
|
||||||
|
}
|
||||||
|
.btn-masarx-stopped {
|
||||||
|
background-color: #d63939 !important;
|
||||||
|
color: white !important;
|
||||||
|
border: none !important;
|
||||||
|
padding: 12px 30px !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
.btn-masarx-stopped:hover {
|
||||||
|
background-color: #b02a2a !important;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(214, 57, 57, 0.2);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
(function($) {
|
(function($) {
|
||||||
// Masar Date Range Filter Layout Fix v4
|
// Masar Date Range Filter Layout Fix v5
|
||||||
// Forces a horizontal layout for the Date Range Filter in Django Admin Sidebar
|
// Forces a horizontal layout for the Date Range Filter in Django Admin Sidebar
|
||||||
// v4: Switches to type="date", removes Django's calendar shortcuts to prevent layout breakage.
|
// v5: Removes Bootstrap classes from Select to ensure native popup works reliably.
|
||||||
|
|
||||||
function initDateRangeDropdown() {
|
function initDateRangeDropdown() {
|
||||||
|
|
||||||
@ -30,7 +30,8 @@
|
|||||||
var $wrapper = $('<div class="masar-date-filter-row"></div>');
|
var $wrapper = $('<div class="masar-date-filter-row"></div>');
|
||||||
|
|
||||||
// Create the Quick Select Dropdown
|
// Create the Quick Select Dropdown
|
||||||
var $select = $('<select class="form-control custom-select admin-date-dropdown">' +
|
// REMOVED 'form-control' and 'custom-select' to prevent Bootstrap/AdminLTE from interfering with click/rendering
|
||||||
|
var $select = $('<select class="admin-date-dropdown">' +
|
||||||
'<option value="any">Any</option>' +
|
'<option value="any">Any</option>' +
|
||||||
'<option value="today">Today</option>' +
|
'<option value="today">Today</option>' +
|
||||||
'<option value="7days">7 Days</option>' +
|
'<option value="7days">7 Days</option>' +
|
||||||
@ -39,7 +40,6 @@
|
|||||||
'</select>');
|
'</select>');
|
||||||
|
|
||||||
// CONVERT INPUTS TO HTML5 DATE
|
// CONVERT INPUTS TO HTML5 DATE
|
||||||
// This gives us a native picker and removes the need for Django's clunky JS shortcuts
|
|
||||||
$gte.attr('type', 'date').removeClass('vDateField');
|
$gte.attr('type', 'date').removeClass('vDateField');
|
||||||
$lte.attr('type', 'date').removeClass('vDateField');
|
$lte.attr('type', 'date').removeClass('vDateField');
|
||||||
|
|
||||||
@ -53,8 +53,6 @@
|
|||||||
$wrapper.append($lte);
|
$wrapper.append($lte);
|
||||||
|
|
||||||
// 3. AGGRESSIVE CLEANUP
|
// 3. AGGRESSIVE CLEANUP
|
||||||
// Remove text nodes, BRs, AND Django's calendar shortcuts (.datetimeshortcuts)
|
|
||||||
// We search the *original parent* for these leftovers.
|
|
||||||
$parent.contents().filter(function() {
|
$parent.contents().filter(function() {
|
||||||
return (
|
return (
|
||||||
(this.nodeType === 3 && $.trim($(this).text()) !== '') || // Text
|
(this.nodeType === 3 && $.trim($(this).text()) !== '') || // Text
|
||||||
@ -63,9 +61,6 @@
|
|||||||
);
|
);
|
||||||
}).remove();
|
}).remove();
|
||||||
|
|
||||||
// Also hide any shortcuts that might be dynamically appended later (via CSS rule or observer)
|
|
||||||
// But removing the 'vDateField' class above usually prevents Django from initializing them.
|
|
||||||
|
|
||||||
// Logic for Dropdown Changes
|
// Logic for Dropdown Changes
|
||||||
function formatDate(d) {
|
function formatDate(d) {
|
||||||
var year = d.getFullYear();
|
var year = d.getFullYear();
|
||||||
@ -87,7 +82,7 @@
|
|||||||
var today = new Date();
|
var today = new Date();
|
||||||
|
|
||||||
if (val === 'custom') {
|
if (val === 'custom') {
|
||||||
// Do nothing, let user edit
|
// Do nothing
|
||||||
} else {
|
} else {
|
||||||
if (val === 'any') {
|
if (val === 'any') {
|
||||||
$gte.val('');
|
$gte.val('');
|
||||||
|
|||||||
BIN
staticfiles/pasted-20260201-075032-c186b500.png
Normal file
BIN
staticfiles/pasted-20260201-075032-c186b500.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
staticfiles/pasted-20260201-081838-f3ed2987.png
Normal file
BIN
staticfiles/pasted-20260201-081838-f3ed2987.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Loading…
x
Reference in New Issue
Block a user