Compare commits

...

8 Commits

Author SHA1 Message Date
76ecf45a88 check 2025-05-20 14:17:43 +03:00
e2499f1725 Merge branch 'main' of http://10.10.1.136:3000/ismail/haikal 2025-05-20 13:14:14 +03:00
ismail
7d8ea79cc9 add loading to payment checkout 2025-05-20 13:13:40 +03:00
6acbf4d6dd Merge branch 'main' of http://10.10.1.136:3000/ismail/haikal 2025-05-20 13:10:25 +03:00
89fd86a7d6 tables 2025-05-20 13:10:19 +03:00
ismail
de3f6cf558 some updatese 2025-05-19 17:35:46 +03:00
808a00edd4 Merge pull request 'frontend' (#37) from frontend into main
Reviewed-on: #37
Reviewed-by: ismail <ismail.mosa.ibrahim@gmail.com>
2025-05-19 17:35:16 +03:00
fa89373030 Merge branch 'main' of http://10.10.1.136:3000/ismail/haikal 2025-05-19 17:20:31 +03:00
13 changed files with 130 additions and 44 deletions

View File

@ -186,6 +186,17 @@ class CustomerForm(forms.ModelForm):
"address",
'image',
]
widgets = {
'title': forms.Select(attrs={'class': 'form-control form-control-sm'}),
'first_name': forms.TextInput(attrs={'class': 'form-control form-control-sm'}),
'last_name': forms.TextInput(attrs={'class': 'form-control form-control-sm'}),
'email': forms.EmailInput(attrs={'class': 'form-control form-control-sm'}),
'phone_number': forms.TextInput(attrs={'class': 'form-control form-control-sm'}),
'national_id': forms.TextInput(attrs={'class': 'form-control form-control-sm'}),
'dob': DateInput(attrs={'class': 'form-control form-control-sm', 'type': 'date'}),
'address': forms.Textarea(attrs={'class': 'form-control form-control-sm'}),
'image': forms.FileInput(attrs={'class': 'form-control form-control-sm'}),
}
# class CustomerForm(forms.Form):
# """
# Represents a form for collecting customer information.
@ -1025,6 +1036,7 @@ class LeadForm(forms.ModelForm):
"hx-swap": "outerHTML",
"hx-on::before-request": "document.querySelector('#id_id_car_model').setAttribute('disabled', true)",
"hx-on::after-request": "document.querySelector('#id_id_car_model').removeAttribute('disabled')",
"hx-indicator": "#spinner",
}
),
required=True,

View File

@ -53,6 +53,11 @@ urlpatterns = [
path("submit_plan/", views.submit_plan, name="submit_plan"),
path('payment-callback/', views.payment_callback, name='payment_callback'),
#
path(
"dealers/activity/",
views.UserActivityLogListView.as_view(),
name="dealer_activity",
),
path("dealers/<slug:slug>/settings/", views.DealerSettingsView, name="dealer_settings"),
path("dealers/assign-car-makes/", views.assign_car_makes, name="assign_car_makes"),
path("dashboards/manager/", views.ManagerDashboard.as_view(), name="manager_dashboard"),
@ -67,11 +72,6 @@ urlpatterns = [
views.DealerUpdateView.as_view(),
name="dealer_update",
),
path(
"dealers/activity/",
views.UserActivityLogListView.as_view(),
name="dealer_activity",
),
# path('dealers/<int:pk>/delete/', views.DealerDeleteView.as_view(), name='dealer_delete'),
# CRM URLs
path(

View File

@ -28,6 +28,7 @@ from django.contrib import messages
from django.http import Http404, JsonResponse, HttpResponseForbidden
from django.forms import HiddenInput, ValidationError
from django.shortcuts import HttpResponse
from django.db.models import Sum, F, Count
from django.core.paginator import Paginator
from django.contrib.auth.models import User
@ -2091,7 +2092,7 @@ class CustomerUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView
@login_required
def delete_customer(request, pk):
def delete_customer(request, slug):
"""
Deletes a customer from the system and deactivates the corresponding user account.
@ -2107,7 +2108,7 @@ def delete_customer(request, pk):
:return: A redirect response to the customer list page.
:rtype: HttpResponseRedirect
"""
customer = get_object_or_404(models.Customer, pk=pk)
customer = get_object_or_404(models.Customer, slug=slug)
customer.deactivate_account()
messages.success(request, _("Customer deactivated successfully"))
return redirect("customer_list")
@ -3760,7 +3761,7 @@ def create_sale_order(request, pk):
models.Car.objects.get(vin=item.item_model.name).mark_as_sold(request)
messages.success(request, "Sale Order created successfully")
return redirect("estimate_detail", pk=pk)
return redirect("estimate_detail", pk=estimate.pk)
form = forms.SaleOrderForm()
form.fields["estimate"].queryset = EstimateModel.objects.filter(pk=pk)
@ -4246,7 +4247,6 @@ def invoice_create(request, pk):
form = forms.InvoiceModelCreateForm(
entity_slug=entity.slug, user_model=entity.admin
)
form.initial.update(
{
"customer": estimate.customer,
@ -4255,6 +4255,7 @@ def invoice_create(request, pk):
"unearned_account": dealer.settings.invoice_unearned_account,
}
)
print(dir(form.fields["customer"]))
context = {
"form": form,
@ -4675,11 +4676,15 @@ def lead_create(request):
form = forms.LeadForm()
form.filter_qs(dealer=dealer)
make = request.GET.get("id_car_make", None)
if make:
if make := request.GET.get("id_car_make", None):
form.fields["id_car_model"].queryset = models.CarModel.objects.filter(
id_car_make=int(make)
)
else:
if first_make := form.fields["id_car_make"].queryset.first():
form.fields["id_car_model"].queryset = models.CarModel.objects.filter(
id_car_make=first_make.id_car_make
)
return render(request, "crm/leads/lead_form.html", {"form": form})
def lead_tracking(request):
@ -4769,7 +4774,7 @@ class LeadUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
@login_required
@permission_required("inventory.delete_lead", raise_exception=True)
def LeadDeleteView(request, pk):
def LeadDeleteView(request, slug):
"""
Handles the deletion of a Lead along with its associated customer and potentially
a related user account in the system. Ensures proper permissions and login
@ -4780,7 +4785,7 @@ def LeadDeleteView(request, pk):
:param pk: The primary key identifier of the Lead to be deleted.
:return: An HTTP redirect response to the lead list page.
"""
lead = get_object_or_404(models.Lead, pk=pk)
lead = get_object_or_404(models.Lead, slug=slug)
try:
User.objects.get(email=lead.customer.email).delete()
lead.customer.delete()
@ -5193,6 +5198,12 @@ class OpportunityCreateView(CreateView,SuccessMessageMixin, LoginRequiredMixin):
dealer = get_user_type(self.request)
return context
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# dealer = get_user_type(self.request)
# kwargs["car"].queryset = models.Car.objects.filter(dealer=dealer,)
# return kwargs
# def get_initial(self):
# initial = super().get_initial()
# if self.kwargs.get("pk", None):

View File

@ -1,11 +1,26 @@
{% extends 'base.html' %}
{% load i18n static crispy_forms_filters %}
{% block customcss %}
<style>
.htmx-indicator{
opacity:0;
transition: opacity 500ms ease-in;
}
.htmx-request .htmx-indicator{
opacity:1;
}
.htmx-request.htmx-indicator{
opacity:1;
}
</style>
{% endblock customcss %}
{% block content %}
<div class="container-fluid">
<h1>{% if object %}{{ _("Update") }}{% else %}{{ _("Create") }}{% endif %}</h1>
<div class="row mb-3">
<div class="col-sm-6 col-md-8">
<div class="col-sm-6 col-md-8">
<form class="form" method="post">
{% csrf_token %}
{{ form|crispy }}
@ -21,4 +36,23 @@
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// First, create the spinner div (or use the existing one)
const spinner = document.createElement('div');
spinner.id = 'spinner';
spinner.className = 'htmx-indicator spinner-border inline-spinner';
spinner.setAttribute('role', 'status');
spinner.innerHTML = '<span class="visually-hidden">Loading...</span>';
// Find the form field you want to place it next to
// Replace 'id_your_field_name' with the actual ID of your form field
const targetField = document.getElementById('div_id_id_car_model');
if (targetField) {
// Insert the spinner right after the target field
targetField.parentNode.insertBefore(spinner, targetField.nextSibling);
}
});
</script>
{% endblock %}

View File

@ -132,9 +132,7 @@
{{ _("Action") }}
</th>
<th class="text-end white-space-nowrap align-middle" scope="col"></th>
</tr>
</thead>
<tbody class="list" id="lead-tables-body">
</tr>image
{% for lead in leads %}
<!-- Delete Modal -->
<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
@ -159,7 +157,7 @@
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="name align-middle white-space-nowrap ps-0">
<div class="d-flex align-items-center">
<div><a class="fs-8 fw-bold" href="{% url 'lead_detail' lead.slug %}">{{lead.full_name}}</a>
<div><a class="fs-8 fw-bold" href="{% url 'lead_detail' lead.slug %}">{{lead.full_name|capfirst}}</a>
<div class="d-flex align-items-center">
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2"></p>
{% if lead.status == "new" %}

View File

@ -41,7 +41,11 @@
<div class="card-body d-flex flex-column justify-content-between pb-3">
<div class="row align-items-center g-5 mb-3 text-center text-sm-start">
<div class="col-12 col-sm-auto mb-sm-2">
<div class="avatar avatar-5xl"><img class="rounded-circle" src="{{ customer.image.url }}" alt="" /></div>
<div class="avatar avatar-5xl">
{% if customer.image %}
<img class="rounded-circle" src="{{ customer.image.url }}" alt="" />
{% endif %}
</div>
</div>
<div class="col-12 col-sm-auto flex-1">
<h3>{{ customer.full_name }}</h3>

View File

@ -20,7 +20,7 @@
{% if not car.ready %}
<div class="alert alert-outline-warning d-flex align-items-center" role="alert">
<i class="fa-solid fa-circle-info fs-6"></i>
<p class="mb-0 flex-1">{{ _("This car information is not complete , please add colors and finances before making it ready for sale .") }}</p>
<p class="mb-0 flex-1">{{ _("This car information is not complete , please add colors and finances before making it ready for sale .") }}<a class="ms-3 text-body-primary fs-9" href="{% url 'add_color' car.slug %}"> {{ _("Add Color") }} </a></p>
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}

View File

@ -11,23 +11,24 @@
<a href="{% url 'bank_account_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Bank Account" %}</a>
</div>
<div class="table-responsive px-1 scrollbar">
<table class="table fs-9 mb-0 border-top border-translucent">
<div class="table-responsive px-1 scrollbar mt-3">
<table class="table align-items-center table-flush">
<thead>
<tr>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Name" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account Number" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Type" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Action" %}</th>
<tr class="bg-body-highlight">
<th class="border-top border-translucent ps-3">{% trans "Name" %}</th>
<th class="border-top border-translucent ps-3">{% trans "Account Number" %}</th>
<th class="border-top border-translucent text-end pe-3">{% trans "Type" %}</th>
<th class="border-top border-translucent text-end pe-3" scope="col">{% trans "Action" %}</th>
</tr>
</thead>
<tbody class="list">
{% for bank in bank_accounts %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle product white-space-nowrap">{{ bank.name }}</td>
<td class="align-middle product white-space-nowrap py-0">{{ bank.account_number }}</td>
<td class="align-middle product white-space-nowrap py-0">{{ bank.account_type|capfirst }}</td>
<td class="">
<td class="align-middle ps-3">{{ bank.name }}</td>
<td class="align-middle ps-3">{{ bank.account_number }}</td>
<td class="align-middle product text-end pe-3 ">{{ bank.account_type|capfirst }}</td>
<td class="align-middle product text-end pe-3 ">
<a href="{% url 'bank_account_update' bank.pk %}"
class="btn btn-sm btn-phoenix-success">
{% trans "Update" %}
@ -42,8 +43,14 @@
</tbody>
</table>
</div>
<div class="d-flex justify-content-center">
<div class="d-flex justify-content-center">
</div>
</div>
{% endblock %}

View File

@ -16,7 +16,7 @@
<div class="table-responsive px-1 scrollbar mt-3">
<table class="table fs-9 mb-0 border-top border-translucent">
<thead>
<tr>
<tr class="bg-body-highlight">
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Ledger Name" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Journal Entries" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Created Date" %}</th>

View File

@ -10,7 +10,7 @@
<h1 class="mb-2 mb-lg-0">{% trans "Payment Failed" %}</h1>
<nav class="breadcrumbs">
<ol>
<li><a href="">{% trans "Home"%}</a></li>
<li><a href="{% url 'home' %}">{% trans "Home"%}</a></li>
<li class="current">{% trans "Failed"%}</li>
</ol>
</nav>
@ -22,13 +22,13 @@
<div class="container text-center" data-aos="fade-up">
<div class="py-5">
<i class="bi bi-x-circle-fill text-danger" style="font-size: 5rem;"></i>
<h2 class="mt-4">{% trans "Payment Failed"%}</h2>
<h2 class="mt-4">{% trans "Payment Failed"%}</h2>
{% if message %}
<p class="lead">{{message}}.</p>
{% else %}
<p class="lead">{% trans "We couldn't process your payment. Please try again"%}.</p>
{% endif %}
<a href="" class="btn btn-primary mt-3">
<a href="{% url 'home' %}" class="btn btn-primary mt-3">
<i class="bi bi-house-door"></i> {% trans "Back to Home"%}
</a>
</div>

View File

@ -29,7 +29,7 @@
<i class="bi bi-house-door"></i> View Invoice
</a>
{% endif %}
<a href="" class="btn btn-primary mt-3">
<a href="{% url 'home' %}" class="btn btn-primary mt-3">
<i class="bi bi-house-door"></i> Back to Home
</a>
</div>

View File

@ -52,7 +52,6 @@
<h1 class="text-center mb-5">{{ _("Choose Your Plan")}}</h1>
<form method="POST" action="{% url 'submit_plan' %}" id="wizardForm">
{% csrf_token %}
<!-- Step 1: Plan Selection -->
<div class="step" id="step1">
<h4 class="mb-4">1. {{ _("Select a Plan")}}</h4>
@ -208,6 +207,27 @@
{% block customJS %}
<script>
document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById('wizardForm');
form.addEventListener('submit', function(e) {
e.preventDefault(); // Prevent default form submission
// Show loading alert
Swal.fire({
title: 'Processing...',
html: 'Please wait while we submit your form',
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
// Submit the form after a slight delay to ensure Swal is shown
setTimeout(() => {
form.submit();
}, 100);
});
const radios = document.querySelectorAll('.btn-check');
radios.forEach(radio => {
radio.addEventListener('change', function () {

View File

@ -17,14 +17,14 @@
{% if not items %}
<div class="alert alert-outline-warning d-flex align-items-center" role="alert">
<i class="fa-solid fa-circle-info fs-6"></i>
<p class="mb-0 flex-1">{{ _("Please add at least one car before creating a quotation.") }}</p>
<p class="mb-0 flex-1">{{ _("Please add at least one car before creating a quotation.") }}<a class="ms-3 text-body-primary fs-9" href="{% url 'car_add' %}"> {{ _("Add Car") }} </a></p>
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
{% if not customer_count %}
<div class="alert alert-outline-warning d-flex align-items-center" role="alert">
<i class="fa-solid fa-circle-info fs-6"></i>&nbsp;&nbsp;
<p class="mb-0 flex-1"> {{ _("Please add at least one customer before creating a quotation.") }}</p>
<p class="mb-0 flex-1"> {{ _("Please add at least one customer before creating a quotation.") }}<a class="ms-3 text-body-primary fs-9" href="{% url 'customer_create' %}"> {{ _("Add Customer") }} </a></p>
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
@ -57,7 +57,7 @@
<div class="col-12">
<button id="addMoreBtn" class="btn btn-sm btn-primary"><i class="fa-solid fa-plus me-2"></i> {{ _("Add More")}}</button>
</div>
</div>
</div>
@ -67,7 +67,7 @@
<a href="{% url 'estimate_list' %}" class="btn btn-danger"><i class="fa-solid fa-ban me-1"></i> {% trans "Cancel" %}</a>
</div> {% endcomment %}
<div class="d-flex justify-content-center">
<button class="btn btn-sm btn-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>
<!--<i class="bi bi-save"></i> -->
{{ _("Save") }}
@ -75,9 +75,9 @@
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
{% endblock content %}