reformat html templates
This commit is contained in:
parent
0c81022e7c
commit
55a7ec1158
@ -41,7 +41,7 @@
|
||||
<div class="tab-pane active" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab1" id="bootstrap-wizard-validation-tab1">
|
||||
<form class="needs-validation" id="wizardValidationForm1" novalidate="novalidate" data-wizard-form="1">
|
||||
{{form1|crispy}}
|
||||
<a class="fs-10 text-decoration-none" href="{% url 'terms_and_privacy' %}" target="_blank">{{ _("Read Terms of Service and Privacy Policy")}}</a>
|
||||
<a class="fs-10 text-decoration-none" href="{% url 'terms_and_privacy' %}" target="_blank">{{ _("Read Terms of Service and Privacy Policy")}}</a>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab2" id="bootstrap-wizard-validation-tab2">
|
||||
|
||||
@ -1,74 +1,74 @@
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% load i18n custom_filters %}
|
||||
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
||||
{% block accounts %}
|
||||
<a class="nav-link active fw-bold">
|
||||
{% trans "Accounts"|capfirst %}
|
||||
<span class="visually-hidden">(current)</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h3 class=""><i class="fas fa-right-to-bracket me-2"></i> {% trans "Audit Log Dashboard" %}</h3>
|
||||
</div>
|
||||
|
||||
<!-- Log Type Tabs -->
|
||||
<div class="mb-4">
|
||||
{% include 'admin_management/nav.html' %}
|
||||
|
||||
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
||||
<!-- modellogs Tab -->
|
||||
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class= "table align-items-center table-flush table-hover">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Timestamp") |capfirst }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("User") |capfirst }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Event Type") }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("username") |capfirst }}</th>
|
||||
<th class="sort white-space-nowrap align-middle"scope="col">{{ _("IP Address") |capfirst }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% for event in page_obj.object_list %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
|
||||
<td class="align-middle product white-space-nowrap">{{event.datetime}}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"N/A" }}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.get_login_type_display}}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.username}}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.remote_ip}}</td>
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% include 'partials/pagination.html' with q='loginEvents' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<p>No authentication audit events found.</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% load i18n custom_filters %}
|
||||
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
||||
{% block accounts %}
|
||||
<a class="nav-link active fw-bold">
|
||||
{% trans "Accounts"|capfirst %}
|
||||
<span class="visually-hidden">(current)</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h3 class=""><i class="fas fa-right-to-bracket me-2"></i> {% trans "Audit Log Dashboard" %}</h3>
|
||||
</div>
|
||||
|
||||
<!-- Log Type Tabs -->
|
||||
<div class="mb-4">
|
||||
{% include 'admin_management/nav.html' %}
|
||||
|
||||
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
||||
<!-- modellogs Tab -->
|
||||
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class= "table align-items-center table-flush table-hover">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Timestamp") |capfirst }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("User") |capfirst }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Event Type") }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("username") |capfirst }}</th>
|
||||
<th class="sort white-space-nowrap align-middle"scope="col">{{ _("IP Address") |capfirst }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% for event in page_obj.object_list %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
|
||||
<td class="align-middle product white-space-nowrap">{{event.datetime}}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"N/A" }}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.get_login_type_display}}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.username}}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.remote_ip}}</td>
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% include 'partials/pagination.html' with q='loginEvents' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<p>No authentication audit events found.</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -2,28 +2,28 @@
|
||||
{% load i18n %}
|
||||
{%block title%} {%trans 'Admin Management' %} {%endblock%}
|
||||
{% block content %}
|
||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 g-4 mt-10">
|
||||
<div class="col">
|
||||
<a href="{% url 'user_management' %}">
|
||||
<div class="card h-100">
|
||||
<div class="card-header text-center">
|
||||
<h5 class="card-title">{{ _("User Management")}}</h5>
|
||||
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 g-4 mt-10">
|
||||
<div class="col">
|
||||
<a href="{% url 'user_management' %}">
|
||||
<div class="card h-100">
|
||||
<div class="card-header text-center">
|
||||
<h5 class="card-title">{{ _("User Management")}}</h5>
|
||||
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<a href="{% url 'audit_log_dashboard' %}">
|
||||
<div class="card h-100">
|
||||
<div class="card-header text-center">
|
||||
<h5 class="card-title">{{ _("Audit Log Dashboard")}}</h5>
|
||||
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<a href="{% url 'audit_log_dashboard' %}">
|
||||
<div class="card h-100">
|
||||
<div class="card-header text-center">
|
||||
<h5 class="card-title">{{ _("Audit Log Dashboard")}}</h5>
|
||||
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
@ -1,133 +1,133 @@
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% load i18n custom_filters %}
|
||||
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
||||
{% block accounts %}
|
||||
<a class="nav-link active fw-bold">
|
||||
{% trans "Accounts"|capfirst %}
|
||||
<span class="visually-hidden">(current)</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h3 class=""><i class="fas fa-history me-2"></i>{% trans "Audit Log Dashboard" %}</h3>
|
||||
</div>
|
||||
|
||||
<!-- Log Type Tabs -->
|
||||
<div class="mb-4">
|
||||
{% include 'admin_management/nav.html' %}
|
||||
|
||||
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
||||
<!-- modellogs Tab -->
|
||||
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class="table align-items-center table-flush table-hover mt-3">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th>{% trans "Timestamp" %}</th>
|
||||
<th>{% trans "User" %}</th>
|
||||
<th>{% trans "Action" %}</th>
|
||||
<th>{% trans "Model" %}</th>
|
||||
<th>{% trans "Object ID" %}</th>
|
||||
<th>{% trans "Object Representation" %}</th>
|
||||
<th>{% trans "Field" %}</th> {# Dedicated column for field name #}
|
||||
<th>{% trans "Old Value" %}</th> {# Dedicated column for old value #}
|
||||
<th>{% trans "New Value" %}</th> {# Dedicated column for new value #}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for event in page_obj.object_list %}
|
||||
{% if event.field_changes %}
|
||||
{# Loop through each individual field change for this event #}
|
||||
{% for change in event.field_changes %}
|
||||
<tr>
|
||||
{# Display common event details using rowspan for the first change #}
|
||||
{% if forloop.first %}
|
||||
<td rowspan="{{ event.field_changes|length }}">
|
||||
{{ event.datetime|date:"Y-m-d H:i:s" }}
|
||||
</td>
|
||||
<td rowspan="{{ event.field_changes|length }}">
|
||||
{{ event.user.username|default:"Anonymous" }}
|
||||
</td>
|
||||
<td rowspan="{{ event.field_changes|length }}">
|
||||
{{ event.event_type_display }}
|
||||
</td>
|
||||
<td rowspan="{{ event.field_changes|length }}">
|
||||
{{ event.model_name|title }}
|
||||
</td>
|
||||
<td rowspan="{{ event.field_changes|length }}">
|
||||
{{ event.object_id }}
|
||||
</td>
|
||||
<td rowspan="{{ event.field_changes|length }}">
|
||||
{{ event.object_repr }}
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
{# Display the specific field change details in their own columns #}
|
||||
<td><strong>{{ change.field }}</strong></td>
|
||||
<td>
|
||||
{% if change.old is not None %}
|
||||
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.old }}</pre>
|
||||
{% else %}
|
||||
(None)
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if change.new is not None %}
|
||||
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.new }}</pre>
|
||||
{% else %}
|
||||
(None)
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{# Fallback for events with no specific field changes (e.g., CREATE, DELETE) #}
|
||||
<tr>
|
||||
<td>{{ event.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td>{{ event.user.username|default:"Anonymous" }}</td>
|
||||
<td>{{ event.event_type_display }}</td>
|
||||
<td>{{ event.model_name|title }}</td>
|
||||
<td>{{ event.object_id }}</td>
|
||||
<td>{{ event.object_repr }}</td>
|
||||
{# Span the 'Field', 'Old Value', 'New Value' columns #}
|
||||
<td>
|
||||
{% if event.event_type_display == "Create" %}
|
||||
{% trans "Object created." %}
|
||||
{% elif event.event_type_display == "Delete" %}
|
||||
{% trans "Object deleted." %}
|
||||
{% else %}
|
||||
{% trans "No specific field changes recorded." %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% include 'partials/pagination.html' with q='userActions' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<p>{% trans "No model change audit events found." %}</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% load i18n custom_filters %}
|
||||
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
||||
{% block accounts %}
|
||||
<a class="nav-link active fw-bold">
|
||||
{% trans "Accounts"|capfirst %}
|
||||
<span class="visually-hidden">(current)</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h3 class=""><i class="fas fa-history me-2"></i>{% trans "Audit Log Dashboard" %}</h3>
|
||||
</div>
|
||||
|
||||
<!-- Log Type Tabs -->
|
||||
<div class="mb-4">
|
||||
{% include 'admin_management/nav.html' %}
|
||||
|
||||
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
||||
<!-- modellogs Tab -->
|
||||
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class="table align-items-center table-flush table-hover mt-3">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th>{% trans "Timestamp" %}</th>
|
||||
<th>{% trans "User" %}</th>
|
||||
<th>{% trans "Action" %}</th>
|
||||
<th>{% trans "Model" %}</th>
|
||||
<th>{% trans "Object ID" %}</th>
|
||||
<th>{% trans "Object Representation" %}</th>
|
||||
<th>{% trans "Field" %}</th> {# Dedicated column for field name #}
|
||||
<th>{% trans "Old Value" %}</th> {# Dedicated column for old value #}
|
||||
<th>{% trans "New Value" %}</th> {# Dedicated column for new value #}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for event in page_obj.object_list %}
|
||||
{% if event.field_changes %}
|
||||
{# Loop through each individual field change for this event #}
|
||||
{% for change in event.field_changes %}
|
||||
<tr>
|
||||
{# Display common event details using rowspan for the first change #}
|
||||
{% if forloop.first %}
|
||||
<td rowspan="{{ event.field_changes|length }}">
|
||||
{{ event.datetime|date:"Y-m-d H:i:s" }}
|
||||
</td>
|
||||
<td rowspan="{{ event.field_changes|length }}">
|
||||
{{ event.user.username|default:"Anonymous" }}
|
||||
</td>
|
||||
<td rowspan="{{ event.field_changes|length }}">
|
||||
{{ event.event_type_display }}
|
||||
</td>
|
||||
<td rowspan="{{ event.field_changes|length }}">
|
||||
{{ event.model_name|title }}
|
||||
</td>
|
||||
<td rowspan="{{ event.field_changes|length }}">
|
||||
{{ event.object_id }}
|
||||
</td>
|
||||
<td rowspan="{{ event.field_changes|length }}">
|
||||
{{ event.object_repr }}
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
{# Display the specific field change details in their own columns #}
|
||||
<td><strong>{{ change.field }}</strong></td>
|
||||
<td>
|
||||
{% if change.old is not None %}
|
||||
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.old }}</pre>
|
||||
{% else %}
|
||||
(None)
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if change.new is not None %}
|
||||
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.new }}</pre>
|
||||
{% else %}
|
||||
(None)
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{# Fallback for events with no specific field changes (e.g., CREATE, DELETE) #}
|
||||
<tr>
|
||||
<td>{{ event.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td>{{ event.user.username|default:"Anonymous" }}</td>
|
||||
<td>{{ event.event_type_display }}</td>
|
||||
<td>{{ event.model_name|title }}</td>
|
||||
<td>{{ event.object_id }}</td>
|
||||
<td>{{ event.object_repr }}</td>
|
||||
{# Span the 'Field', 'Old Value', 'New Value' columns #}
|
||||
<td>
|
||||
{% if event.event_type_display == "Create" %}
|
||||
{% trans "Object created." %}
|
||||
{% elif event.event_type_display == "Delete" %}
|
||||
{% trans "Object deleted." %}
|
||||
{% else %}
|
||||
{% trans "No specific field changes recorded." %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% include 'partials/pagination.html' with q='userActions' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<p>{% trans "No model change audit events found." %}</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
{% load i18n %}
|
||||
<ul class="nav nav-tabs" id="accountTypeTabs" role="tablist">
|
||||
|
||||
<li class="nav-item me-3" role="presentation">
|
||||
<a href="{% url 'audit_log_dashboard' %}?q=userActions">
|
||||
<i class="fas fa-history me-2"></i>{% trans "User Actions" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item me-3" role="presentation">
|
||||
<a href="{% url 'audit_log_dashboard' %}?q=loginEvents">
|
||||
<i class="fas fa-right-to-bracket me-2"></i>{% trans "User Login Events" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a href="{% url 'audit_log_dashboard' %}?q=userRequests">
|
||||
<i class="fas fa-file-alt me-2"></i>{% trans "User Page Requests" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% load i18n %}
|
||||
<ul class="nav nav-tabs" id="accountTypeTabs" role="tablist">
|
||||
|
||||
<li class="nav-item me-3" role="presentation">
|
||||
<a href="{% url 'audit_log_dashboard' %}?q=userActions">
|
||||
<i class="fas fa-history me-2"></i>{% trans "User Actions" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item me-3" role="presentation">
|
||||
<a href="{% url 'audit_log_dashboard' %}?q=loginEvents">
|
||||
<i class="fas fa-right-to-bracket me-2"></i>{% trans "User Login Events" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a href="{% url 'audit_log_dashboard' %}?q=userRequests">
|
||||
<i class="fas fa-file-alt me-2"></i>{% trans "User Page Requests" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -1,69 +1,69 @@
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% load i18n custom_filters %}
|
||||
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
||||
{% block accounts %}
|
||||
<a class="nav-link active fw-bold">
|
||||
{% trans "Accounts"|capfirst %}
|
||||
<span class="visually-hidden">(current)</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h3 class=""><i class="fas fa-file-alt me-2"></i> {% trans "Audit Log Dashboard" %}</h3>
|
||||
</div>
|
||||
|
||||
<!-- Log Type Tabs -->
|
||||
<div class="mb-4">
|
||||
{% include 'admin_management/nav.html' %}
|
||||
|
||||
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
||||
<!-- modellogs Tab -->
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class= "table align-items-center table-flush table-hover">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Timestamp") |capfirst }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("User") |capfirst }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("URL") }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Method") |capfirst }}</th>
|
||||
<th class="sort white-space-nowrap align-middle"scope="col">{{ _("IP Address") |capfirst }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% for event in page_obj.object_list %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
|
||||
<td class="align-middle product white-space-nowrap">{{event.datetime}}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"Anonymous" }}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.url }}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.method}}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.remote_ip}}</td>
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% include 'partials/pagination.html' with q='userRequests' %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p>No request audit events found.</p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% load i18n custom_filters %}
|
||||
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
||||
{% block accounts %}
|
||||
<a class="nav-link active fw-bold">
|
||||
{% trans "Accounts"|capfirst %}
|
||||
<span class="visually-hidden">(current)</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h3 class=""><i class="fas fa-file-alt me-2"></i> {% trans "Audit Log Dashboard" %}</h3>
|
||||
</div>
|
||||
|
||||
<!-- Log Type Tabs -->
|
||||
<div class="mb-4">
|
||||
{% include 'admin_management/nav.html' %}
|
||||
|
||||
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
||||
<!-- modellogs Tab -->
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class= "table align-items-center table-flush table-hover">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Timestamp") |capfirst }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("User") |capfirst }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("URL") }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Method") |capfirst }}</th>
|
||||
<th class="sort white-space-nowrap align-middle"scope="col">{{ _("IP Address") |capfirst }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% for event in page_obj.object_list %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
|
||||
<td class="align-middle product white-space-nowrap">{{event.datetime}}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"Anonymous" }}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.url }}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.method}}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ event.remote_ip}}</td>
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% include 'partials/pagination.html' with q='userRequests' %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p>No request audit events found.</p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -65,13 +65,13 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-5">
|
||||
<div class="col-12">
|
||||
@ -129,13 +129,13 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-5">
|
||||
<div class="col-12">
|
||||
@ -193,13 +193,13 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-5">
|
||||
<div class="col-12">
|
||||
@ -257,13 +257,13 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
<link href="{% static 'vendors/flatpickr/flatpickr.min.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/custom.css' %}" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intro.js/7.2.0/introjs.css" integrity="sha512-4OzqLjfh1aJa7M33b5+h0CSx0Q3i9Qaxlrr1T/Z+Vz+9zs5A7GM3T3MFKXoreghi3iDOSbkPMXiMBhFO7UBW/g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intro.js/7.2.0/introjs.css" integrity="sha512-4OzqLjfh1aJa7M33b5+h0CSx0Q3i9Qaxlrr1T/Z+Vz+9zs5A7GM3T3MFKXoreghi3iDOSbkPMXiMBhFO7UBW/g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
{% if LANGUAGE_CODE == 'ar' %}
|
||||
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
|
||||
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
|
||||
@ -100,9 +100,9 @@
|
||||
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/intro.js/7.2.0/intro.js" integrity="sha512-f26fxKZJiF0AjutUaQHNJ5KnXSisqyUQ3oyfaoen2apB1wLa5ccW3lmtaRe2jdP5kh4LF2gAHP9xQbx7wYhU5w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<!-- Tour Manager -->
|
||||
<script src="{% static 'js/tours/tour-manager.js' %}"></script>
|
||||
<script src="{% static 'js/tours/tour-manager.js' %}"></script>
|
||||
|
||||
<script src="{% static 'js/tours/help-button.js' %}"></script>
|
||||
<script src="{% static 'js/tours/help-button.js' %}"></script>
|
||||
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
|
||||
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
|
||||
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
|
||||
|
||||
@ -7,236 +7,236 @@
|
||||
{% block title %}Bill Details - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
<style>
|
||||
/* Optional custom overrides for Bootstrap 5 */
|
||||
.table th,
|
||||
.table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.table th,
|
||||
.table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.card-header i {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.card-header i {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.text-xs {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.text-xs {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.text-xxs {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
.text-xxs {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
#djl-vendor-card-widget{
|
||||
#djl-vendor-card-widget{
|
||||
|
||||
max-height:30rem;
|
||||
}
|
||||
max-height:30rem;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row g-4" >
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row g-4" >
|
||||
<!-- Left Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm" >
|
||||
<div class="card-body">
|
||||
{% include 'bill/includes/card_bill.html' with bill=bill entity_slug=view.kwargs.entity_slug style='bill-detail' %}
|
||||
<hr class="my-4">
|
||||
{% include 'bill/includes/card_vendor.html' with vendor=bill.vendor %}
|
||||
<div class="d-grid mt-4">
|
||||
<a href="{% url 'bill_list' %}"
|
||||
class="btn btn-phoenix-primary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans 'Bill List' %}
|
||||
</a>
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm" >
|
||||
<div class="card-body">
|
||||
{% include 'bill/includes/card_bill.html' with bill=bill entity_slug=view.kwargs.entity_slug style='bill-detail' %}
|
||||
<hr class="my-4">
|
||||
{% include 'bill/includes/card_vendor.html' with vendor=bill.vendor %}
|
||||
<div class="d-grid mt-4">
|
||||
<a href="{% url 'bill_list' %}"
|
||||
class="btn btn-phoenix-primary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans 'Bill List' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="col-lg-8 ">
|
||||
{% if bill.is_configured %}
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="row text-center g-3">
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Cash Account' %}:
|
||||
<a href="{% url 'account_detail' bill.cash_account.uuid %}"
|
||||
class="text-decoration-none ms-1">
|
||||
{{ bill.cash_account.code }}
|
||||
</a>
|
||||
</h6>
|
||||
<h4 class="mb-0" id="djl-bill-detail-amount-paid">
|
||||
{% currency_symbol %}{{ bill.get_amount_cash | absolute | currency_format }}
|
||||
</h4>
|
||||
<div class="col-lg-8 ">
|
||||
{% if bill.is_configured %}
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="row text-center g-3">
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Cash Account' %}:
|
||||
<a href="{% url 'account_detail' bill.cash_account.uuid %}"
|
||||
class="text-decoration-none ms-1">
|
||||
{{ bill.cash_account.code }}
|
||||
</a>
|
||||
</h6>
|
||||
<h4 class="mb-0" id="djl-bill-detail-amount-paid">
|
||||
{% currency_symbol %}{{ bill.get_amount_cash | absolute | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% if bill.accrue %}
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Prepaid Account' %}:
|
||||
<a href="{% url 'account_detail' bill.prepaid_account.uuid %}"
|
||||
class="text-decoration-none ms-1">
|
||||
{{ bill.prepaid_account.code }}
|
||||
</a>
|
||||
</h6>
|
||||
<h4 class="text-success mb-0" id="djl-bill-detail-amount-prepaid">
|
||||
{% currency_symbol %}{{ bill.get_amount_prepaid | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Accounts Payable' %}:
|
||||
<a href="{% url 'account_detail' bill.unearned_account.uuid %}"
|
||||
class="text-decoration-none ms-1">
|
||||
{{ bill.unearned_account.code }}
|
||||
</a>
|
||||
</h6>
|
||||
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-unearned">
|
||||
{% currency_symbol %}{{ bill.get_amount_unearned | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Accrued' %} {{ bill.get_progress | percentage }}
|
||||
</h6>
|
||||
<h4 class="mb-0">
|
||||
{% currency_symbol %}{{ bill.get_amount_earned | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-md-3 offset-md-6">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'You Still Owe' %}
|
||||
</h6>
|
||||
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-owed">
|
||||
{% currency_symbol %}{{ bill.get_amount_open | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if bill.accrue %}
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Prepaid Account' %}:
|
||||
<a href="{% url 'account_detail' bill.prepaid_account.uuid %}"
|
||||
class="text-decoration-none ms-1">
|
||||
{{ bill.prepaid_account.code }}
|
||||
</a>
|
||||
</h6>
|
||||
<h4 class="text-success mb-0" id="djl-bill-detail-amount-prepaid">
|
||||
{% currency_symbol %}{{ bill.get_amount_prepaid | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Accounts Payable' %}:
|
||||
<a href="{% url 'account_detail' bill.unearned_account.uuid %}"
|
||||
class="text-decoration-none ms-1">
|
||||
{{ bill.unearned_account.code }}
|
||||
</a>
|
||||
</h6>
|
||||
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-unearned">
|
||||
{% currency_symbol %}{{ bill.get_amount_unearned | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Accrued' %} {{ bill.get_progress | percentage }}
|
||||
</h6>
|
||||
<h4 class="mb-0">
|
||||
{% currency_symbol %}{{ bill.get_amount_earned | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-md-3 offset-md-6">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'You Still Owe' %}
|
||||
</h6>
|
||||
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-owed">
|
||||
{% currency_symbol %}{{ bill.get_amount_open | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Bill Items Card -->
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<i class="fas fa-receipt me-3 text-primary"></i>
|
||||
<h5 class="mb-0">{% trans 'Bill Items' %}</h5>
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<i class="fas fa-receipt me-3 text-primary"></i>
|
||||
<h5 class="mb-0">{% trans 'Bill Items' %}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body px-0 pt-0 pb-2">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead >
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Item' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Entity Unit' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Unit Cost' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Quantity' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Total' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle " scope="col">{% trans 'PO' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list" id="project-list-table-body">
|
||||
{% for bill_item in itemtxs_qs %}
|
||||
<tr>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<div class="d-flex px-2 py-1">
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
<h6 class="mb-0 text-sm">{{ bill_item.item_model }}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{% if bill_item.entity_unit %}
|
||||
{{ bill_item.entity_unit }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{% currency_symbol %}{{ bill_item.unit_cost | currency_format }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">{{ bill_item.quantity }}</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{% currency_symbol %}{{ bill_item.total_amount | currency_format }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap pe-2">
|
||||
{% if bill_item.po_model_id %}
|
||||
<a class="btn btn-sm btn-phoenix-primary"
|
||||
href="{% url 'purchase_order_detail' bill_item.po_model_id %}">
|
||||
{% trans 'View PO' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3"></td>
|
||||
<td class="text-end"><strong>{% trans 'Total' %}</strong></td>
|
||||
<td class="text-end">
|
||||
<strong>
|
||||
{% currency_symbol %}{{ total_amount__sum | currency_format }}
|
||||
</strong>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body px-0 pt-0 pb-2">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead >
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Item' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Entity Unit' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Unit Cost' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Quantity' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Total' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle " scope="col">{% trans 'PO' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list" id="project-list-table-body">
|
||||
{% for bill_item in itemtxs_qs %}
|
||||
<tr>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<div class="d-flex px-2 py-1">
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
<h6 class="mb-0 text-sm">{{ bill_item.item_model }}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{% if bill_item.entity_unit %}
|
||||
{{ bill_item.entity_unit }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{% currency_symbol %}{{ bill_item.unit_cost | currency_format }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">{{ bill_item.quantity }}</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{% currency_symbol %}{{ bill_item.total_amount | currency_format }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap pe-2">
|
||||
{% if bill_item.po_model_id %}
|
||||
<a class="btn btn-sm btn-phoenix-primary"
|
||||
href="{% url 'purchase_order_detail' bill_item.po_model_id %}">
|
||||
{% trans 'View PO' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3"></td>
|
||||
<td class="text-end"><strong>{% trans 'Total' %}</strong></td>
|
||||
<td class="text-end">
|
||||
<strong>
|
||||
{% currency_symbol %}{{ total_amount__sum | currency_format }}
|
||||
</strong>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bill Transactions Card -->
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<i class="fas fa-exchange-alt me-3 text-primary"></i>
|
||||
<h5 class="mb-0">{% trans 'Bill Transactions' %}</h5>
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<i class="fas fa-exchange-alt me-3 text-primary"></i>
|
||||
<h5 class="mb-0">{% trans 'Bill Transactions' %}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body px-0 pt-0 pb-2 table-responsive">
|
||||
{% transactions_table bill %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body px-0 pt-0 pb-2 table-responsive">
|
||||
{% transactions_table bill %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bill Notes Card -->
|
||||
<div class="card shadow-sm ">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<i class="fas fa-sticky-note me-3 text-primary"></i>
|
||||
<h5 class="mb-0">{% trans 'Bill Notes' %}</h5>
|
||||
<div class="card shadow-sm ">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<i class="fas fa-sticky-note me-3 text-primary"></i>
|
||||
<h5 class="mb-0">{% trans 'Bill Notes' %}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% include 'bill/includes/card_markdown.html' with style='card_1' title='' notes_html=bill.notes_html %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% include 'bill/includes/card_markdown.html' with style='card_1' title='' notes_html=bill.notes_html %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "bill/includes/mark_as.html" %}
|
||||
{% include "bill/includes/mark_as.html" %}
|
||||
{% endblock %}
|
||||
@ -6,52 +6,52 @@
|
||||
{% load widget_tweaks crispy_forms_filters %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<div class="row g-4">
|
||||
<div class="container py-4">
|
||||
<div class="row g-4">
|
||||
<!-- Vendor Card -->
|
||||
<div class="col-12">
|
||||
{% include 'bill/includes/card_vendor.html' with vendor=bill_model.vendor %}
|
||||
</div>
|
||||
<div class="col-12">
|
||||
{% include 'bill/includes/card_vendor.html' with vendor=bill_model.vendor %}
|
||||
</div>
|
||||
|
||||
<!-- Bill Form -->
|
||||
<div class="col-12">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
{% include 'bill/includes/card_bill.html' with bill=bill_model style='bill-detail' entity_slug=view.kwargs.entity_slug %}
|
||||
<div class="col-12">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
{% include 'bill/includes/card_bill.html' with bill=bill_model style='bill-detail' entity_slug=view.kwargs.entity_slug %}
|
||||
|
||||
<form action="{% url 'bill-update' entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}" method="post">
|
||||
{% csrf_token %}
|
||||
<form action="{% url 'bill-update' entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form|crispy }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
{{ form|crispy }}
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-phoenix-primary w-100 mb-2">
|
||||
<i class="fas fa-save me-2"></i>{% trans 'Save Bill' %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-phoenix-primary w-100 mb-2">
|
||||
<i class="fas fa-save me-2"></i>{% trans 'Save Bill' %}
|
||||
</button>
|
||||
|
||||
<a href="{% url 'bill-detail' entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}"
|
||||
class="btn btn-phoenix-secondary w-100 mb-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>{% trans 'Back to Bill Detail' %}
|
||||
</a>
|
||||
<a href="{% url 'bill-detail' entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}"
|
||||
class="btn btn-phoenix-secondary w-100 mb-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>{% trans 'Back to Bill Detail' %}
|
||||
</a>
|
||||
|
||||
<a href="{% url 'bill_list' %}"
|
||||
class="btn btn-phoenix-info w-100 mb-2">
|
||||
<i class="fas fa-list me-2"></i>{% trans 'Bill List' %}
|
||||
</a>
|
||||
<a href="{% url 'bill_list' %}"
|
||||
class="btn btn-phoenix-info w-100 mb-2">
|
||||
<i class="fas fa-list me-2"></i>{% trans 'Bill List' %}
|
||||
</a>
|
||||
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bill Item Formset -->
|
||||
<div class="col-12">
|
||||
{% bill_item_formset_table itemtxs_formset %}
|
||||
<div class="col-12">
|
||||
{% bill_item_formset_table itemtxs_formset %}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% include "bill/includes/mark_as.html" %}
|
||||
{% endblock %}
|
||||
@ -201,9 +201,9 @@
|
||||
<div class="card-footer p-0">
|
||||
<div class="d-flex flex-wrap gap-2 mt-2">
|
||||
<!-- Update Button -->
|
||||
<a href="{% url 'bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}" class="btn btn-phoenix-primary">
|
||||
<a href="{% url 'bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}" class="btn btn-phoenix-primary">
|
||||
<i class="fas fa-edit me-2"></i>{% trans 'Update' %}
|
||||
</a>
|
||||
</a>
|
||||
<!-- Mark as Draft -->
|
||||
{% if bill.can_draft %}
|
||||
<button class="btn btn-phoenix-success"
|
||||
@ -267,29 +267,29 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.card-footer .btn-link {
|
||||
padding: 1rem;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.card-footer .btn-link:hover {
|
||||
background-color: rgba(0,0,0,0.03);
|
||||
}
|
||||
.card-footer .btn-link {
|
||||
padding: 1rem;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.card-footer .btn-link:hover {
|
||||
background-color: rgba(0,0,0,0.03);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||
const modalEl = document.getElementById('POModal');
|
||||
if (!modalEl) {
|
||||
console.error('Modal element not found');
|
||||
return;
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||
const modalEl = document.getElementById('POModal');
|
||||
if (!modalEl) {
|
||||
console.error('Modal element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
document.getElementById('POModalTitle').textContent = title;
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
document.getElementById('POModalTitle').textContent = title;
|
||||
|
||||
document.getElementById('POModalBody').innerHTML = `
|
||||
document.getElementById('POModalBody').innerHTML = `
|
||||
<div class="d-flex justify-content-center gap-3 py-3">
|
||||
<a class="btn btn-phoenix-primary px-4" href="${actionUrl}">
|
||||
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
||||
@ -300,7 +300,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.show();
|
||||
};
|
||||
});
|
||||
modal.show();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
@ -2,22 +2,22 @@
|
||||
{% load django_ledger %}
|
||||
|
||||
{% if style == 'card_1' %}
|
||||
<div class="card h-100" style="height: 25rem;">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title fs-3 fw-light mb-0">
|
||||
{% if title %}
|
||||
{{ title }}
|
||||
<div class="card h-100" style="height: 25rem;">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title fs-3 fw-light mb-0">
|
||||
{% if title %}
|
||||
{{ title }}
|
||||
{% else %}
|
||||
{% trans 'Notes' %}
|
||||
{% endif %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body overflow-auto">
|
||||
{% if notes_html %}
|
||||
{{ notes_html|safe }}
|
||||
{% else %}
|
||||
{% trans 'Notes' %}
|
||||
<p class="card-text">{% trans 'No available notes to display...' %}</p>
|
||||
{% endif %}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body overflow-auto">
|
||||
{% if notes_html %}
|
||||
{{ notes_html|safe }}
|
||||
{% else %}
|
||||
<p class="card-text">{% trans 'No available notes to display...' %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@ -43,77 +43,77 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for f in item_formset %}
|
||||
<tr class="align-middle">
|
||||
<tr class="align-middle">
|
||||
<!-- Item Column -->
|
||||
<td>
|
||||
<div class="d-flex flex-column">
|
||||
{% for hidden_field in f.hidden_fields %}
|
||||
{{ hidden_field }}
|
||||
{% endfor %}
|
||||
{{ f.item_model|add_class:"form-control" }}
|
||||
{% if f.errors %}
|
||||
<span class="text-danger text-xs">{{ f.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex flex-column">
|
||||
{% for hidden_field in f.hidden_fields %}
|
||||
{{ hidden_field }}
|
||||
{% endfor %}
|
||||
{{ f.item_model|add_class:"form-control" }}
|
||||
{% if f.errors %}
|
||||
<span class="text-danger text-xs">{{ f.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- PO Quantity -->
|
||||
<td class="text-center">
|
||||
<span class="text-muted text-xs">
|
||||
{% if f.instance.po_quantity %}{{ f.instance.po_quantity }}{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="text-muted text-xs">
|
||||
{% if f.instance.po_quantity %}{{ f.instance.po_quantity }}{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<!-- PO Amount -->
|
||||
<td class="text-center">
|
||||
{% if f.instance.po_total_amount %}
|
||||
<div class="d-flex flex-column">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{% currency_symbol %}{{ f.instance.po_total_amount | currency_format }}
|
||||
</span>
|
||||
<a class="btn btn-sm btn-phoenix-info mt-1"
|
||||
href="{% url 'purchase_order_detail' f.instance.po_model_id %}">
|
||||
{% trans 'View PO' %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if f.instance.po_total_amount %}
|
||||
<div class="d-flex flex-column">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{% currency_symbol %}{{ f.instance.po_total_amount | currency_format }}
|
||||
</span>
|
||||
<a class="btn btn-sm btn-phoenix-info mt-1"
|
||||
href="{% url 'purchase_order_detail' f.instance.po_model_id %}">
|
||||
{% trans 'View PO' %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<!-- Quantity -->
|
||||
<td class="text-center">
|
||||
<div class="input-group input-group-sm w-100">
|
||||
{{ f.quantity|add_class:"form-control" }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="input-group input-group-sm w-100">
|
||||
{{ f.quantity|add_class:"form-control" }}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Unit Cost -->
|
||||
<td class="text-center">
|
||||
<div class="input-group input-group-sm w-100">
|
||||
{{ f.unit_cost|add_class:"form-control" }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="input-group input-group-sm w-100">
|
||||
{{ f.unit_cost|add_class:"form-control" }}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Entity Unit -->
|
||||
<td class="text-center">
|
||||
{{ f.entity_unit|add_class:"form-control" }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ f.entity_unit|add_class:"form-control" }}
|
||||
</td>
|
||||
|
||||
<!-- Total Amount -->
|
||||
<td class="text-end">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{% currency_symbol %}{{ f.instance.total_amount | currency_format }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{% currency_symbol %}{{ f.instance.total_amount | currency_format }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<!-- Delete Checkbox -->
|
||||
<td class="text-center">
|
||||
{% if item_formset.can_delete %}
|
||||
<div class="form-check d-flex justify-content-center">
|
||||
{{ f.DELETE }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<td class="text-center">
|
||||
{% if item_formset.can_delete %}
|
||||
<div class="form-check d-flex justify-content-center">
|
||||
{{ f.DELETE }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@ -142,11 +142,11 @@
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
{% if not item_formset.has_po %}
|
||||
<a href="{% url 'django_ledger:product-create' entity_slug=entity_slug %}"
|
||||
class="btn btn-phoenix-primary">
|
||||
<i class="fas fa-plus me-1"></i>
|
||||
{% trans 'New Item' %}
|
||||
</a>
|
||||
<a href="{% url 'django_ledger:product-create' entity_slug=entity_slug %}"
|
||||
class="btn btn-phoenix-primary">
|
||||
<i class="fas fa-plus me-1"></i>
|
||||
{% trans 'New Item' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<button type="submit" class="btn btn-phoenix-primary">
|
||||
<i class="fas fa-save me-1"></i>
|
||||
|
||||
@ -108,11 +108,11 @@
|
||||
<div class="col-6 col-sm-auto d-flex flex-column align-items-center text-center">
|
||||
<h5 class="fw-bolder mb-2 text-body-highlight">{{ _("Related Records") }}</h5>
|
||||
<h6 class="fw-bolder mb-2 text-body-highlight">{{ _("Opportunity") }}</h6>
|
||||
{% if lead.opportunity %}
|
||||
<a href="{% url 'opportunity_detail' lead.opportunity.slug %}" class="">{{ lead.opportunity }}</a>
|
||||
{% else %}
|
||||
<p>{{ _("No Opportunity") }}</p>
|
||||
{% endif %}
|
||||
{% if lead.opportunity %}
|
||||
<a href="{% url 'opportunity_detail' lead.opportunity.slug %}" class="">{{ lead.opportunity }}</a>
|
||||
{% else %}
|
||||
<p>{{ _("No Opportunity") }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -255,28 +255,28 @@
|
||||
<a href="{% url 'opportunity_create' %}" class="btn btn-phoenix-primary btn-sm" type="button"> <i class="fa-solid fa-plus me-2"></i>{{ _("Add Opportunity") }}</a>
|
||||
</div>
|
||||
|
||||
<div class="border-top border-bottom border-translucent" id="leadDetailsTable">
|
||||
<div class="table-responsive scrollbar mx-n1 px-1">
|
||||
<table class="table fs-9 mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="align-middle pe-6 text-uppercase text-start" scope="col" style="width:40%;">{{ _("Car") }}</th>
|
||||
<th class="align-middle text-start text-uppercase" scope="col" style="width:20%;">{{ _("Probability")}}</th>
|
||||
<th class="align-middle text-start text-uppercase white-space-nowrap" scope="col" style="width:20%;">{{ _("Priority")}}</th>
|
||||
<th class="align-middle pe-0 text-end" scope="col" style="width:10%;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody >
|
||||
{% for opportunity in lead.get_opportunities %}
|
||||
<div class="border-top border-bottom border-translucent" id="leadDetailsTable">
|
||||
<div class="table-responsive scrollbar mx-n1 px-1">
|
||||
<table class="table fs-9 mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="align-middle pe-6 text-uppercase text-start" scope="col" style="width:40%;">{{ _("Car") }}</th>
|
||||
<th class="align-middle text-start text-uppercase" scope="col" style="width:20%;">{{ _("Probability")}}</th>
|
||||
<th class="align-middle text-start text-uppercase white-space-nowrap" scope="col" style="width:20%;">{{ _("Priority")}}</th>
|
||||
<th class="align-middle pe-0 text-end" scope="col" style="width:10%;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody >
|
||||
{% for opportunity in lead.get_opportunities %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{opportunity.car}}</td>
|
||||
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{opportunity.probability}}</td>
|
||||
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{opportunity.priority|capfirst}}</td>
|
||||
<td class="align-middle text-start fw-bold text-body-tertiary ps-1"><a class="btn btn-sm btn-phoenix-primary" href="{% url 'opportunity_detail' opportunity.slug %}">View</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -580,109 +580,91 @@
|
||||
let form = document.querySelector('.add_note_form')
|
||||
form.action = "{% url 'add_note' 'lead' lead.slug %}"
|
||||
}
|
||||
let Toast = Swal.mixin({
|
||||
let Toast = Swal.mixin({
|
||||
toast: true,
|
||||
position: "top-end",
|
||||
showConfirmButton: false,
|
||||
timer: 3000,
|
||||
timerProgressBar: true,
|
||||
didOpen: (toast) => {
|
||||
toast.onmouseenter = Swal.stopTimer;
|
||||
toast.onmouseleave = Swal.resumeTimer;
|
||||
}
|
||||
});
|
||||
|
||||
// Display Django messages
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
Toast.fire({
|
||||
icon: "{{ message.tags }}",
|
||||
titleText: "{{ message|safe }}"
|
||||
});
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
function openActionModal(leadId, currentAction, nextAction, nextActionDate) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('actionTrackingModal'));
|
||||
document.getElementById('leadId').value = leadId;
|
||||
document.getElementById('currentAction').value = currentAction;
|
||||
document.getElementById('nextAction').value = nextAction;
|
||||
document.getElementById('nextActionDate').value = nextActionDate;
|
||||
modal.show();
|
||||
}
|
||||
|
||||
document.getElementById('actionTrackingForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
|
||||
// Show loading indicator
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
icon: 'info',
|
||||
text: 'Please wait...',
|
||||
allowOutsideClick: false,
|
||||
position: "top-end",
|
||||
showConfirmButton: false,
|
||||
timer: 3000,
|
||||
timerProgressBar: true,
|
||||
timer: 2000,
|
||||
timerProgressBar: false,
|
||||
didOpen: (toast) => {
|
||||
toast.onmouseenter = Swal.stopTimer;
|
||||
toast.onmouseleave = Swal.resumeTimer;
|
||||
}
|
||||
});
|
||||
|
||||
// Display Django messages
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
Toast.fire({
|
||||
icon: "{{ message.tags }}",
|
||||
titleText: "{{ message|safe }}"
|
||||
});
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
function openActionModal(leadId, currentAction, nextAction, nextActionDate) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('actionTrackingModal'));
|
||||
document.getElementById('leadId').value = leadId;
|
||||
document.getElementById('currentAction').value = currentAction;
|
||||
document.getElementById('nextAction').value = nextAction;
|
||||
document.getElementById('nextActionDate').value = nextActionDate;
|
||||
modal.show();
|
||||
}
|
||||
|
||||
document.getElementById('actionTrackingForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
|
||||
// Show loading indicator
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
icon: 'info',
|
||||
text: 'Please wait...',
|
||||
allowOutsideClick: false,
|
||||
position: "top-end",
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: false,
|
||||
didOpen: (toast) => {
|
||||
toast.onmouseenter = Swal.stopTimer;
|
||||
toast.onmouseleave = Swal.resumeTimer;
|
||||
}
|
||||
});
|
||||
|
||||
fetch("{% url 'update_lead_actions' %}", {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
Swal.close();
|
||||
if (data.success) {
|
||||
fetch("{% url 'update_lead_actions' %}", {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
Swal.close();
|
||||
if (data.success) {
|
||||
// Success notification
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
icon: 'success',
|
||||
position: "top-end",
|
||||
text: data.message || 'Actions updated successfully',
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: false,
|
||||
didOpen: (toast) => {
|
||||
toast.onmouseenter = Swal.stopTimer;
|
||||
toast.onmouseleave = Swal.resumeTimer;
|
||||
}
|
||||
}).then(() => {
|
||||
location.reload(); // Refresh after user clicks OK
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
icon: 'success',
|
||||
position: "top-end",
|
||||
text: data.message || 'Actions updated successfully',
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: false,
|
||||
didOpen: (toast) => {
|
||||
toast.onmouseenter = Swal.stopTimer;
|
||||
toast.onmouseleave = Swal.resumeTimer;
|
||||
}
|
||||
}).then(() => {
|
||||
location.reload(); // Refresh after user clicks OK
|
||||
});
|
||||
} else {
|
||||
// Error notification
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
icon: 'error',
|
||||
position: "top-end",
|
||||
text: data.message || 'Failed to update actions',
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: false,
|
||||
didOpen: (toast) => {
|
||||
toast.onmouseenter = Swal.stopTimer;
|
||||
toast.onmouseleave = Swal.resumeTimer;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Swal.close();
|
||||
console.error('Error:', error);
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
icon: 'error',
|
||||
position: "top-end",
|
||||
text: 'An unexpected error occurred',
|
||||
text: data.message || 'Failed to update actions',
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: false,
|
||||
@ -691,16 +673,34 @@
|
||||
toast.onmouseleave = Swal.resumeTimer;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Swal.close();
|
||||
console.error('Error:', error);
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
icon: 'error',
|
||||
position: "top-end",
|
||||
text: 'An unexpected error occurred',
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: false,
|
||||
didOpen: (toast) => {
|
||||
toast.onmouseenter = Swal.stopTimer;
|
||||
toast.onmouseleave = Swal.resumeTimer;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Helper function for notifications
|
||||
function notify(tag, msg) {
|
||||
Toast.fire({
|
||||
icon: tag,
|
||||
titleText: msg
|
||||
});
|
||||
}
|
||||
function notify(tag, msg) {
|
||||
Toast.fire({
|
||||
icon: tag,
|
||||
titleText: msg
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock customJS %}
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Lead'%}
|
||||
{% trans 'Update Lead'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Lead'%}
|
||||
{% trans 'Add New Lead'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block customcss %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<div class="row g-3">
|
||||
<h2 class="mb-4">{{ _("Leads")|capfirst }}</h2>
|
||||
<!-- Action Tracking Modal -->
|
||||
{% include "crm/leads/partials/update_action.html" %}
|
||||
{% include "crm/leads/partials/update_action.html" %}
|
||||
|
||||
<div class="row g-3 justify-content-between mb-4">
|
||||
<div class="col-auto">
|
||||
@ -102,79 +102,79 @@
|
||||
</div>
|
||||
</div>
|
||||
<tbody>
|
||||
<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|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" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-primary"><span class="badge-label">{{_("New")}}</span><span class="fa fa-bell ms-1"></span></span>
|
||||
{% elif lead.status == "pending" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-warning"><span class="badge-label">{{_("Pending")}}</span><span class="fa fa-clock-o ms-1"></span></span>
|
||||
{% elif lead.status == "in_progress" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("In Progress")}}</span><span class="fa fa-wrench ms-1"></span></span>
|
||||
{% elif lead.status == "qualified" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-success"><span class="badge-label">{{_("Qualified")}}</span><span class="fa fa-check ms-1"></span></span>
|
||||
{% elif lead.status == "contacted" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("Contacted")}}</span><span class="fa fa-times ms-1"></span></span>
|
||||
{% elif lead.status == "canceled" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-danger"><span class="badge-label">{{_("Canceled")}}</span><span class="fa fa-times ms-1"></span></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.id_car_make.get_local_name }} - {{ lead.id_car_model.get_local_name }} {{ lead.year }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.email }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="tel:{{ lead.phone_number }}">{{ lead.phone_number }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold">
|
||||
{% if request.user.staffmember.staff %}
|
||||
<div class="accordion" id="accordionExample">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingTwo">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse{{lead.slug}}" aria-expanded="false" aria-controls="collapseTwo">
|
||||
{{ _("View Schedules")}} ({{lead.get_latest_schedules.count}})
|
||||
</button>
|
||||
</h2>
|
||||
<div class="accordion-collapse collapse" id="collapse{{lead.slug}}" aria-labelledby="headingTwo" data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body pt-0">
|
||||
<div class="d-flex flex-column gap-2">
|
||||
<table><tbody>
|
||||
{% for schedule in lead.get_latest_schedules %}
|
||||
<tr class="schedule-{{ schedule.pk }}">
|
||||
<td class="align-middle white-space-nowrap">
|
||||
{% if schedule.scheduled_type == "call" %}
|
||||
<a href="{% url 'appointment:get_user_appointments' %}">
|
||||
<span class="badge badge-phoenix badge-phoenix-primary text-primary {% if schedule.schedule_past_date %}badge-phoenix-danger text-danger{% endif %} fw-semibold"><span class="text-primary {% if schedule.schedule_past_date %}text-danger{% endif %}" data-feather="phone"></span>
|
||||
{{ schedule.scheduled_at|naturaltime|capfirst }}</span></a>
|
||||
{% elif schedule.scheduled_type == "meeting" %}
|
||||
<a href="{% url 'appointment:get_user_appointments' %}">
|
||||
<span class="badge badge-phoenix badge-phoenix-success text-success fw-semibold"><span class="text-success" data-feather="calendar"></span>
|
||||
{{ schedule.scheduled_at|naturaltime|capfirst }}</span></a>
|
||||
{% elif schedule.scheduled_type == "email" %}
|
||||
<a href="{% url 'appointment:get_user_appointments' %}">
|
||||
<span class="badge badge-phoenix badge-phoenix-warning text-warning fw-semibold"><span class="text-warning" data-feather="email"></span>
|
||||
{{ schedule.scheduled_at|naturaltime|capfirst }}</span></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a style="cursor: pointer;" hx-delete="{% url 'schedule_cancel' schedule.pk %}" hx-target=".schedule-{{ schedule.pk }}" hx-confirm="Are you sure you want to cancel this schedule?"><i class="fa-solid fa-ban text-danger"></i></a>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
<tr><td><small><a href="{% url 'appointment:get_user_appointments' %}">View All ...</a></small></td></tr>
|
||||
</tbody></table>
|
||||
<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|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" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-primary"><span class="badge-label">{{_("New")}}</span><span class="fa fa-bell ms-1"></span></span>
|
||||
{% elif lead.status == "pending" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-warning"><span class="badge-label">{{_("Pending")}}</span><span class="fa fa-clock-o ms-1"></span></span>
|
||||
{% elif lead.status == "in_progress" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("In Progress")}}</span><span class="fa fa-wrench ms-1"></span></span>
|
||||
{% elif lead.status == "qualified" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-success"><span class="badge-label">{{_("Qualified")}}</span><span class="fa fa-check ms-1"></span></span>
|
||||
{% elif lead.status == "contacted" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("Contacted")}}</span><span class="fa fa-times ms-1"></span></span>
|
||||
{% elif lead.status == "canceled" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-danger"><span class="badge-label">{{_("Canceled")}}</span><span class="fa fa-times ms-1"></span></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.get_status|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.staff|upper }}</td>
|
||||
</td>
|
||||
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.id_car_make.get_local_name }} - {{ lead.id_car_model.get_local_name }} {{ lead.year }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.email }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="tel:{{ lead.phone_number }}">{{ lead.phone_number }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold">
|
||||
{% if request.user.staffmember.staff %}
|
||||
<div class="accordion" id="accordionExample">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingTwo">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse{{lead.slug}}" aria-expanded="false" aria-controls="collapseTwo">
|
||||
{{ _("View Schedules")}} ({{lead.get_latest_schedules.count}})
|
||||
</button>
|
||||
</h2>
|
||||
<div class="accordion-collapse collapse" id="collapse{{lead.slug}}" aria-labelledby="headingTwo" data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body pt-0">
|
||||
<div class="d-flex flex-column gap-2">
|
||||
<table><tbody>
|
||||
{% for schedule in lead.get_latest_schedules %}
|
||||
<tr class="schedule-{{ schedule.pk }}">
|
||||
<td class="align-middle white-space-nowrap">
|
||||
{% if schedule.scheduled_type == "call" %}
|
||||
<a href="{% url 'appointment:get_user_appointments' %}">
|
||||
<span class="badge badge-phoenix badge-phoenix-primary text-primary {% if schedule.schedule_past_date %}badge-phoenix-danger text-danger{% endif %} fw-semibold"><span class="text-primary {% if schedule.schedule_past_date %}text-danger{% endif %}" data-feather="phone"></span>
|
||||
{{ schedule.scheduled_at|naturaltime|capfirst }}</span></a>
|
||||
{% elif schedule.scheduled_type == "meeting" %}
|
||||
<a href="{% url 'appointment:get_user_appointments' %}">
|
||||
<span class="badge badge-phoenix badge-phoenix-success text-success fw-semibold"><span class="text-success" data-feather="calendar"></span>
|
||||
{{ schedule.scheduled_at|naturaltime|capfirst }}</span></a>
|
||||
{% elif schedule.scheduled_type == "email" %}
|
||||
<a href="{% url 'appointment:get_user_appointments' %}">
|
||||
<span class="badge badge-phoenix badge-phoenix-warning text-warning fw-semibold"><span class="text-warning" data-feather="email"></span>
|
||||
{{ schedule.scheduled_at|naturaltime|capfirst }}</span></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a style="cursor: pointer;" hx-delete="{% url 'schedule_cancel' schedule.pk %}" hx-target=".schedule-{{ schedule.pk }}" hx-confirm="Are you sure you want to cancel this schedule?"><i class="fa-solid fa-ban text-danger"></i></a>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
<tr><td><small><a href="{% url 'appointment:get_user_appointments' %}">View All ...</a></small></td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.get_status|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.staff|upper }}</td>
|
||||
{% comment %} <td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
|
||||
{% if lead.opportunity.stage == "prospect" %}
|
||||
<span class="badge text-bg-primary">{{ lead.opportunity.stage|upper }}</span>
|
||||
@ -188,47 +188,47 @@
|
||||
<span class="badge text-bg-danger">{{ lead.opportunity.stage|upper }}</span>
|
||||
{% endif %}
|
||||
</td> {% endcomment %}
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
|
||||
{% if lead.opportunity %}
|
||||
<a href="{% url 'opportunity_detail' lead.opportunity.slug %}">
|
||||
<span class="badge badge-phoenix badge-phoenix-success">Opportunity {{ lead.opportunity.lead}} <i class="fa-solid fa-arrow-up-right-from-square"></i></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-end">
|
||||
{% if user == lead.staff.user or request.is_dealer %}
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button
|
||||
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
data-boundary="window"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
data-bs-reference="parent">
|
||||
<span class="fas fa-ellipsis-h fs-10"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
{% if perms.inventory.change_lead %}
|
||||
<a href="{% url 'lead_update' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
|
||||
{% endif %}
|
||||
<button class="dropdown-item text-primary" onclick="openActionModal('{{ lead.pk }}', '{{ lead.action }}', '{{ lead.next_action }}', '{{ lead.next_action_date|date:"Y-m-d\TH:i" }}')">
|
||||
{% trans "Update Actions" %}
|
||||
</button>
|
||||
<a href="{% url 'send_lead_email' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a>
|
||||
<a href="{% url 'schedule_lead' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Schedule Event" %}</a>
|
||||
{% if not lead.opportunity %}
|
||||
<a href="{% url 'lead_opportunity_create' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Convert to Opportunity" %}</a>
|
||||
{% endif %}
|
||||
<div class="dropdown-divider"></div>
|
||||
{% if perms.inventory.delete_lead %}
|
||||
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
|
||||
{% if lead.opportunity %}
|
||||
<a href="{% url 'opportunity_detail' lead.opportunity.slug %}">
|
||||
<span class="badge badge-phoenix badge-phoenix-success">Opportunity {{ lead.opportunity.lead}} <i class="fa-solid fa-arrow-up-right-from-square"></i></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-end">
|
||||
{% if user == lead.staff.user or request.is_dealer %}
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button
|
||||
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
data-boundary="window"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
data-bs-reference="parent">
|
||||
<span class="fas fa-ellipsis-h fs-10"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
{% if perms.inventory.change_lead %}
|
||||
<a href="{% url 'lead_update' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
|
||||
{% endif %}
|
||||
<button class="dropdown-item text-primary" onclick="openActionModal('{{ lead.pk }}', '{{ lead.action }}', '{{ lead.next_action }}', '{{ lead.next_action_date|date:"Y-m-d\TH:i" }}')">
|
||||
{% trans "Update Actions" %}
|
||||
</button>
|
||||
<a href="{% url 'send_lead_email' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a>
|
||||
<a href="{% url 'schedule_lead' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Schedule Event" %}</a>
|
||||
{% if not lead.opportunity %}
|
||||
<a href="{% url 'lead_opportunity_create' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Convert to Opportunity" %}</a>
|
||||
{% endif %}
|
||||
<div class="dropdown-divider"></div>
|
||||
{% if perms.inventory.delete_lead %}
|
||||
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endif %}
|
||||
@ -237,13 +237,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@ -10,19 +10,19 @@
|
||||
min-height: 500px;
|
||||
}
|
||||
.kanban-header {
|
||||
position: relative;
|
||||
font-weight: 600;
|
||||
padding: 0.5rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
--pointed-edge: {% if LANGUAGE_CODE == 'en' %} right {% else %} left {% endif %};
|
||||
clip-path: {% if LANGUAGE_CODE == 'en' %}
|
||||
polygon(0 0, calc(100% - 15px) 0, 100% 50%, calc(100% - 15px) 100%, 0 100%)
|
||||
{% else %}
|
||||
polygon(15px 0, 100% 0, 100% 100%, 15px 100%, 0 50%)
|
||||
{% endif %};
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
||||
}
|
||||
position: relative;
|
||||
font-weight: 600;
|
||||
padding: 0.5rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
--pointed-edge: {% if LANGUAGE_CODE == 'en' %} right {% else %} left {% endif %};
|
||||
clip-path: {% if LANGUAGE_CODE == 'en' %}
|
||||
polygon(0 0, calc(100% - 15px) 0, 100% 50%, calc(100% - 15px) 100%, 0 100%)
|
||||
{% else %}
|
||||
polygon(15px 0, 100% 0, 100% 100%, 15px 100%, 0 50%)
|
||||
{% endif %};
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
|
||||
.kanban-header::after {
|
||||
@ -65,95 +65,95 @@
|
||||
{% endblock customCSS %}
|
||||
{% block content %}
|
||||
<div class="container-fluid my-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<h3>{{ _("Lead Tracking")}}</h3>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<!-- New Lead -->
|
||||
<div class="col-md">
|
||||
<div class="kanban-column bg-body">
|
||||
<div class="kanban-header opacity-75"><span class="text-body">{{ _("New Leads")}} ({{new|length}})</span></div>
|
||||
{% for lead in new %}
|
||||
<a href="{% url 'lead_detail' lead.slug %}">
|
||||
<div class="lead-card">
|
||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||
<small>{{lead.email}}</small><br>
|
||||
<small>{{lead.phone_number}}</small>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<h3>{{ _("Lead Tracking")}}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<!-- New Lead -->
|
||||
<div class="col-md">
|
||||
<div class="kanban-column bg-body">
|
||||
<div class="kanban-header opacity-75"><span class="text-body">{{ _("New Leads")}} ({{new|length}})</span></div>
|
||||
{% for lead in new %}
|
||||
<a href="{% url 'lead_detail' lead.slug %}">
|
||||
<div class="lead-card">
|
||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||
<small>{{lead.email}}</small><br>
|
||||
<small>{{lead.phone_number}}</small>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Follow Ups -->
|
||||
<div class="col-md">
|
||||
<div class="kanban-column bg-body">
|
||||
<div class="kanban-header opacity-75"><span class="text-body">{{ _("Follow Ups")}} ({{follow_up|length}})</span></div>
|
||||
{% for lead in follow_up %}
|
||||
<a href="{% url 'lead_detail' lead.slug %}">
|
||||
<div class="lead-card">
|
||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||
<small>{{lead.email}}</small><br>
|
||||
<small>{{lead.phone_number}}</small>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="kanban-column bg-body">
|
||||
<div class="kanban-header opacity-75"><span class="text-body">{{ _("Follow Ups")}} ({{follow_up|length}})</span></div>
|
||||
{% for lead in follow_up %}
|
||||
<a href="{% url 'lead_detail' lead.slug %}">
|
||||
<div class="lead-card">
|
||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||
<small>{{lead.email}}</small><br>
|
||||
<small>{{lead.phone_number}}</small>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Negotiation -->
|
||||
<div class="col-md">
|
||||
<div class="kanban-column bg-body">
|
||||
<div class="kanban-header opacity-75"><span class="text-body">{{ _("Negotiation Ups")}} ({{follow_up|length}})</span></div>
|
||||
{% for lead in negotiation %}
|
||||
<a href="{% url 'lead_detail' lead.slug %}">
|
||||
<div class="lead-card">
|
||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||
<small>{{lead.email}}</small><br>
|
||||
<small>{{lead.phone_number}}</small>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="kanban-column bg-body">
|
||||
<div class="kanban-header opacity-75"><span class="text-body">{{ _("Negotiation Ups")}} ({{follow_up|length}})</span></div>
|
||||
{% for lead in negotiation %}
|
||||
<a href="{% url 'lead_detail' lead.slug %}">
|
||||
<div class="lead-card">
|
||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||
<small>{{lead.email}}</small><br>
|
||||
<small>{{lead.phone_number}}</small>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Won -->
|
||||
<div class="col-md">
|
||||
<div class="kanban-column bg-body">
|
||||
<div class="kanban-header bg-success-light opacity-75"><span class="text-body">{{ _("Won") }} ({{won|length}}) ({{follow_up|length}})</span></div>
|
||||
{% for lead in won %}
|
||||
<a href="{% url 'lead_detail' lead.slug %}">
|
||||
<div class="lead-card">
|
||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||
<small>{{lead.email}}</small><br>
|
||||
<small>{{lead.phone_number}}</small>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="kanban-column bg-body">
|
||||
<div class="kanban-header bg-success-light opacity-75"><span class="text-body">{{ _("Won") }} ({{won|length}}) ({{follow_up|length}})</span></div>
|
||||
{% for lead in won %}
|
||||
<a href="{% url 'lead_detail' lead.slug %}">
|
||||
<div class="lead-card">
|
||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||
<small>{{lead.email}}</small><br>
|
||||
<small>{{lead.phone_number}}</small>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lose -->
|
||||
<div class="col-md">
|
||||
<div class="kanban-column bg-body">
|
||||
<div class="kanban-header bg-danger-light opacity-75">{{ _("Lost") }} ({{lose|length}})</div>
|
||||
{% for lead in lose %}
|
||||
<a href="{% url 'lead_detail' lead.slug %}">
|
||||
<div class="lead-card">
|
||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||
<small>{{lead.email}}</small><br>
|
||||
<small>{{lead.phone_number}}</small>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
<div class="col-md">
|
||||
<div class="kanban-column bg-body">
|
||||
<div class="kanban-header bg-danger-light opacity-75">{{ _("Lost") }} ({{lose|length}})</div>
|
||||
{% for lead in lose %}
|
||||
<a href="{% url 'lead_detail' lead.slug %}">
|
||||
<div class="lead-card">
|
||||
<strong>{{lead.full_name|capfirst}}</strong><br>
|
||||
<small>{{lead.email}}</small><br>
|
||||
<small>{{lead.phone_number}}</small>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,53 +1,53 @@
|
||||
<div class="modal fade" id="actionTrackingModal" tabindex="-1" aria-labelledby="actionTrackingModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="actionTrackingModalLabel">{{ _("Update Lead Actions") }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="actionTrackingForm" method="post">
|
||||
<div class="modal-body">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" id="leadId" name="lead_id">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="actionTrackingModalLabel">{{ _("Update Lead Actions") }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="actionTrackingForm" method="post">
|
||||
<div class="modal-body">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" id="leadId" name="lead_id">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="currentAction" class="form-label">{{ _("Current Stage") }}</label>
|
||||
<select class="form-select" id="currentAction" name="current_action" required>
|
||||
<option value="">{{ _("Select Stage") }}</option>
|
||||
<option value="new">{{ _("New") }}</option>
|
||||
<option value="contacted">{{ _("Contacted") }}</option>
|
||||
<option value="qualified">{{ _("Qualified") }}</option>
|
||||
<option value="unqualified">{{ _("Unqualified") }}</option>
|
||||
<option value="converted">{{ _("Converted") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="currentAction" class="form-label">{{ _("Current Stage") }}</label>
|
||||
<select class="form-select" id="currentAction" name="current_action" required>
|
||||
<option value="">{{ _("Select Stage") }}</option>
|
||||
<option value="new">{{ _("New") }}</option>
|
||||
<option value="contacted">{{ _("Contacted") }}</option>
|
||||
<option value="qualified">{{ _("Qualified") }}</option>
|
||||
<option value="unqualified">{{ _("Unqualified") }}</option>
|
||||
<option value="converted">{{ _("Converted") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="nextAction" class="form-label">{{ _("Next Action") }}</label>
|
||||
<select class="form-select" id="nextAction" name="next_action" required>
|
||||
<option value="">{{ _("Select Next Action") }}</option>
|
||||
<option value="no_action">{{ _("No Action") }}</option>
|
||||
<option value="call">{{ _("Call") }}</option>
|
||||
<option value="meeting">{{ _("Meeting") }}</option>
|
||||
<option value="email">{{ _("Email") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="nextAction" class="form-label">{{ _("Next Action") }}</label>
|
||||
<select class="form-select" id="nextAction" name="next_action" required>
|
||||
<option value="">{{ _("Select Next Action") }}</option>
|
||||
<option value="no_action">{{ _("No Action") }}</option>
|
||||
<option value="call">{{ _("Call") }}</option>
|
||||
<option value="meeting">{{ _("Meeting") }}</option>
|
||||
<option value="email">{{ _("Email") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="nextActionDate" class="form-label">{{ _("Next Action Date") }}</label>
|
||||
<input type="datetime-local" class="form-control" id="nextActionDate" name="next_action_date">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="nextActionDate" class="form-label">{{ _("Next Action Date") }}</label>
|
||||
<input type="datetime-local" class="form-control" id="nextActionDate" name="next_action_date">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="actionNotes" class="form-label">{{ _("Notes") }}</label>
|
||||
<textarea class="form-control" id="actionNotes" name="action_notes" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-phoenix-secondary" data-bs-dismiss="modal">{{ _("Close") }}</button>
|
||||
<button type="submit" class="btn btn-phoenix-primary">{{ _("Save Changes") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mb-3">
|
||||
<label for="actionNotes" class="form-label">{{ _("Notes") }}</label>
|
||||
<textarea class="form-control" id="actionNotes" name="action_notes" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-phoenix-secondary" data-bs-dismiss="modal">{{ _("Close") }}</button>
|
||||
<button type="submit" class="btn btn-phoenix-primary">{{ _("Save Changes") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,12 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static widget_tweaks custom_filters %}
|
||||
{% block title %}
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Opportunity'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Opportunity'%}
|
||||
{% endif %}
|
||||
{% if object %}
|
||||
{% trans 'Update Opportunity'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Opportunity'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
@ -88,8 +88,8 @@
|
||||
<span class="text-danger">*</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><span class="icon-saudi_riyal"></span></span>
|
||||
{{ form.amount|add_class:"form-control" }}
|
||||
<span class="input-group-text"><span class="icon-saudi_riyal"></span></span>
|
||||
{{ form.amount|add_class:"form-control" }}
|
||||
</div>
|
||||
{% if form.amount.errors %}
|
||||
<div class="invalid-feedback d-block">
|
||||
|
||||
@ -90,11 +90,11 @@
|
||||
<div id="opportunities-grid" class="row g-4 px-2 px-lg-4 mt-1">
|
||||
{% include 'crm/opportunities/partials/opportunity_grid.html' %}
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -20,15 +20,15 @@
|
||||
{% elif opportunity.get_stage_display == 'Closed Lost' %}bg-danger-soft{% endif %}">
|
||||
<div class="card-body">
|
||||
<div class="avatar avatar-xl me-3 mb-3">
|
||||
{% if opportunity.car.id_car_make.logo %}
|
||||
<img class="rounded" src="{{ opportunity.car.id_car_make.logo.url }}" alt="" />
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if opportunity.car.id_car_make.logo %}
|
||||
<img class="rounded" src="{{ opportunity.car.id_car_make.logo.url }}" alt="" />
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if opportunity.customer %}
|
||||
<h5 class="mb-4">Opportunity for {{ opportunity.customer }}</h5>
|
||||
{% elif opportunity.organization %}
|
||||
<h5 class="mb-4">Opportunity for {{ opportunity.organization }}</h5>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||
<div class="d-flex gap-2">
|
||||
@ -50,27 +50,27 @@
|
||||
<span class="badge badge-phoenix fs-10 badge-phoenix-secondary">
|
||||
{% endif %}
|
||||
{{ opportunity.stage }}</span>
|
||||
<span class="badge badge-phoenix fs-10
|
||||
{% if opportunity.get_stage_display == 'Won' %}badge-phoenix-success
|
||||
{% elif opportunity.get_stage_display == 'Lost' %}badge-phoenix-danger{% endif %}">
|
||||
{{ opportunity.get_status_display }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fa-regular fa-clock"></i>
|
||||
<p class="mb-0 fs-9 fw-semibold text-body-tertiary">{{ opportunity.created|naturalday|capfirst }}</p>
|
||||
</div>
|
||||
<span class="badge badge-phoenix fs-10
|
||||
{% if opportunity.get_stage_display == 'Won' %}badge-phoenix-success
|
||||
{% elif opportunity.get_stage_display == 'Lost' %}badge-phoenix-danger{% endif %}">
|
||||
{{ opportunity.get_status_display }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fa-regular fa-clock"></i>
|
||||
<p class="mb-0 fs-9 fw-semibold text-body-tertiary">{{ opportunity.created|naturalday|capfirst }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="deals-company-agent d-flex justify-content-between mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="uil uil-user me-2"></span>
|
||||
<p class="text-body-secondary fw-bold fs-10 mb-0">
|
||||
{{ _("Assigned To") }}{% if request.user.email == opportunity.staff.email %}
|
||||
{{ _("You") }}
|
||||
{% else %}
|
||||
{{ opportunity.staff.name }}</p>
|
||||
{% endif %}
|
||||
<div class="deals-company-agent d-flex justify-content-between mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="uil uil-user me-2"></span>
|
||||
<p class="text-body-secondary fw-bold fs-10 mb-0">
|
||||
{{ _("Assigned To") }}{% if request.user.email == opportunity.staff.email %}
|
||||
{{ _("You") }}
|
||||
{% else %}
|
||||
{{ opportunity.staff.name }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<table class="mb-3 w-100">
|
||||
|
||||
@ -2,176 +2,176 @@
|
||||
{% load static i18n %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
.color-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
width: 80px; /* Increased from 3rem for better visibility */
|
||||
height: 80px; /* Increased from 3rem for better visibility */
|
||||
margin-right: 10px;
|
||||
}
|
||||
<style>
|
||||
.color-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
width: 80px; /* Increased from 3rem for better visibility */
|
||||
height: 80px; /* Increased from 3rem for better visibility */
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.color-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
.color-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.color-option {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.color-option {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.color-radio {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.color-radio {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.color-radio:checked + .color-display {
|
||||
border: 2px solid #0d6efd;
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
.color-radio:checked + .color-display {
|
||||
border: 2px solid #0d6efd;
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
.color-radio:focus + .color-display {
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
.color-radio:focus + .color-display {
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
.color-display {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
border-radius: 0.25rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.color-display {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
border-radius: 0.25rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.color-name {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.color-name {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Added for better layout of color options */
|
||||
.color-options-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
.color-options-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
{% endblock customCSS %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>Upload Cars CSV</h2>
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="{% static 'sample/cars_sample.csv' %}" class="btn btn-outline-success mt-4">
|
||||
<i class="fa-solid fa-file-csv me-2"></i>Download Sample CSV
|
||||
</a>
|
||||
<div class="container mt-4">
|
||||
<h2>Upload Cars CSV</h2>
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="{% static 'sample/cars_sample.csv' %}" class="btn btn-outline-success mt-4">
|
||||
<i class="fa-solid fa-file-csv me-2"></i>Download Sample CSV
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-outline-warning }} mt-4" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="mt-4">
|
||||
{% csrf_token %}
|
||||
{% if not item %}
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
{{form.vendor.label}}
|
||||
{{form.vendor}}
|
||||
</div>
|
||||
<div class="col">
|
||||
{{form.year.label}}
|
||||
{{form.year}}
|
||||
</div>
|
||||
<div class="col">
|
||||
{{form.receiving_date.label}}
|
||||
{{form.receiving_date}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
<p class="fs-5 mb-2">{% trans 'Exterior Colors' %}</p>
|
||||
<div class="color-options-container">
|
||||
{% for color in form.fields.exterior.queryset %}
|
||||
<div class="color-card">
|
||||
<label class="color-option">
|
||||
<input class="color-radio" type="radio" name="exterior" value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||
<span class="color-name">{{ color.get_local_name }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p class="fs-5 mb-2">{% trans 'Interior Colors' %}</p>
|
||||
<div class="color-options-container">
|
||||
{% for color in form.fields.interior.queryset %}
|
||||
<div class="color-card">
|
||||
<label class="color-option">
|
||||
<input class="color-radio" type="radio" name="interior" value="{{ color.id }}" {% if color.id == form.instance.interior.id %}checked{% endif %}>
|
||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||
<span class="color-name">{{ color.get_local_name }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if item %}
|
||||
<h3 class="mt-4">List of Items</h3>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<span class="badge bg-primary">{{ item.item_model }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<div class="mb-3">
|
||||
<label for="csv_file" class="form-label">CSV File</label>
|
||||
<input type="file" class="form-control" id="csv_file" name="csv_file" accept=".csv" required>
|
||||
<div class="form-text">
|
||||
CSV should include columns: vin
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Upload</button>
|
||||
<a href="{{ request.META.HTTP_REFERER }}" class="btn btn-secondary">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-outline-warning }} mt-4" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="mt-4">
|
||||
{% csrf_token %}
|
||||
{% if not item %}
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
{{form.vendor.label}}
|
||||
{{form.vendor}}
|
||||
</div>
|
||||
<div class="col">
|
||||
{{form.year.label}}
|
||||
{{form.year}}
|
||||
</div>
|
||||
<div class="col">
|
||||
{{form.receiving_date.label}}
|
||||
{{form.receiving_date}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
<p class="fs-5 mb-2">{% trans 'Exterior Colors' %}</p>
|
||||
<div class="color-options-container">
|
||||
{% for color in form.fields.exterior.queryset %}
|
||||
<div class="color-card">
|
||||
<label class="color-option">
|
||||
<input class="color-radio" type="radio" name="exterior" value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||
<span class="color-name">{{ color.get_local_name }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p class="fs-5 mb-2">{% trans 'Interior Colors' %}</p>
|
||||
<div class="color-options-container">
|
||||
{% for color in form.fields.interior.queryset %}
|
||||
<div class="color-card">
|
||||
<label class="color-option">
|
||||
<input class="color-radio" type="radio" name="interior" value="{{ color.id }}" {% if color.id == form.instance.interior.id %}checked{% endif %}>
|
||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||
<span class="color-name">{{ color.get_local_name }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if item %}
|
||||
<h3 class="mt-4">List of Items</h3>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<span class="badge bg-primary">{{ item.item_model }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<div class="mb-3">
|
||||
<label for="csv_file" class="form-label">CSV File</label>
|
||||
<input type="file" class="form-control" id="csv_file" name="csv_file" accept=".csv" required>
|
||||
<div class="form-text">
|
||||
CSV should include columns: vin
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Upload</button>
|
||||
<a href="{{ request.META.HTTP_REFERER }}" class="btn btn-secondary">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -4,9 +4,9 @@
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Customer'%}
|
||||
{% trans 'Update Customer'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Customer'%}
|
||||
{% trans 'Add New Customer'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
@ -110,11 +110,11 @@
|
||||
{% endif %}
|
||||
</table>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% include 'modal/delete_modal.html' %}
|
||||
{% endblock %}
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static custom_filters%}
|
||||
{%block title%}{%trans 'Profile'%} {%endblock%}
|
||||
{% block content %}
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
<i class="fa fa-save"></i> {{ _("Save") }}
|
||||
</button>
|
||||
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
||||
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
@ -137,7 +137,7 @@
|
||||
<!-- End of Main Content-->
|
||||
<!-- ===============================================-->
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="offcanvas offcanvas-end settings-panel border-0" id="settings-offcanvas" tabindex="-1" aria-labelledby="settings-offcanvas">
|
||||
<div class="offcanvas-header align-items-start border-bottom flex-column border-translucent">
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Group'%}
|
||||
{% trans 'Update Group'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Group'%}
|
||||
{% trans 'Add New Group'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -4,42 +4,42 @@
|
||||
{% block title %}Haikal Bot{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.2/papaparse.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.2/papaparse.min.js"></script>
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><i class="fas fa-robot me-2"></i>{% trans "HaikalBot" %}</h5>
|
||||
<div>
|
||||
<button id="export-btn" class="btn btn-sm btn-phoenix-secondary" style="display:none;">
|
||||
{% trans "Export CSV" %}
|
||||
</button>
|
||||
<div class="container mt-5">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><i class="fas fa-robot me-2"></i>{% trans "HaikalBot" %}</h5>
|
||||
<div>
|
||||
<button id="export-btn" class="btn btn-sm btn-phoenix-secondary" style="display:none;">
|
||||
{% trans "Export CSV" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body" style="max-height: 60vh; overflow-y: auto;" id="chat-history"></div>
|
||||
<div class="card-footer bg-white border-top">
|
||||
<form id="chat-form" class="d-flex align-items-center gap-2">
|
||||
<button type="button" class="btn btn-light" id="mic-btn"><i class="fas fa-microphone"></i></button>
|
||||
<input type="text" class="form-control" id="chat-input" placeholder="{% trans 'Type your question...' %}" required />
|
||||
<button type="submit" class="btn btn-phoenix-primary"><i class="fas fa-paper-plane"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
<div id="chart-container" style="display:none;" class="p-4 border-top">
|
||||
<canvas id="chart-canvas" height="200px"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body" style="max-height: 60vh; overflow-y: auto;" id="chat-history"></div>
|
||||
<div class="card-footer bg-white border-top">
|
||||
<form id="chat-form" class="d-flex align-items-center gap-2">
|
||||
<button type="button" class="btn btn-light" id="mic-btn"><i class="fas fa-microphone"></i></button>
|
||||
<input type="text" class="form-control" id="chat-input" placeholder="{% trans 'Type your question...' %}" required />
|
||||
<button type="submit" class="btn btn-phoenix-primary"><i class="fas fa-paper-plane"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
<div id="chart-container" style="display:none;" class="p-4 border-top">
|
||||
<canvas id="chart-canvas" height="200px"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const chatHistory = document.getElementById('chat-history');
|
||||
const chartContainer = document.getElementById('chart-container');
|
||||
const chartCanvas = document.getElementById('chart-canvas');
|
||||
const exportBtn = document.getElementById('export-btn');
|
||||
let chartInstance = null;
|
||||
let latestDataTable = null;
|
||||
<script>
|
||||
const chatHistory = document.getElementById('chat-history');
|
||||
const chartContainer = document.getElementById('chart-container');
|
||||
const chartCanvas = document.getElementById('chart-canvas');
|
||||
const exportBtn = document.getElementById('export-btn');
|
||||
let chartInstance = null;
|
||||
let latestDataTable = null;
|
||||
|
||||
function getCookie(name) {
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
@ -54,135 +54,135 @@ function getCookie(name) {
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function speak(text) {
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = document.documentElement.lang || "en";
|
||||
window.speechSynthesis.speak(utterance);
|
||||
}
|
||||
function speak(text) {
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = document.documentElement.lang || "en";
|
||||
window.speechSynthesis.speak(utterance);
|
||||
}
|
||||
|
||||
function renderTable(data) {
|
||||
latestDataTable = data;
|
||||
exportBtn.style.display = 'inline-block';
|
||||
const headers = Object.keys(data[0]);
|
||||
let html = '<div class="table-responsive"><table class="table table-bordered table-striped"><thead><tr>';
|
||||
headers.forEach(h => html += `<th>${h}</th>`);
|
||||
html += '</tr></thead><tbody>';
|
||||
data.forEach(row => {
|
||||
html += '<tr>' + headers.map(h => `<td>${row[h]}</td>`).join('') + '</tr>';
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
return html;
|
||||
}
|
||||
function renderTable(data) {
|
||||
latestDataTable = data;
|
||||
exportBtn.style.display = 'inline-block';
|
||||
const headers = Object.keys(data[0]);
|
||||
let html = '<div class="table-responsive"><table class="table table-bordered table-striped"><thead><tr>';
|
||||
headers.forEach(h => html += `<th>${h}</th>`);
|
||||
html += '</tr></thead><tbody>';
|
||||
data.forEach(row => {
|
||||
html += '<tr>' + headers.map(h => `<td>${row[h]}</td>`).join('') + '</tr>';
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function appendMessage(role, htmlContent) {
|
||||
const align = role === 'AI' ? 'bg-secondary-light' : 'bg-primary-light';
|
||||
chatHistory.innerHTML += `
|
||||
function appendMessage(role, htmlContent) {
|
||||
const align = role === 'AI' ? 'bg-secondary-light' : 'bg-primary-light';
|
||||
chatHistory.innerHTML += `
|
||||
<div class="mb-3 p-3 rounded ${align}">
|
||||
<strong>${role}:</strong><br>${htmlContent}
|
||||
</div>
|
||||
`;
|
||||
chatHistory.scrollTop = chatHistory.scrollHeight;
|
||||
}
|
||||
chatHistory.scrollTop = chatHistory.scrollHeight;
|
||||
}
|
||||
|
||||
document.getElementById('chat-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const input = document.getElementById('chat-input');
|
||||
const prompt = input.value.trim();
|
||||
const csrfToken = getCookie("csrftoken");
|
||||
document.getElementById('chat-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const input = document.getElementById('chat-input');
|
||||
const prompt = input.value.trim();
|
||||
const csrfToken = getCookie("csrftoken");
|
||||
|
||||
if (!prompt) return;
|
||||
if (!prompt) return;
|
||||
|
||||
appendMessage('You', prompt);
|
||||
input.value = "";
|
||||
chartContainer.style.display = 'none';
|
||||
exportBtn.style.display = 'none';
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy();
|
||||
chartInstance = null;
|
||||
}
|
||||
appendMessage('You', prompt);
|
||||
input.value = "";
|
||||
chartContainer.style.display = 'none';
|
||||
exportBtn.style.display = 'none';
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy();
|
||||
chartInstance = null;
|
||||
}
|
||||
|
||||
const response = await fetch("{% url 'haikalbot' %}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"X-CSRFToken": csrfToken
|
||||
},
|
||||
body: new URLSearchParams({ prompt })
|
||||
});
|
||||
const response = await fetch("{% url 'haikalbot' %}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"X-CSRFToken": csrfToken
|
||||
},
|
||||
body: new URLSearchParams({ prompt })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
const result = await response.json();
|
||||
|
||||
// Show chart if available
|
||||
if (result.chart && result.chart.type && result.chart.labels && result.chart.data) {
|
||||
chartInstance = new Chart(chartCanvas, {
|
||||
type: result.chart.type,
|
||||
data: {
|
||||
labels: result.chart.labels,
|
||||
datasets: [{
|
||||
label: result.chart.labels.join(", "),
|
||||
data: result.chart.data,
|
||||
backgroundColor: result.chart.backgroundColor || []
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: result.chart.type.toUpperCase()
|
||||
if (result.chart && result.chart.type && result.chart.labels && result.chart.data) {
|
||||
chartInstance = new Chart(chartCanvas, {
|
||||
type: result.chart.type,
|
||||
data: {
|
||||
labels: result.chart.labels,
|
||||
datasets: [{
|
||||
label: result.chart.labels.join(", "),
|
||||
data: result.chart.data,
|
||||
backgroundColor: result.chart.backgroundColor || []
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: result.chart.type.toUpperCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
chartContainer.style.display = 'block';
|
||||
appendMessage('AI', `{% trans "Chart displayed below." %}`);
|
||||
});
|
||||
chartContainer.style.display = 'block';
|
||||
appendMessage('AI', `{% trans "Chart displayed below." %}`);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Table if list of objects
|
||||
if (Array.isArray(result.data) && result.data.length && typeof result.data[0] === 'object') {
|
||||
const tableHTML = renderTable(result.data);
|
||||
appendMessage('AI', tableHTML);
|
||||
if (Array.isArray(result.data) && result.data.length && typeof result.data[0] === 'object') {
|
||||
const tableHTML = renderTable(result.data);
|
||||
appendMessage('AI', tableHTML);
|
||||
|
||||
} else {
|
||||
const content = typeof result.data === 'object'
|
||||
? `<pre>${JSON.stringify(result.data, null, 2)}</pre>`
|
||||
: `<p>${result.data}</p>`;
|
||||
appendMessage('AI', content);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const content = typeof result.data === 'object'
|
||||
? `<pre>${JSON.stringify(result.data, null, 2)}</pre>`
|
||||
: `<p>${result.data}</p>`;
|
||||
appendMessage('AI', content);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('export-btn').addEventListener('click', () => {
|
||||
if (!latestDataTable) return;
|
||||
const csv = Papa.unparse(latestDataTable);
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'haikal_data.csv';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
});
|
||||
document.getElementById('export-btn').addEventListener('click', () => {
|
||||
if (!latestDataTable) return;
|
||||
const csv = Papa.unparse(latestDataTable);
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'haikal_data.csv';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
});
|
||||
|
||||
// Voice input (speech-to-text)
|
||||
document.getElementById('mic-btn').addEventListener('click', () => {
|
||||
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
|
||||
recognition.lang = document.documentElement.lang || "en";
|
||||
recognition.interimResults = false;
|
||||
recognition.maxAlternatives = 1;
|
||||
document.getElementById('mic-btn').addEventListener('click', () => {
|
||||
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
|
||||
recognition.lang = document.documentElement.lang || "en";
|
||||
recognition.interimResults = false;
|
||||
recognition.maxAlternatives = 1;
|
||||
|
||||
recognition.onresult = (event) => {
|
||||
const speech = event.results[0][0].transcript;
|
||||
document.getElementById('chat-input').value = speech;
|
||||
};
|
||||
recognition.onresult = (event) => {
|
||||
const speech = event.results[0][0].transcript;
|
||||
document.getElementById('chat-input').value = speech;
|
||||
};
|
||||
|
||||
recognition.onerror = (e) => {
|
||||
console.error('Speech recognition error', e);
|
||||
};
|
||||
recognition.onerror = (e) => {
|
||||
console.error('Speech recognition error', e);
|
||||
};
|
||||
|
||||
recognition.start();
|
||||
});
|
||||
</script>
|
||||
recognition.start();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -6,355 +6,355 @@
|
||||
{% endblock title %}
|
||||
|
||||
{% block description %}
|
||||
AI assistant
|
||||
AI assistant
|
||||
{% endblock description %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.chat-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.chat-textarea {
|
||||
border-radius: 20px;
|
||||
padding: 15px;
|
||||
resize: none;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #dee2e6;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.chat-textarea:focus {
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||
outline: none;
|
||||
}
|
||||
.send-button {
|
||||
border-radius: 20px;
|
||||
padding: 10px 25px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.textarea-container {
|
||||
position: relative;
|
||||
}
|
||||
.textarea-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
font-size: 0.8rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
.character-count.warning {
|
||||
color: #dc3545;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.chat-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.chat-textarea {
|
||||
border-radius: 20px;
|
||||
padding: 15px;
|
||||
resize: none;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #dee2e6;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.chat-textarea:focus {
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||
outline: none;
|
||||
}
|
||||
.send-button {
|
||||
border-radius: 20px;
|
||||
padding: 10px 25px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.textarea-container {
|
||||
position: relative;
|
||||
}
|
||||
.textarea-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
font-size: 0.8rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
.character-count.warning {
|
||||
color: #dc3545;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
{% endblock customCSS %}
|
||||
|
||||
{% block content %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<div class="card shadow-none mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||
<img class="d-dark-none" src="{% static 'images/favicons/haikalbot_v1.png' %}" alt="{% trans 'home' %}" width="32" />
|
||||
<img class="d-light-none" src="{% static 'images/favicons/haikalbot_v2.png' %}" alt="{% trans 'home' %}" width="32" />
|
||||
</div>
|
||||
<div class="d-flex gap-3">
|
||||
<span id="clearChatBtn" class="translate-middle-y cursor-pointer" title="{% if LANGUAGE_CODE == 'ar' %}مسح المحادثة{% else %}Clear Chat{% endif %}">
|
||||
<i class="fas fa-trash-alt text-danger"></i>
|
||||
</span>
|
||||
<span id="exportChatBtn" class="translate-middle-y cursor-pointer" title="{% if LANGUAGE_CODE == 'ar' %}تصدير المحادثة{% else %}Export Chat{% endif %}">
|
||||
<i class="fas fa-download text-success"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="chatMessages" class="overflow-auto p-3" style="height: 60vh;"></div>
|
||||
<div class="bg-100 border-top p-3">
|
||||
<div class="d-flex gap-2 flex-wrap mb-3" id="suggestionChips">
|
||||
<button class="btn btn-sm btn-phoenix-primary suggestion-chip">{{ _("How many cars are in inventory")}}?</button>
|
||||
<button class="btn btn-sm btn-phoenix-primary suggestion-chip">{{ _("Show me sales analysis")}}</button>
|
||||
<button class="btn btn-sm btn-phoenix-primary suggestion-chip">{{ _("What are the best-selling cars")}}?</button>
|
||||
</div>
|
||||
<div class="chat-container">
|
||||
<div class="textarea-container mb-3">
|
||||
<label for="messageInput"></label>
|
||||
<textarea class="form-control chat-textarea" id="messageInput" rows="3" placeholder="{{ _("Type your message here")}}..." maxlength="400"></textarea>
|
||||
<div class="textarea-footer">
|
||||
<div class="character-count">
|
||||
<span id="charCount">0</span>/400
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<div class="card shadow-none mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||
<img class="d-dark-none" src="{% static 'images/favicons/haikalbot_v1.png' %}" alt="{% trans 'home' %}" width="32" />
|
||||
<img class="d-light-none" src="{% static 'images/favicons/haikalbot_v2.png' %}" alt="{% trans 'home' %}" width="32" />
|
||||
</div>
|
||||
<div class="d-flex gap-3">
|
||||
<span id="clearChatBtn" class="translate-middle-y cursor-pointer" title="{% if LANGUAGE_CODE == 'ar' %}مسح المحادثة{% else %}Clear Chat{% endif %}">
|
||||
<i class="fas fa-trash-alt text-danger"></i>
|
||||
</span>
|
||||
<span id="exportChatBtn" class="translate-middle-y cursor-pointer" title="{% if LANGUAGE_CODE == 'ar' %}تصدير المحادثة{% else %}Export Chat{% endif %}">
|
||||
<i class="fas fa-download text-success"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="chatMessages" class="overflow-auto p-3" style="height: 60vh;"></div>
|
||||
<div class="bg-100 border-top p-3">
|
||||
<div class="d-flex gap-2 flex-wrap mb-3" id="suggestionChips">
|
||||
<button class="btn btn-sm btn-phoenix-primary suggestion-chip">{{ _("How many cars are in inventory")}}?</button>
|
||||
<button class="btn btn-sm btn-phoenix-primary suggestion-chip">{{ _("Show me sales analysis")}}</button>
|
||||
<button class="btn btn-sm btn-phoenix-primary suggestion-chip">{{ _("What are the best-selling cars")}}?</button>
|
||||
</div>
|
||||
<div class="chat-container">
|
||||
<div class="textarea-container mb-3">
|
||||
<label for="messageInput"></label>
|
||||
<textarea class="form-control chat-textarea" id="messageInput" rows="3" placeholder="{{ _("Type your message here")}}..." maxlength="400"></textarea>
|
||||
<div class="textarea-footer">
|
||||
<div class="character-count">
|
||||
<span id="charCount">0</span>/400
|
||||
</div>
|
||||
<span class="send-button position-absolute top-50 end-0 translate-middle-y cursor-pointer" id="sendMessageBtn" disabled>
|
||||
<i class="fas fa-paper-plane text-body"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="send-button position-absolute top-50 end-0 translate-middle-y cursor-pointer" id="sendMessageBtn" disabled>
|
||||
<i class="fas fa-paper-plane text-body"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script>
|
||||
// Global configuration
|
||||
const MAX_MESSAGE_LENGTH = 400;
|
||||
const WARNING_THRESHOLD = 350;
|
||||
const isArabic = '{{ LANGUAGE_CODE }}' === 'ar';
|
||||
const MAX_MESSAGE_LENGTH = 400;
|
||||
const WARNING_THRESHOLD = 350;
|
||||
const isArabic = '{{ LANGUAGE_CODE }}' === 'ar';
|
||||
|
||||
// Chart rendering function
|
||||
function renderInsightChart(labels, data, chartType = 'bar', title = 'Insight Chart') {
|
||||
const canvasId = 'chart_' + Date.now();
|
||||
const chartHtml = `<div class="chart-container"><canvas id="${canvasId}"></canvas></div>`;
|
||||
$('#chatMessages').append(chartHtml);
|
||||
function renderInsightChart(labels, data, chartType = 'bar', title = 'Insight Chart') {
|
||||
const canvasId = 'chart_' + Date.now();
|
||||
const chartHtml = `<div class="chart-container"><canvas id="${canvasId}"></canvas></div>`;
|
||||
$('#chatMessages').append(chartHtml);
|
||||
|
||||
new Chart(document.getElementById(canvasId), {
|
||||
type: chartType,
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: title,
|
||||
data: data,
|
||||
borderWidth: 1,
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.5)',
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
title: { display: true, text: title }
|
||||
},
|
||||
scales: {
|
||||
y: { beginAtZero: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
new Chart(document.getElementById(canvasId), {
|
||||
type: chartType,
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: title,
|
||||
data: data,
|
||||
borderWidth: 1,
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.5)',
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
title: { display: true, text: title }
|
||||
},
|
||||
scales: {
|
||||
y: { beginAtZero: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$(document).ready(function() {
|
||||
// DOM elements
|
||||
const messageInput = $('#messageInput');
|
||||
const charCount = $('#charCount');
|
||||
const sendMessageBtn = $('#sendMessageBtn');
|
||||
const chatMessages = $('#chatMessages');
|
||||
const clearChatBtn = $('#clearChatBtn');
|
||||
const exportChatBtn = $('#exportChatBtn');
|
||||
const suggestionChips = $('.suggestion-chip');
|
||||
const messageInput = $('#messageInput');
|
||||
const charCount = $('#charCount');
|
||||
const sendMessageBtn = $('#sendMessageBtn');
|
||||
const chatMessages = $('#chatMessages');
|
||||
const clearChatBtn = $('#clearChatBtn');
|
||||
const exportChatBtn = $('#exportChatBtn');
|
||||
const suggestionChips = $('.suggestion-chip');
|
||||
|
||||
// Initialize character count
|
||||
updateCharacterCount();
|
||||
updateCharacterCount();
|
||||
|
||||
// Event handlers
|
||||
messageInput.on('input', handleInput);
|
||||
messageInput.on('keydown', handleKeyDown);
|
||||
sendMessageBtn.on('click', sendMessage);
|
||||
suggestionChips.on('click', handleSuggestionClick);
|
||||
clearChatBtn.on('click', clearChat);
|
||||
exportChatBtn.on('click', exportChat);
|
||||
messageInput.on('input', handleInput);
|
||||
messageInput.on('keydown', handleKeyDown);
|
||||
sendMessageBtn.on('click', sendMessage);
|
||||
suggestionChips.on('click', handleSuggestionClick);
|
||||
clearChatBtn.on('click', clearChat);
|
||||
exportChatBtn.on('click', exportChat);
|
||||
|
||||
// Input handling
|
||||
function handleInput() {
|
||||
updateCharacterCount();
|
||||
autoResizeTextarea();
|
||||
toggleSendButton();
|
||||
}
|
||||
function handleInput() {
|
||||
updateCharacterCount();
|
||||
autoResizeTextarea();
|
||||
toggleSendButton();
|
||||
}
|
||||
|
||||
function handleKeyDown(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
if (!sendMessageBtn.prop('disabled')) {
|
||||
function handleKeyDown(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
if (!sendMessageBtn.prop('disabled')) {
|
||||
sendMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleSuggestionClick() {
|
||||
messageInput.val($(this).text().trim());
|
||||
updateCharacterCount();
|
||||
autoResizeTextarea();
|
||||
sendMessageBtn.prop('disabled', false);
|
||||
sendMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleSuggestionClick() {
|
||||
messageInput.val($(this).text().trim());
|
||||
updateCharacterCount();
|
||||
autoResizeTextarea();
|
||||
sendMessageBtn.prop('disabled', false);
|
||||
sendMessage();
|
||||
}
|
||||
|
||||
// UI utilities
|
||||
function updateCharacterCount() {
|
||||
const currentLength = messageInput.val().length;
|
||||
charCount.text(currentLength);
|
||||
function updateCharacterCount() {
|
||||
const currentLength = messageInput.val().length;
|
||||
charCount.text(currentLength);
|
||||
|
||||
if (currentLength > WARNING_THRESHOLD) {
|
||||
charCount.parent().addClass('warning');
|
||||
} else {
|
||||
charCount.parent().removeClass('warning');
|
||||
}
|
||||
}
|
||||
if (currentLength > WARNING_THRESHOLD) {
|
||||
charCount.parent().addClass('warning');
|
||||
} else {
|
||||
charCount.parent().removeClass('warning');
|
||||
}
|
||||
}
|
||||
|
||||
function autoResizeTextarea() {
|
||||
messageInput[0].style.height = 'auto';
|
||||
messageInput[0].style.height = (messageInput[0].scrollHeight) + 'px';
|
||||
}
|
||||
function autoResizeTextarea() {
|
||||
messageInput[0].style.height = 'auto';
|
||||
messageInput[0].style.height = (messageInput[0].scrollHeight) + 'px';
|
||||
}
|
||||
|
||||
function toggleSendButton() {
|
||||
sendMessageBtn.prop('disabled', !messageInput.val().trim());
|
||||
}
|
||||
function toggleSendButton() {
|
||||
sendMessageBtn.prop('disabled', !messageInput.val().trim());
|
||||
}
|
||||
|
||||
// Chat actions
|
||||
function sendMessage() {
|
||||
const message = messageInput.val().trim();
|
||||
if (!message) return;
|
||||
function sendMessage() {
|
||||
const message = messageInput.val().trim();
|
||||
if (!message) return;
|
||||
|
||||
// Add user message to chat
|
||||
addMessage(message, true);
|
||||
addMessage(message, true);
|
||||
|
||||
// Clear input and reset UI
|
||||
resetInputUI();
|
||||
resetInputUI();
|
||||
|
||||
// Show typing indicator
|
||||
showTypingIndicator();
|
||||
showTypingIndicator();
|
||||
|
||||
// Send to backend
|
||||
$.ajax({
|
||||
url: '{% url "haikalbot:haikalbot" %}',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
prompt: message,
|
||||
language: '{{ LANGUAGE_CODE }}'
|
||||
}),
|
||||
headers: {
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
},
|
||||
success: function(response) {
|
||||
processBotResponse(response);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
handleRequestError(error);
|
||||
$.ajax({
|
||||
url: '{% url "haikalbot:haikalbot" %}',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
prompt: message,
|
||||
language: '{{ LANGUAGE_CODE }}'
|
||||
}),
|
||||
headers: {
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
},
|
||||
success: function(response) {
|
||||
processBotResponse(response);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
handleRequestError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetInputUI() {
|
||||
messageInput.val('').css('height', 'auto');
|
||||
charCount.text('0').parent().removeClass('warning');
|
||||
sendMessageBtn.prop('disabled', true);
|
||||
}
|
||||
function resetInputUI() {
|
||||
messageInput.val('').css('height', 'auto');
|
||||
charCount.text('0').parent().removeClass('warning');
|
||||
sendMessageBtn.prop('disabled', true);
|
||||
}
|
||||
|
||||
function processBotResponse(response) {
|
||||
hideTypingIndicator();
|
||||
function processBotResponse(response) {
|
||||
hideTypingIndicator();
|
||||
|
||||
// Debug response structure
|
||||
console.log("API Response:", response);
|
||||
console.log("API Response:", response);
|
||||
|
||||
let botResponse = '';
|
||||
let botResponse = '';
|
||||
|
||||
// Check for direct response first
|
||||
if (response.response) {
|
||||
botResponse = response.response;
|
||||
}
|
||||
if (response.response) {
|
||||
botResponse = response.response;
|
||||
}
|
||||
// Then check for insights data
|
||||
else if (hasInsightsData(response)) {
|
||||
botResponse = formatInsightsResponse(response);
|
||||
}
|
||||
else if (hasInsightsData(response)) {
|
||||
botResponse = formatInsightsResponse(response);
|
||||
}
|
||||
// Fallback
|
||||
else {
|
||||
botResponse = isArabic ?
|
||||
'عذرًا، لم أتمكن من معالجة طلبك. يبدو أن هيكل الاستجابة غير متوقع.' :
|
||||
'Sorry, I couldn\'t process your request. The response structure appears unexpected.';
|
||||
console.error("Unexpected response structure:", response);
|
||||
}
|
||||
else {
|
||||
botResponse = isArabic ?
|
||||
'عذرًا، لم أتمكن من معالجة طلبك. يبدو أن هيكل الاستجابة غير متوقع.' :
|
||||
'Sorry, I couldn\'t process your request. The response structure appears unexpected.';
|
||||
console.error("Unexpected response structure:", response);
|
||||
}
|
||||
|
||||
addMessage(botResponse, false);
|
||||
scrollToBottom();
|
||||
}
|
||||
addMessage(botResponse, false);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function hasInsightsData(response) {
|
||||
return response.insights || response['التحليلات'] ||
|
||||
response.recommendations || response['التوصيات'];
|
||||
}
|
||||
function hasInsightsData(response) {
|
||||
return response.insights || response['التحليلات'] ||
|
||||
response.recommendations || response['التوصيات'];
|
||||
}
|
||||
|
||||
function handleRequestError(error) {
|
||||
hideTypingIndicator();
|
||||
const errorMsg = isArabic ?
|
||||
'عذرًا، حدث خطأ أثناء معالجة طلبك. يرجى المحاولة مرة أخرى.' :
|
||||
'Sorry, an error occurred while processing your request. Please try again.';
|
||||
addMessage(errorMsg, false);
|
||||
console.error('API Error:', error);
|
||||
}
|
||||
function handleRequestError(error) {
|
||||
hideTypingIndicator();
|
||||
const errorMsg = isArabic ?
|
||||
'عذرًا، حدث خطأ أثناء معالجة طلبك. يرجى المحاولة مرة أخرى.' :
|
||||
'Sorry, an error occurred while processing your request. Please try again.';
|
||||
addMessage(errorMsg, false);
|
||||
console.error('API Error:', error);
|
||||
}
|
||||
|
||||
// Chat management
|
||||
function clearChat() {
|
||||
if (confirm(isArabic ?
|
||||
'هل أنت متأكد من أنك تريد مسح المحادثة؟' :
|
||||
'Are you sure you want to clear the chat?')) {
|
||||
const welcomeMessage = chatMessages.children().first();
|
||||
chatMessages.empty().append(welcomeMessage);
|
||||
}
|
||||
}
|
||||
function clearChat() {
|
||||
if (confirm(isArabic ?
|
||||
'هل أنت متأكد من أنك تريد مسح المحادثة؟' :
|
||||
'Are you sure you want to clear the chat?')) {
|
||||
const welcomeMessage = chatMessages.children().first();
|
||||
chatMessages.empty().append(welcomeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
function exportChat() {
|
||||
let chatContent = '';
|
||||
$('.message').each(function() {
|
||||
const isUser = $(this).hasClass('user-message');
|
||||
const sender = isUser ?
|
||||
(isArabic ? 'أنت' : 'You') :
|
||||
(isArabic ? 'المساعد الذكي' : 'AI Assistant');
|
||||
const text = $(this).find('.chat-message').text().trim();
|
||||
const time = $(this).find('.text-400').text().trim();
|
||||
function exportChat() {
|
||||
let chatContent = '';
|
||||
$('.message').each(function() {
|
||||
const isUser = $(this).hasClass('user-message');
|
||||
const sender = isUser ?
|
||||
(isArabic ? 'أنت' : 'You') :
|
||||
(isArabic ? 'المساعد الذكي' : 'AI Assistant');
|
||||
const text = $(this).find('.chat-message').text().trim();
|
||||
const time = $(this).find('.text-400').text().trim();
|
||||
|
||||
chatContent += `${sender} (${time}):\n${text}\n\n`;
|
||||
});
|
||||
chatContent += `${sender} (${time}):\n${text}\n\n`;
|
||||
});
|
||||
|
||||
downloadTextFile(chatContent, 'chat-export-' + new Date().toISOString().slice(0, 10) + '.txt');
|
||||
}
|
||||
downloadTextFile(chatContent, 'chat-export-' + new Date().toISOString().slice(0, 10) + '.txt');
|
||||
}
|
||||
|
||||
function downloadTextFile(content, filename) {
|
||||
const blob = new Blob([content], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
function downloadTextFile(content, filename) {
|
||||
const blob = new Blob([content], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// Message display functions
|
||||
function addMessage(text, isUser) {
|
||||
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
const messageClass = isUser ? 'user-message justify-content-between' : '';
|
||||
function addMessage(text, isUser) {
|
||||
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
const messageClass = isUser ? 'user-message justify-content-between' : '';
|
||||
|
||||
const avatarHtml = getAvatarHtml(isUser);
|
||||
const messageHtml = getMessageHtml(text, isUser, time);
|
||||
const avatarHtml = getAvatarHtml(isUser);
|
||||
const messageHtml = getMessageHtml(text, isUser, time);
|
||||
|
||||
const fullMessageHtml = `
|
||||
const fullMessageHtml = `
|
||||
<div class="message d-flex mb-3 ${messageClass}">
|
||||
${avatarHtml}
|
||||
${messageHtml}
|
||||
</div>
|
||||
`;
|
||||
|
||||
chatMessages.append(fullMessageHtml);
|
||||
scrollToBottom();
|
||||
}
|
||||
chatMessages.append(fullMessageHtml);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function getAvatarHtml(isUser) {
|
||||
if (isUser) {
|
||||
return `
|
||||
function getAvatarHtml(isUser) {
|
||||
if (isUser) {
|
||||
return `
|
||||
<div class="avatar avatar-l ms-3 order-1">
|
||||
<div class="avatar-name rounded-circle">
|
||||
<span><i class="fas fa-user"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return `
|
||||
}
|
||||
return `
|
||||
<div class="me-3">
|
||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||
<img class="d-dark-none" src="{% static 'images/favicons/haikalbot_v1.png' %}" width="32" />
|
||||
@ -362,11 +362,11 @@ $(document).ready(function() {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function getMessageHtml(text, isUser, time) {
|
||||
if (isUser) {
|
||||
return `
|
||||
function getMessageHtml(text, isUser, time) {
|
||||
if (isUser) {
|
||||
return `
|
||||
<div class="flex-1 order-0">
|
||||
<div class="w-xxl-75 ms-auto">
|
||||
<div class="d-flex hover-actions-trigger align-items-center">
|
||||
@ -385,11 +385,11 @@ $(document).ready(function() {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
const processedText = marked.parse(text);
|
||||
const processedText = marked.parse(text);
|
||||
|
||||
return `
|
||||
return `
|
||||
<div class="flex-1">
|
||||
<div class="w-xxl-75">
|
||||
<div class="d-flex hover-actions-trigger align-items-center">
|
||||
@ -408,10 +408,10 @@ $(document).ready(function() {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function showTypingIndicator() {
|
||||
const typingHtml = `
|
||||
function showTypingIndicator() {
|
||||
const typingHtml = `
|
||||
<div class="message d-flex mb-3" id="typingIndicator">
|
||||
<div class="avatar avatar-l me-3">
|
||||
<div class="avatar-name rounded-circle">
|
||||
@ -425,97 +425,97 @@ $(document).ready(function() {
|
||||
</div>
|
||||
`;
|
||||
|
||||
chatMessages.append(typingHtml);
|
||||
scrollToBottom();
|
||||
}
|
||||
chatMessages.append(typingHtml);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function hideTypingIndicator() {
|
||||
$('#typingIndicator').remove();
|
||||
}
|
||||
function hideTypingIndicator() {
|
||||
$('#typingIndicator').remove();
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
chatMessages.scrollTop(chatMessages[0].scrollHeight);
|
||||
}
|
||||
function scrollToBottom() {
|
||||
chatMessages.scrollTop(chatMessages[0].scrollHeight);
|
||||
}
|
||||
|
||||
// Insights formatting
|
||||
function formatInsightsResponse(response) {
|
||||
console.log("Formatting insights response:", response);
|
||||
function formatInsightsResponse(response) {
|
||||
console.log("Formatting insights response:", response);
|
||||
|
||||
let formattedResponse = '';
|
||||
let formattedResponse = '';
|
||||
|
||||
// Get data using both possible key formats
|
||||
const insightsData = response.insights || response['التحليلات'] || [];
|
||||
const recommendationsData = response.recommendations || response['التوصيات'] || [];
|
||||
const insightsData = response.insights || response['التحليلات'] || [];
|
||||
const recommendationsData = response.recommendations || response['التوصيات'] || [];
|
||||
|
||||
// Process insights
|
||||
if (insightsData.length > 0) {
|
||||
formattedResponse += isArabic ? '## نتائج التحليل\n\n' : '## Analysis Results\n\n';
|
||||
if (insightsData.length > 0) {
|
||||
formattedResponse += isArabic ? '## نتائج التحليل\n\n' : '## Analysis Results\n\n';
|
||||
|
||||
insightsData.forEach(insight => {
|
||||
if (insight.type) {
|
||||
formattedResponse += `### ${insight.type}\n\n`;
|
||||
}
|
||||
insightsData.forEach(insight => {
|
||||
if (insight.type) {
|
||||
formattedResponse += `### ${insight.type}\n\n`;
|
||||
}
|
||||
|
||||
if (insight.results && Array.isArray(insight.results)) {
|
||||
insight.results.forEach(result => {
|
||||
if (result.error) {
|
||||
formattedResponse += `- **${result.model || ''}**: ${result.error}\n`;
|
||||
} else if (result.count !== undefined) {
|
||||
formattedResponse += `- **${result.model || ''}**: ${result.count}\n`;
|
||||
} else if (result.value !== undefined) {
|
||||
const field = getLocalizedValue(result, 'field', 'الحقل');
|
||||
const statType = getLocalizedValue(result, 'statistic_type', 'نوع_الإحصاء');
|
||||
formattedResponse += `- **${result.model || ''}**: ${statType} ${isArabic ? 'لـ' : 'of'} ${field} = ${result.value}\n`;
|
||||
if (insight.results && Array.isArray(insight.results)) {
|
||||
insight.results.forEach(result => {
|
||||
if (result.error) {
|
||||
formattedResponse += `- **${result.model || ''}**: ${result.error}\n`;
|
||||
} else if (result.count !== undefined) {
|
||||
formattedResponse += `- **${result.model || ''}**: ${result.count}\n`;
|
||||
} else if (result.value !== undefined) {
|
||||
const field = getLocalizedValue(result, 'field', 'الحقل');
|
||||
const statType = getLocalizedValue(result, 'statistic_type', 'نوع_الإحصاء');
|
||||
formattedResponse += `- **${result.model || ''}**: ${statType} ${isArabic ? 'لـ' : 'of'} ${field} = ${result.value}\n`;
|
||||
}
|
||||
});
|
||||
formattedResponse += '\n';
|
||||
}
|
||||
|
||||
if (insight.relationships && Array.isArray(insight.relationships)) {
|
||||
formattedResponse += isArabic ? '### العلاقات\n\n' : '### Relationships\n\n';
|
||||
insight.relationships.forEach(rel => {
|
||||
const from = getLocalizedValue(rel, 'from', 'من');
|
||||
const to = getLocalizedValue(rel, 'to', 'إلى');
|
||||
const type = getLocalizedValue(rel, 'type', 'نوع');
|
||||
formattedResponse += `- ${from} → ${to} (${type})\n`;
|
||||
});
|
||||
formattedResponse += '\n';
|
||||
}
|
||||
});
|
||||
formattedResponse += '\n';
|
||||
}
|
||||
|
||||
if (insight.relationships && Array.isArray(insight.relationships)) {
|
||||
formattedResponse += isArabic ? '### العلاقات\n\n' : '### Relationships\n\n';
|
||||
insight.relationships.forEach(rel => {
|
||||
const from = getLocalizedValue(rel, 'from', 'من');
|
||||
const to = getLocalizedValue(rel, 'to', 'إلى');
|
||||
const type = getLocalizedValue(rel, 'type', 'نوع');
|
||||
formattedResponse += `- ${from} → ${to} (${type})\n`;
|
||||
});
|
||||
formattedResponse += '\n';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process recommendations
|
||||
if (recommendationsData.length > 0) {
|
||||
formattedResponse += isArabic ? '## التوصيات\n\n' : '## Recommendations\n\n';
|
||||
recommendationsData.forEach(rec => {
|
||||
formattedResponse += `- ${rec}\n`;
|
||||
if (recommendationsData.length > 0) {
|
||||
formattedResponse += isArabic ? '## التوصيات\n\n' : '## Recommendations\n\n';
|
||||
recommendationsData.forEach(rec => {
|
||||
formattedResponse += `- ${rec}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
return formattedResponse.trim() ||
|
||||
(isArabic ? 'تم تحليل البيانات بنجاح ولكن لا توجد نتائج للعرض.' : 'Data analyzed successfully but no results to display.');
|
||||
}
|
||||
|
||||
function getLocalizedValue(obj, englishKey, arabicKey) {
|
||||
return isArabic ? (obj[arabicKey] || obj[englishKey] || '') : (obj[englishKey] || obj[arabicKey] || '');
|
||||
}
|
||||
|
||||
$(document).on('click', '.copy-btn', function() {
|
||||
const text = $(this).closest('.d-flex').find('.chat-message').text().trim();
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
showCopySuccess($(this));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return formattedResponse.trim() ||
|
||||
(isArabic ? 'تم تحليل البيانات بنجاح ولكن لا توجد نتائج للعرض.' : 'Data analyzed successfully but no results to display.');
|
||||
}
|
||||
function showCopySuccess(button) {
|
||||
const originalIcon = button.html();
|
||||
button.html('<i class="fas fa-check"></i>');
|
||||
setTimeout(() => {
|
||||
button.html(originalIcon);
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function getLocalizedValue(obj, englishKey, arabicKey) {
|
||||
return isArabic ? (obj[arabicKey] || obj[englishKey] || '') : (obj[englishKey] || obj[arabicKey] || '');
|
||||
}
|
||||
|
||||
$(document).on('click', '.copy-btn', function() {
|
||||
const text = $(this).closest('.d-flex').find('.chat-message').text().trim();
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
showCopySuccess($(this));
|
||||
scrollToBottom();
|
||||
});
|
||||
});
|
||||
|
||||
function showCopySuccess(button) {
|
||||
const originalIcon = button.html();
|
||||
button.html('<i class="fas fa-check"></i>');
|
||||
setTimeout(() => {
|
||||
button.html(originalIcon);
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
scrollToBottom();
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
{% endblock content %}
|
||||
@ -342,18 +342,18 @@
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-vertical-footer">
|
||||
<button class="btn navbar-vertical-toggle border-0 fw-semibold w-100 white-space-nowrap d-flex align-items-center">
|
||||
<span class="uil uil-left-arrow-to-left fs-8"></span><span class="uil uil-arrow-from-right fs-8"></span><span class="navbar-vertical-footer-text ms-2">Collapsed View</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-vertical-footer">
|
||||
<button class="btn navbar-vertical-toggle border-0 fw-semibold w-100 white-space-nowrap d-flex align-items-center">
|
||||
<span class="uil uil-left-arrow-to-left fs-8"></span><span class="uil uil-arrow-from-right fs-8"></span><span class="navbar-vertical-footer-text ms-2">Collapsed View</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
type="radio"
|
||||
name="exterior"
|
||||
value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
||||
|
||||
|
||||
<div class="card-body color-display"
|
||||
style="background-color: rgb({{ color.rgb }})">
|
||||
<div class="">
|
||||
|
||||
@ -3,37 +3,37 @@
|
||||
{% block title %}{{ _("Car Details") }}{% endblock %}
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
.disabled{
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.car_status {
|
||||
position: absolute;
|
||||
top: 13%;
|
||||
left: 90%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.disabled{
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.car_status {
|
||||
position: absolute;
|
||||
top: 13%;
|
||||
left: 90%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
{% endblock customCSS %}
|
||||
{% endblock customCSS %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
{% if not car.ready and not car.status == 'sold' %}
|
||||
<div class="alert alert-outline-warning d-flex align-items-center"
|
||||
role="alert">
|
||||
<i class="fa-solid fa-circle-info fs-6"></i>
|
||||
{%if not car.finances and not car.colors%}
|
||||
<p class="mb-0 flex-1">
|
||||
{{ _("This car information is not complete , please add colors and finances both before making it ready for sale .") }}
|
||||
</p>
|
||||
{% elif car.finances and not car.colors %}
|
||||
<p class="mb-0 flex-1">
|
||||
{{ _("This car information is not complete , please add colors before making it ready for sale .") }}
|
||||
</p>
|
||||
<p class="mb-0 flex-1">
|
||||
{{ _("This car information is not complete , please add colors and finances both before making it ready for sale .") }}
|
||||
</p>
|
||||
{% elif car.finances and not car.colors %}
|
||||
<p class="mb-0 flex-1">
|
||||
{{ _("This car information is not complete , please add colors before making it ready for sale .") }}
|
||||
</p>
|
||||
{%else%}
|
||||
<p class="mb-0 flex-1">
|
||||
{{ _("This car information is not complete , please add finances before making it ready for sale .") }}
|
||||
</p>
|
||||
<p class="mb-0 flex-1">
|
||||
{{ _("This car information is not complete , please add finances before making it ready for sale .") }}
|
||||
</p>
|
||||
{%endif%}
|
||||
<button class="btn-close"
|
||||
type="button"
|
||||
@ -277,13 +277,13 @@
|
||||
{% else %}
|
||||
<span class="badge bg-danger">{% trans "Cannot Edit, Car in Transfer." %}</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p>{% trans "No finance details available." %}</p>
|
||||
{% if perms.inventory.add_carfinance %}
|
||||
<a href="{% url 'car_finance_create' car.slug %}"
|
||||
class="btn btn-phoenix-success btn-sm mb-3">{% trans "Add" %}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% else %}
|
||||
<p>{% trans "No finance details available." %}</p>
|
||||
{% if perms.inventory.add_carfinance %}
|
||||
<a href="{% url 'car_finance_create' car.slug %}"
|
||||
class="btn btn-phoenix-success btn-sm mb-3">{% trans "Add" %}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
@ -318,7 +318,7 @@
|
||||
style="background-color: rgb({{ car.colors.interior.rgb }})"></div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
{% if not car.get_transfer %}
|
||||
@ -332,16 +332,16 @@
|
||||
{% else %}
|
||||
<tr>
|
||||
|
||||
<td colspan="2">
|
||||
<td colspan="2">
|
||||
<p>{% trans "No color details available." %}</p>
|
||||
{% if perms.inventory.add_carcolors %}
|
||||
<a class="btn btn-phoenix-success btn-sm mb-3" href="{% url 'add_color' car.slug %}">{{ _("Add Color") }}</a>
|
||||
<a class="btn btn-phoenix-success btn-sm mb-3" href="{% url 'add_color' car.slug %}">{{ _("Add Color") }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<!--test-->
|
||||
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@ -390,19 +390,19 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if perms.inventory.change_carreservation %}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-phoenix-success"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#reserveModal">
|
||||
{% trans 'Reserve' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if perms.inventory.change_carreservation %}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-phoenix-success"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#reserveModal">
|
||||
{% trans 'Reserve' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{% endif %}
|
||||
</table>
|
||||
@ -570,152 +570,152 @@
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const csrftoken = getCookie("csrftoken");
|
||||
const ajaxUrl = "{% url 'ajax_handler' %}";
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const csrftoken = getCookie("csrftoken");
|
||||
const ajaxUrl = "{% url 'ajax_handler' %}";
|
||||
|
||||
const customCardModal = document.getElementById("customCardModal");
|
||||
const modalBody = customCardModal.querySelector(".modal-body");
|
||||
const customCardModal = document.getElementById("customCardModal");
|
||||
const modalBody = customCardModal.querySelector(".modal-body");
|
||||
|
||||
// When the modal is triggered, load the form
|
||||
customCardModal.addEventListener("show.bs.modal", function () {
|
||||
const url = "{% url 'add_custom_card' car.slug %}";
|
||||
customCardModal.addEventListener("show.bs.modal", function () {
|
||||
const url = "{% url 'add_custom_card' car.slug %}";
|
||||
|
||||
fetch(url)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
modalBody.innerHTML = html;
|
||||
})
|
||||
.catch((error) => {
|
||||
modalBody.innerHTML = '<p class="text-danger">Error loading form. Please try again later.</p>';
|
||||
console.error("Error loading form:", error);
|
||||
});
|
||||
});
|
||||
fetch(url)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
modalBody.innerHTML = html;
|
||||
})
|
||||
.catch((error) => {
|
||||
modalBody.innerHTML = '<p class="text-danger">Error loading form. Please try again later.</p>';
|
||||
console.error("Error loading form:", error);
|
||||
});
|
||||
});
|
||||
|
||||
customCardModal.addEventListener("hidden.bs.modal", function () {
|
||||
modalBody.innerHTML = "";
|
||||
});
|
||||
customCardModal.addEventListener("hidden.bs.modal", function () {
|
||||
modalBody.innerHTML = "";
|
||||
});
|
||||
|
||||
const registrationModal = document.getElementById("registrationModal");
|
||||
const modalBody_r = registrationModal.querySelector(".modal-body");
|
||||
const registrationModal = document.getElementById("registrationModal");
|
||||
const modalBody_r = registrationModal.querySelector(".modal-body");
|
||||
|
||||
// When the modal is triggered, load the form
|
||||
registrationModal.addEventListener("show.bs.modal", function () {
|
||||
const url = "{% url 'add_registration' car.slug %}";
|
||||
registrationModal.addEventListener("show.bs.modal", function () {
|
||||
const url = "{% url 'add_registration' car.slug %}";
|
||||
|
||||
fetch(url)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
modalBody_r.innerHTML = html;
|
||||
})
|
||||
.catch((error) => {
|
||||
modalBody_r.innerHTML = '<p class="text-danger">{{_("Error loading form. Please try again later")}}.</p>';
|
||||
console.error("Error loading form:", error);
|
||||
});
|
||||
});
|
||||
fetch(url)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
modalBody_r.innerHTML = html;
|
||||
})
|
||||
.catch((error) => {
|
||||
modalBody_r.innerHTML = '<p class="text-danger">{{_("Error loading form. Please try again later")}}.</p>';
|
||||
console.error("Error loading form:", error);
|
||||
});
|
||||
});
|
||||
|
||||
registrationModal.addEventListener("hidden.bs.modal", function () {
|
||||
modalBody_r.innerHTML = "";
|
||||
});
|
||||
registrationModal.addEventListener("hidden.bs.modal", function () {
|
||||
modalBody_r.innerHTML = "";
|
||||
});
|
||||
|
||||
const showSpecificationButton = document.getElementById("specification-btn");
|
||||
const specificationsContent = document.getElementById("specificationsContent");
|
||||
const showSpecificationButton = document.getElementById("specification-btn");
|
||||
const specificationsContent = document.getElementById("specificationsContent");
|
||||
|
||||
showSpecificationButton.addEventListener("click", function () {
|
||||
specificationsContent.innerHTML = "";
|
||||
fetch(`${ajaxUrl}?action=get_specifications&trim_id={{ car.id_car_trim.id_car_trim }}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrftoken,
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.length > 0) {
|
||||
data.forEach(function (parent) {
|
||||
showSpecificationButton.addEventListener("click", function () {
|
||||
specificationsContent.innerHTML = "";
|
||||
fetch(`${ajaxUrl}?action=get_specifications&trim_id={{ car.id_car_trim.id_car_trim }}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrftoken,
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.length > 0) {
|
||||
data.forEach(function (parent) {
|
||||
// Create a section container
|
||||
const section = document.createElement("div");
|
||||
section.classList.add("mb-4", "p-3", "border", "rounded");
|
||||
const section = document.createElement("div");
|
||||
section.classList.add("mb-4", "p-3", "border", "rounded");
|
||||
|
||||
// Add section title
|
||||
const sectionTitle = document.createElement("h4");
|
||||
sectionTitle.classList.add("mb-3", "fw-bold");
|
||||
sectionTitle.textContent = parent.parent_name;
|
||||
section.appendChild(sectionTitle);
|
||||
const sectionTitle = document.createElement("h4");
|
||||
sectionTitle.classList.add("mb-3", "fw-bold");
|
||||
sectionTitle.textContent = parent.parent_name;
|
||||
section.appendChild(sectionTitle);
|
||||
|
||||
// Create a table for the specifications
|
||||
const specsTable = document.createElement("div");
|
||||
specsTable.classList.add("row");
|
||||
const specsTable = document.createElement("div");
|
||||
specsTable.classList.add("row");
|
||||
|
||||
parent.specifications.forEach(function (specification) {
|
||||
parent.specifications.forEach(function (specification) {
|
||||
// Create a row for each specification
|
||||
const specRow = document.createElement("div");
|
||||
specRow.classList.add("row", "mb-2");
|
||||
const specRow = document.createElement("div");
|
||||
specRow.classList.add("row", "mb-2");
|
||||
|
||||
// Left Column: Spec name
|
||||
const specName = document.createElement("div");
|
||||
specName.classList.add("col-6", "text-muted");
|
||||
specName.textContent = specification.s_name;
|
||||
const specName = document.createElement("div");
|
||||
specName.classList.add("col-6", "text-muted");
|
||||
specName.textContent = specification.s_name;
|
||||
|
||||
// Right Column: Spec value + unit
|
||||
const specValue = document.createElement("div");
|
||||
specValue.classList.add("col-6", "text-end");
|
||||
specValue.textContent = `${specification.s_value} ${specification.s_unit || ""}`;
|
||||
const specValue = document.createElement("div");
|
||||
specValue.classList.add("col-6", "text-end");
|
||||
specValue.textContent = `${specification.s_value} ${specification.s_unit || ""}`;
|
||||
|
||||
specRow.appendChild(specName);
|
||||
specRow.appendChild(specValue);
|
||||
specRow.appendChild(specName);
|
||||
specRow.appendChild(specValue);
|
||||
|
||||
specsTable.appendChild(specRow);
|
||||
});
|
||||
specsTable.appendChild(specRow);
|
||||
});
|
||||
|
||||
section.appendChild(specsTable);
|
||||
specificationsContent.appendChild(section);
|
||||
});
|
||||
} else {
|
||||
specificationsContent.innerHTML = '<p>{% trans "No specifications available." %}</p>';
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
specificationsContent.innerHTML = '<p>{% trans "Error loading specifications." %}</p>';
|
||||
console.error("Error fetching specifications:", error);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll(".reserve-btn").forEach((button) => {
|
||||
button.addEventListener("click", async function () {
|
||||
try {
|
||||
const response = await fetch(`{% url 'reserve_car' car.slug %}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-CSRFToken": csrfToken,
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
section.appendChild(specsTable);
|
||||
specificationsContent.appendChild(section);
|
||||
});
|
||||
} else {
|
||||
specificationsContent.innerHTML = '<p>{% trans "No specifications available." %}</p>';
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
specificationsContent.innerHTML = '<p>{% trans "Error loading specifications." %}</p>';
|
||||
console.error("Error fetching specifications:", error);
|
||||
});
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
this.textContent = "Reserved";
|
||||
this.classList.remove("btn-phoenix-success");
|
||||
this.classList.add("btn-phoenix-danger");
|
||||
this.disabled = true;
|
||||
alert("Car reserved successfully.");
|
||||
} else {
|
||||
alert(data.message || "Failed to reserve the car.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
alert("An error occurred. Please try again.");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if('{{car.status}}' == "sold"){
|
||||
document.querySelector(".row-fluid").querySelectorAll("button").forEach((button) => {
|
||||
button.classList.add("d-none");
|
||||
});
|
||||
document.querySelector(".row-fluid").querySelectorAll("a").forEach((button) => {
|
||||
button.classList.add("d-none");
|
||||
});
|
||||
}
|
||||
document.querySelectorAll(".reserve-btn").forEach((button) => {
|
||||
button.addEventListener("click", async function () {
|
||||
try {
|
||||
const response = await fetch(`{% url 'reserve_car' car.slug %}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-CSRFToken": csrfToken,
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
this.textContent = "Reserved";
|
||||
this.classList.remove("btn-phoenix-success");
|
||||
this.classList.add("btn-phoenix-danger");
|
||||
this.disabled = true;
|
||||
alert("Car reserved successfully.");
|
||||
} else {
|
||||
alert(data.message || "Failed to reserve the car.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
alert("An error occurred. Please try again.");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if('{{car.status}}' == "sold"){
|
||||
document.querySelector(".row-fluid").querySelectorAll("button").forEach((button) => {
|
||||
button.classList.add("d-none");
|
||||
});
|
||||
document.querySelector(".row-fluid").querySelectorAll("a").forEach((button) => {
|
||||
button.classList.add("d-none");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@ -3,16 +3,16 @@
|
||||
{%block title%} {%trans 'Add New Car'%} {%endblock%}
|
||||
{% block content %}
|
||||
<style>
|
||||
#video {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.disabled{
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
#video {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.disabled{
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
<!-- JavaScript Section -->
|
||||
<script src="{% static 'vendors/zxing/index.min.js' %}"></script>
|
||||
@ -314,335 +314,335 @@
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let cookie of cookies) {
|
||||
cookie = cookie.trim();
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let cookie of cookies) {
|
||||
cookie = cookie.trim();
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const csrfToken = getCookie("csrftoken");
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const csrfToken = getCookie("csrftoken");
|
||||
|
||||
const vinInput = document.getElementById("{{ form.vin.id_for_label }}");
|
||||
const decodeVinBtn = document.getElementById("decodeVinBtn");
|
||||
const makeSelect = document.getElementById("{{ form.id_car_make.id_for_label }}");
|
||||
const modelSelect = document.getElementById("{{ form.id_car_model.id_for_label }}");
|
||||
const yearSelect = document.getElementById("{{ form.year.id_for_label }}");
|
||||
const serieSelect = document.getElementById("{{ form.id_car_serie.id_for_label }}");
|
||||
const trimSelect = document.getElementById("{{ form.id_car_trim.id_for_label }}");
|
||||
const equipmentSelect = document.getElementById("equipment_id")
|
||||
const showSpecificationButton = document.getElementById("specification-btn");
|
||||
const showEquipmentButton = document.getElementById("options-btn")
|
||||
const specificationsContent = document.getElementById("specificationsContent");
|
||||
const optionsContent = document.getElementById("optionsContent")
|
||||
const generationContainer = document.getElementById("generation-div")
|
||||
const vinInput = document.getElementById("{{ form.vin.id_for_label }}");
|
||||
const decodeVinBtn = document.getElementById("decodeVinBtn");
|
||||
const makeSelect = document.getElementById("{{ form.id_car_make.id_for_label }}");
|
||||
const modelSelect = document.getElementById("{{ form.id_car_model.id_for_label }}");
|
||||
const yearSelect = document.getElementById("{{ form.year.id_for_label }}");
|
||||
const serieSelect = document.getElementById("{{ form.id_car_serie.id_for_label }}");
|
||||
const trimSelect = document.getElementById("{{ form.id_car_trim.id_for_label }}");
|
||||
const equipmentSelect = document.getElementById("equipment_id")
|
||||
const showSpecificationButton = document.getElementById("specification-btn");
|
||||
const showEquipmentButton = document.getElementById("options-btn")
|
||||
const specificationsContent = document.getElementById("specificationsContent");
|
||||
const optionsContent = document.getElementById("optionsContent")
|
||||
const generationContainer = document.getElementById("generation-div")
|
||||
|
||||
const ajaxUrl = "{% url 'ajax_handler' %}";
|
||||
const ajaxUrl = "{% url 'ajax_handler' %}";
|
||||
|
||||
const closeButton = document.querySelector(".btn-close");
|
||||
const scanVinBtn = document.getElementById("scan-vin-btn");
|
||||
const videoElement = document.getElementById("video");
|
||||
const resultDisplay = document.getElementById("result");
|
||||
const fallbackButton = document.getElementById("ocr-fallback-btn");
|
||||
let codeReader;
|
||||
codeReader = new ZXing.BrowserMultiFormatReader();
|
||||
let currentStream = null;
|
||||
const closeButton = document.querySelector(".btn-close");
|
||||
const scanVinBtn = document.getElementById("scan-vin-btn");
|
||||
const videoElement = document.getElementById("video");
|
||||
const resultDisplay = document.getElementById("result");
|
||||
const fallbackButton = document.getElementById("ocr-fallback-btn");
|
||||
let codeReader;
|
||||
codeReader = new ZXing.BrowserMultiFormatReader();
|
||||
let currentStream = null;
|
||||
|
||||
function closeModal() {
|
||||
stopScanner();
|
||||
try {
|
||||
const scannerModal = document.getElementById("scannerModal");
|
||||
if (scannerModal) {
|
||||
document.activeElement.blur();
|
||||
scannerModal.setAttribute("inert", "true");
|
||||
const modalInstance = bootstrap.Modal.getInstance(scannerModal);
|
||||
if (modalInstance) modalInstance.hide();
|
||||
if (scanVinBtn) scanVinBtn.focus();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error closing scanner modal:", err);
|
||||
}
|
||||
}
|
||||
function closeModal() {
|
||||
stopScanner();
|
||||
try {
|
||||
const scannerModal = document.getElementById("scannerModal");
|
||||
if (scannerModal) {
|
||||
document.activeElement.blur();
|
||||
scannerModal.setAttribute("inert", "true");
|
||||
const modalInstance = bootstrap.Modal.getInstance(scannerModal);
|
||||
if (modalInstance) modalInstance.hide();
|
||||
if (scanVinBtn) scanVinBtn.focus();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error closing scanner modal:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function decodeVin() {
|
||||
const vinNumber = vinInput.value.trim();
|
||||
if (vinNumber.length !== 17) {
|
||||
Swal.fire("error", "{% trans 'Please enter a valid VIN.' %}");
|
||||
async function decodeVin() {
|
||||
const vinNumber = vinInput.value.trim();
|
||||
if (vinNumber.length !== 17) {
|
||||
Swal.fire("error", "{% trans 'Please enter a valid VIN.' %}");
|
||||
/*alert("{% trans 'Please enter a valid VIN.' %}");*/
|
||||
return;
|
||||
}
|
||||
showLoading();
|
||||
try {
|
||||
const response = await fetch(`${ajaxUrl}?action=decode_vin&vin_no=${vinNumber}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
hideLoading();
|
||||
await updateFields(data.data);
|
||||
} else {
|
||||
hideLoading();
|
||||
Swal.fire("{% trans 'error' %}", data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error decoding VIN:", error);
|
||||
hideLoading();
|
||||
Swal.fire("error", "{% trans 'An error occurred while decoding the VIN.' %}");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
showLoading();
|
||||
try {
|
||||
const response = await fetch(`${ajaxUrl}?action=decode_vin&vin_no=${vinNumber}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
hideLoading();
|
||||
await updateFields(data.data);
|
||||
} else {
|
||||
hideLoading();
|
||||
Swal.fire("{% trans 'error' %}", data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error decoding VIN:", error);
|
||||
hideLoading();
|
||||
Swal.fire("error", "{% trans 'An error occurred while decoding the VIN.' %}");
|
||||
}
|
||||
}
|
||||
|
||||
async function updateFields(vinData) {
|
||||
console.log(vinData);
|
||||
if (vinData.make_id) {
|
||||
makeSelect.value = vinData.make_id;
|
||||
document.getElementById("make-check").innerHTML = "✓";
|
||||
await loadModels(vinData.make_id);
|
||||
}
|
||||
if (vinData.model_id) {
|
||||
modelSelect.value = vinData.model_id;
|
||||
document.getElementById("model-check").innerHTML = "✓";
|
||||
async function updateFields(vinData) {
|
||||
console.log(vinData);
|
||||
if (vinData.make_id) {
|
||||
makeSelect.value = vinData.make_id;
|
||||
document.getElementById("make-check").innerHTML = "✓";
|
||||
await loadModels(vinData.make_id);
|
||||
}
|
||||
if (vinData.model_id) {
|
||||
modelSelect.value = vinData.model_id;
|
||||
document.getElementById("model-check").innerHTML = "✓";
|
||||
|
||||
await loadSeries(vinData.model_id, vinData.year);
|
||||
}
|
||||
if (vinData.year) {
|
||||
yearSelect.value = vinData.year;
|
||||
document.getElementById("year-check").innerHTML = "✓";
|
||||
}
|
||||
}
|
||||
await loadSeries(vinData.model_id, vinData.year);
|
||||
}
|
||||
if (vinData.year) {
|
||||
yearSelect.value = vinData.year;
|
||||
document.getElementById("year-check").innerHTML = "✓";
|
||||
}
|
||||
}
|
||||
|
||||
// Start the scanner
|
||||
async function startScanner() {
|
||||
codeReader
|
||||
.decodeFromVideoDevice(null, videoElement, async (result, err) => {
|
||||
let res = await result;
|
||||
if (result) {
|
||||
vinInput.value = result.text;
|
||||
closeModal();
|
||||
await decodeVin();
|
||||
async function startScanner() {
|
||||
codeReader
|
||||
.decodeFromVideoDevice(null, videoElement, async (result, err) => {
|
||||
let res = await result;
|
||||
if (result) {
|
||||
vinInput.value = result.text;
|
||||
closeModal();
|
||||
await decodeVin();
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
function captureAndOCR() {
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
canvas.width = videoElement.videoWidth;
|
||||
canvas.height = videoElement.videoHeight;
|
||||
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
||||
Tesseract.recognize(canvas.toDataURL("image/png"), "eng")
|
||||
.then(({ data: { text } }) => {
|
||||
const vin = text.match(/[A-HJ-NPR-Z0-9]{17}/);
|
||||
if (vin) vinInput.value = vin[0];
|
||||
closeModal();
|
||||
decodeVin();
|
||||
})
|
||||
.catch((err) => console.error("OCR Error:", err));
|
||||
}
|
||||
function captureAndOCR() {
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
canvas.width = videoElement.videoWidth;
|
||||
canvas.height = videoElement.videoHeight;
|
||||
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
||||
Tesseract.recognize(canvas.toDataURL("image/png"), "eng")
|
||||
.then(({ data: { text } }) => {
|
||||
const vin = text.match(/[A-HJ-NPR-Z0-9]{17}/);
|
||||
if (vin) vinInput.value = vin[0];
|
||||
closeModal();
|
||||
decodeVin();
|
||||
})
|
||||
.catch((err) => console.error("OCR Error:", err));
|
||||
}
|
||||
|
||||
function stopScanner() {
|
||||
if (currentStream) {
|
||||
currentStream.getTracks().forEach((track) => track.stop());
|
||||
currentStream = null;
|
||||
function stopScanner() {
|
||||
if (currentStream) {
|
||||
currentStream.getTracks().forEach((track) => track.stop());
|
||||
currentStream = null;
|
||||
}
|
||||
codeReader.reset();
|
||||
}
|
||||
|
||||
function resetDropdown(dropdown, placeholder) {
|
||||
dropdown.innerHTML = `<option value="">${placeholder}</option>`;
|
||||
}
|
||||
|
||||
async function loadModels(makeId) {
|
||||
resetDropdown(modelSelect, '{% trans "Select" %}');
|
||||
const response = await fetch(`${ajaxUrl}?action=get_models&make_id=${makeId}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((model) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = model.id_car_model;
|
||||
option.textContent = document.documentElement.lang === "en" ? model.name : model.arabic_name;
|
||||
modelSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSeries(modelId, year) {
|
||||
resetDropdown(serieSelect, '{% trans "Select" %}');
|
||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
||||
specificationsContent.innerHTML = "";
|
||||
|
||||
const response = await fetch(`${ajaxUrl}?action=get_series&model_id=${modelId}&year=${year}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log(data)
|
||||
data.forEach((serie) => {
|
||||
|
||||
const option = document.createElement("option");
|
||||
option.value = serie.id_car_serie;
|
||||
option.textContent = document.documentElement.lang === "en" ? serie.name : serie.name;
|
||||
generationContainer.innerHTML = serie.generation_name
|
||||
serieSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadTrims(serie_id, model_id) {
|
||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
||||
specificationsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((trim) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = trim.id_car_trim;
|
||||
option.textContent = document.documentElement.lang === "en" ? trim.name : trim.name;
|
||||
trimSelect.appendChild(option);
|
||||
});
|
||||
showSpecificationButton.disabled = !this.value;
|
||||
}
|
||||
|
||||
async function loadEquipment(trimId){
|
||||
resetDropdown(equipmentSelect, '{% trans "Select" %}');
|
||||
optionsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_equipments&trim_id=${trimId}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-CSRFToken': csrfToken
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((equipment) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = equipment.id_car_equipment;
|
||||
option.textContent = equipment.name;
|
||||
equipmentSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSpecifications(trimId) {
|
||||
specificationsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_specifications&trim_id=${trimId}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((spec) => {
|
||||
const parentDiv = document.createElement("div");
|
||||
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
||||
spec.specifications.forEach((s) => {
|
||||
const specDiv = document.createElement("div");
|
||||
specDiv.innerHTML = `• ${s.s_name}: ${s.s_value} ${s.s_unit}`;
|
||||
parentDiv.appendChild(specDiv);
|
||||
});
|
||||
specificationsContent.appendChild(parentDiv);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadOptions(equipmentId) {
|
||||
optionsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_options&equipment_id=${equipmentId}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((parent) => {
|
||||
const parentDiv = document.createElement("div");
|
||||
parentDiv.innerHTML = `<strong>${parent.parent_name}</strong>`;
|
||||
parent.options.forEach((option) => {
|
||||
const optDiv = document.createElement("div");
|
||||
optDiv.innerHTML = `• ${option.option_name}`;
|
||||
parentDiv.appendChild(optDiv);
|
||||
});
|
||||
optionsContent.appendChild(parentDiv);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
scanVinBtn.addEventListener("click", () => {
|
||||
resultDisplay.textContent = "";
|
||||
startScanner();
|
||||
});
|
||||
|
||||
fallbackButton.addEventListener("click", () => {
|
||||
captureAndOCR();
|
||||
})
|
||||
|
||||
serieSelect.addEventListener("change", () => {
|
||||
const serie_id = serieSelect.value;
|
||||
const model_id = modelSelect.value;
|
||||
if (serie_id && model_id) loadTrims(serie_id, model_id);
|
||||
})
|
||||
|
||||
trimSelect.addEventListener("change", () => {
|
||||
const trimId = trimSelect.value
|
||||
showSpecificationButton.disabled = !trimId
|
||||
showEquipmentButton.disabled = !trimId
|
||||
if (trimId) loadSpecifications(trimId)
|
||||
loadEquipment(trimId)
|
||||
})
|
||||
|
||||
equipmentSelect.addEventListener("change", () => {
|
||||
const equipmentId = equipmentSelect.value
|
||||
if (equipmentId) loadOptions(equipmentId)
|
||||
})
|
||||
|
||||
closeButton.addEventListener("click", closeModal);
|
||||
makeSelect.addEventListener("change", (e) => {
|
||||
loadModels(e.target.value, modelSelect.value);
|
||||
})
|
||||
modelSelect.addEventListener("change", (e) => {
|
||||
loadSeries(e.target.value, yearSelect.value);
|
||||
})
|
||||
decodeVinBtn.addEventListener("click", decodeVin);
|
||||
})
|
||||
|
||||
|
||||
function showLoading() {
|
||||
Swal.fire({
|
||||
title: "{% trans 'Please Wait' %}",
|
||||
text: "{% trans 'Loading' %}...",
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading();
|
||||
},
|
||||
});
|
||||
}
|
||||
codeReader.reset();
|
||||
}
|
||||
|
||||
function resetDropdown(dropdown, placeholder) {
|
||||
dropdown.innerHTML = `<option value="">${placeholder}</option>`;
|
||||
}
|
||||
function hideLoading() {
|
||||
Swal.close();
|
||||
}
|
||||
|
||||
async function loadModels(makeId) {
|
||||
resetDropdown(modelSelect, '{% trans "Select" %}');
|
||||
const response = await fetch(`${ajaxUrl}?action=get_models&make_id=${makeId}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((model) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = model.id_car_model;
|
||||
option.textContent = document.documentElement.lang === "en" ? model.name : model.arabic_name;
|
||||
modelSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSeries(modelId, year) {
|
||||
resetDropdown(serieSelect, '{% trans "Select" %}');
|
||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
||||
specificationsContent.innerHTML = "";
|
||||
|
||||
const response = await fetch(`${ajaxUrl}?action=get_series&model_id=${modelId}&year=${year}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log(data)
|
||||
data.forEach((serie) => {
|
||||
|
||||
const option = document.createElement("option");
|
||||
option.value = serie.id_car_serie;
|
||||
option.textContent = document.documentElement.lang === "en" ? serie.name : serie.name;
|
||||
generationContainer.innerHTML = serie.generation_name
|
||||
serieSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadTrims(serie_id, model_id) {
|
||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
||||
specificationsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((trim) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = trim.id_car_trim;
|
||||
option.textContent = document.documentElement.lang === "en" ? trim.name : trim.name;
|
||||
trimSelect.appendChild(option);
|
||||
});
|
||||
showSpecificationButton.disabled = !this.value;
|
||||
}
|
||||
|
||||
async function loadEquipment(trimId){
|
||||
resetDropdown(equipmentSelect, '{% trans "Select" %}');
|
||||
optionsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_equipments&trim_id=${trimId}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-CSRFToken': csrfToken
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((equipment) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = equipment.id_car_equipment;
|
||||
option.textContent = equipment.name;
|
||||
equipmentSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSpecifications(trimId) {
|
||||
specificationsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_specifications&trim_id=${trimId}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((spec) => {
|
||||
const parentDiv = document.createElement("div");
|
||||
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
||||
spec.specifications.forEach((s) => {
|
||||
const specDiv = document.createElement("div");
|
||||
specDiv.innerHTML = `• ${s.s_name}: ${s.s_value} ${s.s_unit}`;
|
||||
parentDiv.appendChild(specDiv);
|
||||
});
|
||||
specificationsContent.appendChild(parentDiv);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadOptions(equipmentId) {
|
||||
optionsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_options&equipment_id=${equipmentId}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((parent) => {
|
||||
const parentDiv = document.createElement("div");
|
||||
parentDiv.innerHTML = `<strong>${parent.parent_name}</strong>`;
|
||||
parent.options.forEach((option) => {
|
||||
const optDiv = document.createElement("div");
|
||||
optDiv.innerHTML = `• ${option.option_name}`;
|
||||
parentDiv.appendChild(optDiv);
|
||||
});
|
||||
optionsContent.appendChild(parentDiv);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
scanVinBtn.addEventListener("click", () => {
|
||||
resultDisplay.textContent = "";
|
||||
startScanner();
|
||||
});
|
||||
|
||||
fallbackButton.addEventListener("click", () => {
|
||||
captureAndOCR();
|
||||
})
|
||||
|
||||
serieSelect.addEventListener("change", () => {
|
||||
const serie_id = serieSelect.value;
|
||||
const model_id = modelSelect.value;
|
||||
if (serie_id && model_id) loadTrims(serie_id, model_id);
|
||||
})
|
||||
|
||||
trimSelect.addEventListener("change", () => {
|
||||
const trimId = trimSelect.value
|
||||
showSpecificationButton.disabled = !trimId
|
||||
showEquipmentButton.disabled = !trimId
|
||||
if (trimId) loadSpecifications(trimId)
|
||||
loadEquipment(trimId)
|
||||
})
|
||||
|
||||
equipmentSelect.addEventListener("change", () => {
|
||||
const equipmentId = equipmentSelect.value
|
||||
if (equipmentId) loadOptions(equipmentId)
|
||||
})
|
||||
|
||||
closeButton.addEventListener("click", closeModal);
|
||||
makeSelect.addEventListener("change", (e) => {
|
||||
loadModels(e.target.value, modelSelect.value);
|
||||
})
|
||||
modelSelect.addEventListener("change", (e) => {
|
||||
loadSeries(e.target.value, yearSelect.value);
|
||||
})
|
||||
decodeVinBtn.addEventListener("click", decodeVin);
|
||||
})
|
||||
|
||||
|
||||
function showLoading() {
|
||||
Swal.fire({
|
||||
title: "{% trans 'Please Wait' %}",
|
||||
text: "{% trans 'Loading' %}...",
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
Swal.close();
|
||||
}
|
||||
|
||||
function notify(tag, msg) {
|
||||
Swal.fire({
|
||||
icon: tag,
|
||||
titleText: msg,
|
||||
});
|
||||
}
|
||||
function notify(tag, msg) {
|
||||
Swal.fire({
|
||||
icon: tag,
|
||||
titleText: msg,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -2,15 +2,15 @@
|
||||
{% load i18n static custom_filters %}
|
||||
{% block content %}
|
||||
<style>
|
||||
#video {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.modal-dialog {
|
||||
max-width: 95%;
|
||||
}
|
||||
#video {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.modal-dialog {
|
||||
max-width: 95%;
|
||||
}
|
||||
</style>
|
||||
{% include 'partials/form_errors.html' %}
|
||||
<!-- JavaScript Section -->
|
||||
@ -298,331 +298,331 @@
|
||||
<!-- CAR FORM -->
|
||||
</div>
|
||||
<script>
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let cookie of cookies) {
|
||||
cookie = cookie.trim();
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let cookie of cookies) {
|
||||
cookie = cookie.trim();
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const csrfToken = getCookie("token");
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const csrfToken = getCookie("token");
|
||||
|
||||
const vinInput = document.getElementById("{{ form.vin.id_for_label }}");
|
||||
const decodeVinBtn = document.getElementById("decodeVinBtn");
|
||||
const makeSelect = document.getElementById("{{ form.id_car_make.id_for_label }}");
|
||||
const modelSelect = document.getElementById("{{ form.id_car_model.id_for_label }}");
|
||||
const yearSelect = document.getElementById("{{ form.year.id_for_label }}");
|
||||
const serieSelect = document.getElementById("{{ form.id_car_serie.id_for_label }}");
|
||||
const trimSelect = document.getElementById("{{ form.id_car_trim.id_for_label }}");
|
||||
const equipmentSelect = document.getElementById("equipment_id")
|
||||
const showSpecificationButton = document.getElementById("specification-btn");
|
||||
const showEquipmentButton = document.getElementById("options-btn")
|
||||
const specificationsContent = document.getElementById("specificationsContent");
|
||||
const optionsContent = document.getElementById("optionsContent")
|
||||
const generationContainer = document.getElementById("generation-div")
|
||||
const vinInput = document.getElementById("{{ form.vin.id_for_label }}");
|
||||
const decodeVinBtn = document.getElementById("decodeVinBtn");
|
||||
const makeSelect = document.getElementById("{{ form.id_car_make.id_for_label }}");
|
||||
const modelSelect = document.getElementById("{{ form.id_car_model.id_for_label }}");
|
||||
const yearSelect = document.getElementById("{{ form.year.id_for_label }}");
|
||||
const serieSelect = document.getElementById("{{ form.id_car_serie.id_for_label }}");
|
||||
const trimSelect = document.getElementById("{{ form.id_car_trim.id_for_label }}");
|
||||
const equipmentSelect = document.getElementById("equipment_id")
|
||||
const showSpecificationButton = document.getElementById("specification-btn");
|
||||
const showEquipmentButton = document.getElementById("options-btn")
|
||||
const specificationsContent = document.getElementById("specificationsContent");
|
||||
const optionsContent = document.getElementById("optionsContent")
|
||||
const generationContainer = document.getElementById("generation-div")
|
||||
|
||||
const ajaxUrl = "{% url 'ajax_handler' %}";
|
||||
const ajaxUrl = "{% url 'ajax_handler' %}";
|
||||
|
||||
const closeButton = document.querySelector(".btn-close");
|
||||
const scanVinBtn = document.getElementById("scan-vin-btn");
|
||||
const videoElement = document.getElementById("video");
|
||||
const resultDisplay = document.getElementById("result");
|
||||
const fallbackButton = document.getElementById("ocr-fallback-btn");
|
||||
let codeReader;
|
||||
codeReader = new ZXing.BrowserMultiFormatReader();
|
||||
let currentStream = null;
|
||||
const closeButton = document.querySelector(".btn-close");
|
||||
const scanVinBtn = document.getElementById("scan-vin-btn");
|
||||
const videoElement = document.getElementById("video");
|
||||
const resultDisplay = document.getElementById("result");
|
||||
const fallbackButton = document.getElementById("ocr-fallback-btn");
|
||||
let codeReader;
|
||||
codeReader = new ZXing.BrowserMultiFormatReader();
|
||||
let currentStream = null;
|
||||
|
||||
function closeModal() {
|
||||
stopScanner();
|
||||
try {
|
||||
const scannerModal = document.getElementById("scannerModal");
|
||||
if (scannerModal) {
|
||||
document.activeElement.blur();
|
||||
scannerModal.setAttribute("inert", "true");
|
||||
const modalInstance = bootstrap.Modal.getInstance(scannerModal);
|
||||
if (modalInstance) modalInstance.hide();
|
||||
if (scanVinBtn) scanVinBtn.focus();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error closing scanner modal:", err);
|
||||
}
|
||||
}
|
||||
function closeModal() {
|
||||
stopScanner();
|
||||
try {
|
||||
const scannerModal = document.getElementById("scannerModal");
|
||||
if (scannerModal) {
|
||||
document.activeElement.blur();
|
||||
scannerModal.setAttribute("inert", "true");
|
||||
const modalInstance = bootstrap.Modal.getInstance(scannerModal);
|
||||
if (modalInstance) modalInstance.hide();
|
||||
if (scanVinBtn) scanVinBtn.focus();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error closing scanner modal:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function decodeVin() {
|
||||
const vinNumber = vinInput.value.trim();
|
||||
if (vinNumber.length !== 17) {
|
||||
Swal.fire("error", "{% trans 'Please enter a valid VIN.' %}");
|
||||
async function decodeVin() {
|
||||
const vinNumber = vinInput.value.trim();
|
||||
if (vinNumber.length !== 17) {
|
||||
Swal.fire("error", "{% trans 'Please enter a valid VIN.' %}");
|
||||
/*alert("{% trans 'Please enter a valid VIN.' %}");*/
|
||||
return;
|
||||
}
|
||||
showLoading();
|
||||
try {
|
||||
const response = await fetch(`${ajaxUrl}?action=decode_vin&vin_no=${vinNumber}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
hideLoading();
|
||||
await updateFields(data.data);
|
||||
} else {
|
||||
hideLoading();
|
||||
Swal.fire("{% trans 'error' %}", data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error decoding VIN:", error);
|
||||
hideLoading();
|
||||
Swal.fire("error", "{% trans 'An error occurred while decoding the VIN.' %}");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
showLoading();
|
||||
try {
|
||||
const response = await fetch(`${ajaxUrl}?action=decode_vin&vin_no=${vinNumber}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
hideLoading();
|
||||
await updateFields(data.data);
|
||||
} else {
|
||||
hideLoading();
|
||||
Swal.fire("{% trans 'error' %}", data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error decoding VIN:", error);
|
||||
hideLoading();
|
||||
Swal.fire("error", "{% trans 'An error occurred while decoding the VIN.' %}");
|
||||
}
|
||||
}
|
||||
|
||||
async function updateFields(vinData) {
|
||||
console.log(vinData);
|
||||
if (vinData.make_id) {
|
||||
makeSelect.value = vinData.make_id;
|
||||
document.getElementById("make-check").innerHTML = "✓";
|
||||
await loadModels(vinData.make_id);
|
||||
}
|
||||
if (vinData.model_id) {
|
||||
modelSelect.value = vinData.model_id;
|
||||
document.getElementById("model-check").innerHTML = "✓";
|
||||
await loadSeries(vinData.model_id, vinData.year);
|
||||
}
|
||||
if (vinData.year) {
|
||||
yearSelect.value = vinData.year;
|
||||
document.getElementById("year-check").innerHTML = "✓";
|
||||
}
|
||||
}
|
||||
async function updateFields(vinData) {
|
||||
console.log(vinData);
|
||||
if (vinData.make_id) {
|
||||
makeSelect.value = vinData.make_id;
|
||||
document.getElementById("make-check").innerHTML = "✓";
|
||||
await loadModels(vinData.make_id);
|
||||
}
|
||||
if (vinData.model_id) {
|
||||
modelSelect.value = vinData.model_id;
|
||||
document.getElementById("model-check").innerHTML = "✓";
|
||||
await loadSeries(vinData.model_id, vinData.year);
|
||||
}
|
||||
if (vinData.year) {
|
||||
yearSelect.value = vinData.year;
|
||||
document.getElementById("year-check").innerHTML = "✓";
|
||||
}
|
||||
}
|
||||
|
||||
// Start the scanner
|
||||
async function startScanner() {
|
||||
codeReader
|
||||
.decodeFromVideoDevice(null, videoElement, async (result, err) => {
|
||||
let res = await result;
|
||||
if (result) {
|
||||
vinInput.value = result.text;
|
||||
closeModal();
|
||||
await decodeVin();
|
||||
async function startScanner() {
|
||||
codeReader
|
||||
.decodeFromVideoDevice(null, videoElement, async (result, err) => {
|
||||
let res = await result;
|
||||
if (result) {
|
||||
vinInput.value = result.text;
|
||||
closeModal();
|
||||
await decodeVin();
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
function captureAndOCR() {
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
canvas.width = videoElement.videoWidth;
|
||||
canvas.height = videoElement.videoHeight;
|
||||
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
||||
Tesseract.recognize(canvas.toDataURL("image/png"), "eng")
|
||||
.then(({ data: { text } }) => {
|
||||
const vin = text.match(/[A-HJ-NPR-Z0-9]{17}/);
|
||||
if (vin) vinInput.value = vin[0];
|
||||
closeModal();
|
||||
decodeVin();
|
||||
})
|
||||
.catch((err) => console.error("OCR Error:", err));
|
||||
}
|
||||
function captureAndOCR() {
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
canvas.width = videoElement.videoWidth;
|
||||
canvas.height = videoElement.videoHeight;
|
||||
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
||||
Tesseract.recognize(canvas.toDataURL("image/png"), "eng")
|
||||
.then(({ data: { text } }) => {
|
||||
const vin = text.match(/[A-HJ-NPR-Z0-9]{17}/);
|
||||
if (vin) vinInput.value = vin[0];
|
||||
closeModal();
|
||||
decodeVin();
|
||||
})
|
||||
.catch((err) => console.error("OCR Error:", err));
|
||||
}
|
||||
|
||||
function stopScanner() {
|
||||
if (currentStream) {
|
||||
currentStream.getTracks().forEach((track) => track.stop());
|
||||
currentStream = null;
|
||||
function stopScanner() {
|
||||
if (currentStream) {
|
||||
currentStream.getTracks().forEach((track) => track.stop());
|
||||
currentStream = null;
|
||||
}
|
||||
codeReader.reset();
|
||||
}
|
||||
|
||||
function resetDropdown(dropdown, placeholder) {
|
||||
dropdown.innerHTML = `<option value="">${placeholder}</option>`;
|
||||
}
|
||||
|
||||
async function loadModels(makeId) {
|
||||
resetDropdown(modelSelect, '{% trans "Select" %}');
|
||||
const response = await fetch(`${ajaxUrl}?action=get_models&make_id=${makeId}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((model) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = model.id_car_model;
|
||||
option.textContent = document.documentElement.lang === "en" ? model.name : model.arabic_name;
|
||||
modelSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSeries(modelId, year) {
|
||||
resetDropdown(serieSelect, '{% trans "Select" %}');
|
||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
||||
specificationsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_series&model_id=${modelId}&year=${year}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((serie) => {
|
||||
|
||||
const option = document.createElement("option");
|
||||
option.value = serie.id_car_serie;
|
||||
option.textContent = document.documentElement.lang === "en" ? serie.name : serie.name;
|
||||
generationContainer.innerHTML = serie.generation_name
|
||||
serieSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadTrims(serie_id, model_id) {
|
||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
||||
specificationsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((trim) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = trim.id_car_trim;
|
||||
option.textContent = document.documentElement.lang === "en" ? trim.name : trim.name;
|
||||
trimSelect.appendChild(option);
|
||||
});
|
||||
showSpecificationButton.disabled = !this.value;
|
||||
}
|
||||
|
||||
async function loadEquipment(trimId){
|
||||
resetDropdown(equipmentSelect, '{% trans "Select" %}');
|
||||
optionsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_equipments&trim_id=${trimId}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-CSRFToken': csrfToken
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((equipment) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = equipment.id_car_equipment;
|
||||
option.textContent = equipment.name;
|
||||
equipmentSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSpecifications(trimId) {
|
||||
specificationsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_specifications&trim_id=${trimId}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((spec) => {
|
||||
const parentDiv = document.createElement("div");
|
||||
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
||||
spec.specifications.forEach((s) => {
|
||||
const specDiv = document.createElement("div");
|
||||
specDiv.innerHTML = `• ${s.s_name}: ${s.s_value} ${s.s_unit}`;
|
||||
parentDiv.appendChild(specDiv);
|
||||
});
|
||||
specificationsContent.appendChild(parentDiv);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadOptions(equipmentId) {
|
||||
optionsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_options&equipment_id=${equipmentId}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((parent) => {
|
||||
const parentDiv = document.createElement("div");
|
||||
parentDiv.innerHTML = `<strong>${parent.parent_name}</strong>`;
|
||||
parent.options.forEach((option) => {
|
||||
const optDiv = document.createElement("div");
|
||||
optDiv.innerHTML = `• ${option.option_name}`;
|
||||
parentDiv.appendChild(optDiv);
|
||||
});
|
||||
optionsContent.appendChild(parentDiv);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
scanVinBtn.addEventListener("click", () => {
|
||||
resultDisplay.textContent = "";
|
||||
startScanner();
|
||||
});
|
||||
|
||||
fallbackButton.addEventListener("click", () => {
|
||||
captureAndOCR();
|
||||
})
|
||||
|
||||
serieSelect.addEventListener("change", () => {
|
||||
const serie_id = serieSelect.value;
|
||||
const model_id = modelSelect.value;
|
||||
if (serie_id && model_id) loadTrims(serie_id, model_id);
|
||||
})
|
||||
|
||||
trimSelect.addEventListener("change", () => {
|
||||
const trimId = trimSelect.value
|
||||
showSpecificationButton.disabled = !trimId
|
||||
showEquipmentButton.disabled = !trimId
|
||||
if (trimId) loadSpecifications(trimId)
|
||||
})
|
||||
|
||||
equipmentSelect.addEventListener("change", () => {
|
||||
const equipmentId = equipmentSelect.value
|
||||
if (equipmentId) loadOptions(equipmentId)
|
||||
})
|
||||
|
||||
closeButton.addEventListener("click", closeModal);
|
||||
makeSelect.addEventListener("change", (e) => {
|
||||
loadModels(e.target.value, modelSelect.value);
|
||||
})
|
||||
modelSelect.addEventListener("change", (e) => {
|
||||
loadSeries(e.target.value, yearSelect.value);
|
||||
})
|
||||
decodeVinBtn.addEventListener("click", decodeVin);
|
||||
})
|
||||
|
||||
|
||||
function showLoading() {
|
||||
Swal.fire({
|
||||
title: "{% trans 'Please Wait' %}",
|
||||
text: "{% trans 'Loading' %}...",
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading();
|
||||
},
|
||||
});
|
||||
}
|
||||
codeReader.reset();
|
||||
}
|
||||
|
||||
function resetDropdown(dropdown, placeholder) {
|
||||
dropdown.innerHTML = `<option value="">${placeholder}</option>`;
|
||||
}
|
||||
function hideLoading() {
|
||||
Swal.close();
|
||||
}
|
||||
|
||||
async function loadModels(makeId) {
|
||||
resetDropdown(modelSelect, '{% trans "Select" %}');
|
||||
const response = await fetch(`${ajaxUrl}?action=get_models&make_id=${makeId}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((model) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = model.id_car_model;
|
||||
option.textContent = document.documentElement.lang === "en" ? model.name : model.arabic_name;
|
||||
modelSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSeries(modelId, year) {
|
||||
resetDropdown(serieSelect, '{% trans "Select" %}');
|
||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
||||
specificationsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_series&model_id=${modelId}&year=${year}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((serie) => {
|
||||
|
||||
const option = document.createElement("option");
|
||||
option.value = serie.id_car_serie;
|
||||
option.textContent = document.documentElement.lang === "en" ? serie.name : serie.name;
|
||||
generationContainer.innerHTML = serie.generation_name
|
||||
serieSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadTrims(serie_id, model_id) {
|
||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
||||
specificationsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((trim) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = trim.id_car_trim;
|
||||
option.textContent = document.documentElement.lang === "en" ? trim.name : trim.name;
|
||||
trimSelect.appendChild(option);
|
||||
});
|
||||
showSpecificationButton.disabled = !this.value;
|
||||
}
|
||||
|
||||
async function loadEquipment(trimId){
|
||||
resetDropdown(equipmentSelect, '{% trans "Select" %}');
|
||||
optionsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_equipments&trim_id=${trimId}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-CSRFToken': csrfToken
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((equipment) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = equipment.id_car_equipment;
|
||||
option.textContent = equipment.name;
|
||||
equipmentSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSpecifications(trimId) {
|
||||
specificationsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_specifications&trim_id=${trimId}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((spec) => {
|
||||
const parentDiv = document.createElement("div");
|
||||
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
||||
spec.specifications.forEach((s) => {
|
||||
const specDiv = document.createElement("div");
|
||||
specDiv.innerHTML = `• ${s.s_name}: ${s.s_value} ${s.s_unit}`;
|
||||
parentDiv.appendChild(specDiv);
|
||||
});
|
||||
specificationsContent.appendChild(parentDiv);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadOptions(equipmentId) {
|
||||
optionsContent.innerHTML = "";
|
||||
const response = await fetch(`${ajaxUrl}?action=get_options&equipment_id=${equipmentId}`, {
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((parent) => {
|
||||
const parentDiv = document.createElement("div");
|
||||
parentDiv.innerHTML = `<strong>${parent.parent_name}</strong>`;
|
||||
parent.options.forEach((option) => {
|
||||
const optDiv = document.createElement("div");
|
||||
optDiv.innerHTML = `• ${option.option_name}`;
|
||||
parentDiv.appendChild(optDiv);
|
||||
});
|
||||
optionsContent.appendChild(parentDiv);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
scanVinBtn.addEventListener("click", () => {
|
||||
resultDisplay.textContent = "";
|
||||
startScanner();
|
||||
});
|
||||
|
||||
fallbackButton.addEventListener("click", () => {
|
||||
captureAndOCR();
|
||||
})
|
||||
|
||||
serieSelect.addEventListener("change", () => {
|
||||
const serie_id = serieSelect.value;
|
||||
const model_id = modelSelect.value;
|
||||
if (serie_id && model_id) loadTrims(serie_id, model_id);
|
||||
})
|
||||
|
||||
trimSelect.addEventListener("change", () => {
|
||||
const trimId = trimSelect.value
|
||||
showSpecificationButton.disabled = !trimId
|
||||
showEquipmentButton.disabled = !trimId
|
||||
if (trimId) loadSpecifications(trimId)
|
||||
})
|
||||
|
||||
equipmentSelect.addEventListener("change", () => {
|
||||
const equipmentId = equipmentSelect.value
|
||||
if (equipmentId) loadOptions(equipmentId)
|
||||
})
|
||||
|
||||
closeButton.addEventListener("click", closeModal);
|
||||
makeSelect.addEventListener("change", (e) => {
|
||||
loadModels(e.target.value, modelSelect.value);
|
||||
})
|
||||
modelSelect.addEventListener("change", (e) => {
|
||||
loadSeries(e.target.value, yearSelect.value);
|
||||
})
|
||||
decodeVinBtn.addEventListener("click", decodeVin);
|
||||
})
|
||||
|
||||
|
||||
function showLoading() {
|
||||
Swal.fire({
|
||||
title: "{% trans 'Please Wait' %}",
|
||||
text: "{% trans 'Loading' %}...",
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
Swal.close();
|
||||
}
|
||||
|
||||
function notify(tag, msg) {
|
||||
Swal.fire({
|
||||
icon: tag,
|
||||
titleText: msg,
|
||||
});
|
||||
}
|
||||
function notify(tag, msg) {
|
||||
Swal.fire({
|
||||
icon: tag,
|
||||
titleText: msg,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@ -133,9 +133,9 @@
|
||||
<span class="badge badge-phoenix fs-10 badge-phoenix-primary"><span class="badge-label">{{ _("Sold") }}</span><span class="ms-1" data-feather="package" style="height:13px;width:13px;"></span></span>
|
||||
{% elif car.status == "hold" %}
|
||||
<span class="badge badge-phoenix fs-10 badge-phoenix-warning"><span class="badge-label">{{ _("Hold") }}</span><span class="ms-1"
|
||||
data-feather="alert-octagon"
|
||||
style="height:13px;
|
||||
width:13px"></span></span>
|
||||
data-feather="alert-octagon"
|
||||
style="height:13px;
|
||||
width:13px"></span></span>
|
||||
{% elif car.status == "reserved" %}
|
||||
<span class="badge badge-phoenix fs-10 badge-phoenix-danger"><span class="badge-label">{{ _("Reserved") }}</span><span class="ms-1" data-feather="info" style="height:13px;width:13px;"></span></span>
|
||||
{% elif car.status == "damaged" %}
|
||||
|
||||
@ -3,23 +3,23 @@
|
||||
{%block title%} {%trans 'Stocks'%} {%endblock%}
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
.htmx-indicator{
|
||||
opacity:0;
|
||||
transition: opacity 500ms ease-in;
|
||||
}
|
||||
.htmx-request .htmx-indicator{
|
||||
opacity:1;
|
||||
}
|
||||
.htmx-request.htmx-indicator{
|
||||
opacity:1;
|
||||
}
|
||||
.on-before-request{
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.transition {
|
||||
transition: all ease-in 1s ;
|
||||
}
|
||||
.htmx-indicator{
|
||||
opacity:0;
|
||||
transition: opacity 500ms ease-in;
|
||||
}
|
||||
.htmx-request .htmx-indicator{
|
||||
opacity:1;
|
||||
}
|
||||
.htmx-request.htmx-indicator{
|
||||
opacity:1;
|
||||
}
|
||||
.on-before-request{
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.transition {
|
||||
transition: all ease-in 1s ;
|
||||
}
|
||||
</style>
|
||||
{% endblock customCSS %}
|
||||
{% block content %}
|
||||
@ -145,16 +145,16 @@
|
||||
hx-on::after-request="filter_after_request()">{{ _("Search") }}</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<form hx-boost='true' action="{% url 'bulk_update_car_price' %}" method="post"
|
||||
hx-include=".car-checkbox"
|
||||
class="update-price-form d-flex flex-row align-items-center ms-auto w-25 d-none" style="float:right;">
|
||||
{% csrf_token %}
|
||||
<div class="form-floating me-2">
|
||||
<input class="form-control" type="number" placeholder='{{ _("Search") }}' name="price" aria-label="Price" id="price">
|
||||
<label for="price">{{ _("Price") }}</label>
|
||||
</div>
|
||||
<button class="btn btn-outline-primary" type="submit">{{ _("Search") }}</button>
|
||||
</form>
|
||||
<form hx-boost='true' action="{% url 'bulk_update_car_price' %}" method="post"
|
||||
hx-include=".car-checkbox"
|
||||
class="update-price-form d-flex flex-row align-items-center ms-auto w-25 d-none" style="float:right;">
|
||||
{% csrf_token %}
|
||||
<div class="form-floating me-2">
|
||||
<input class="form-control" type="number" placeholder='{{ _("Search") }}' name="price" aria-label="Price" id="price">
|
||||
<label for="price">{{ _("Price") }}</label>
|
||||
</div>
|
||||
<button class="btn btn-outline-primary" type="submit">{{ _("Search") }}</button>
|
||||
</form>
|
||||
<div class="table-responsive scrollbar transition">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9">
|
||||
<div class="d-flex"
|
||||
@ -189,24 +189,24 @@
|
||||
</thead>
|
||||
<tbody class="list" id="project-list-table-body">
|
||||
{% for car in cars %}
|
||||
<tr class="position-static">
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input car-checkbox" type="checkbox" name="car" value="{{ car.pk }}" id="car-{{car.pk}}">
|
||||
</div>
|
||||
</td>
|
||||
<tr class="position-static">
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input car-checkbox" type="checkbox" name="car" value="{{ car.pk }}" id="car-{{car.pk}}">
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap ps-1">
|
||||
<a class="fw-bold" href="{% url 'car_detail' car.slug %}">{{ car.vin }}</a>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
{% if car.id_car_make %}
|
||||
<p class="text-body mb-0">{{ car.id_car_make.get_local_name|default:car.id_car_make.name }}</p>
|
||||
{% endif %}
|
||||
{% if car.id_car_make %}
|
||||
<p class="text-body mb-0">{{ car.id_car_make.get_local_name|default:car.id_car_make.name }}</p>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
{% if car.id_car_model %}
|
||||
<p class="text-body mb-0">{{ car.id_car_model.get_local_name|default:car.id_car_model.name }}</p>
|
||||
{% endif %}
|
||||
{% if car.id_car_model %}
|
||||
<p class="text-body mb-0">{{ car.id_car_model.get_local_name|default:car.id_car_model.name }}</p>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<p class="text-body mb-0">{{ car.year }}</p>
|
||||
@ -244,9 +244,9 @@
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if not car.ready %}
|
||||
<span class="text-danger"> {{ _("NO") }} </span>
|
||||
<span class="text-danger"> {{ _("NO") }} </span>
|
||||
{%else%}
|
||||
<span class="text-success"> {{ _("YES") }} </span>
|
||||
<span class="text-success"> {{ _("YES") }} </span>
|
||||
{%endif%}
|
||||
</td>
|
||||
<td class="align-middle text-end white-space-nowrap pe-0 action">
|
||||
@ -281,69 +281,69 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block customJS %}
|
||||
<script>
|
||||
links = document.querySelectorAll('.nav-link')
|
||||
links.forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
{% endblock %}
|
||||
{% block customJS %}
|
||||
<script>
|
||||
links = document.querySelectorAll('.nav-link')
|
||||
links.forEach(link => {
|
||||
link.classList.remove('active')
|
||||
link.addEventListener('click', () => {
|
||||
links.forEach(link => {
|
||||
link.classList.remove('active')
|
||||
})
|
||||
link.classList.add('active')
|
||||
})
|
||||
})
|
||||
link.classList.add('active')
|
||||
})
|
||||
})
|
||||
function on_before_request() {
|
||||
document.querySelector('.table').classList.toggle('on-before-request')
|
||||
document.querySelector('.model-select').classList.add('on-after-request')
|
||||
}
|
||||
function on_after_request() {
|
||||
document.querySelector('.table').classList.remove('on-before-request')
|
||||
document.querySelector('.model-select').classList.remove('on-after-request')
|
||||
}
|
||||
function toggle_filter(){
|
||||
document.querySelector('.filter').classList.toggle('d-none')
|
||||
document.querySelector('.filter-icon').classList.toggle("fa-caret-down");
|
||||
document.querySelector('.filter-icon').classList.toggle("fa-caret-up");
|
||||
}
|
||||
function filter_before_request(){
|
||||
document.querySelector('.model-select').setAttribute('disabled', true)
|
||||
document.querySelector('.year').setAttribute('disabled', true)
|
||||
document.querySelector('.car_status').setAttribute('disabled', true)
|
||||
}
|
||||
function filter_after_request(){
|
||||
document.querySelector('.model-select').removeAttribute('disabled')
|
||||
document.querySelector('.year').removeAttribute('disabled')
|
||||
document.querySelector('.car_status').removeAttribute('disabled')
|
||||
}
|
||||
function on_before_request() {
|
||||
document.querySelector('.table').classList.toggle('on-before-request')
|
||||
document.querySelector('.model-select').classList.add('on-after-request')
|
||||
}
|
||||
function on_after_request() {
|
||||
document.querySelector('.table').classList.remove('on-before-request')
|
||||
document.querySelector('.model-select').classList.remove('on-after-request')
|
||||
}
|
||||
function toggle_filter(){
|
||||
document.querySelector('.filter').classList.toggle('d-none')
|
||||
document.querySelector('.filter-icon').classList.toggle("fa-caret-down");
|
||||
document.querySelector('.filter-icon').classList.toggle("fa-caret-up");
|
||||
}
|
||||
function filter_before_request(){
|
||||
document.querySelector('.model-select').setAttribute('disabled', true)
|
||||
document.querySelector('.year').setAttribute('disabled', true)
|
||||
document.querySelector('.car_status').setAttribute('disabled', true)
|
||||
}
|
||||
function filter_after_request(){
|
||||
document.querySelector('.model-select').removeAttribute('disabled')
|
||||
document.querySelector('.year').removeAttribute('disabled')
|
||||
document.querySelector('.car_status').removeAttribute('disabled')
|
||||
}
|
||||
|
||||
document.getElementById('select-all').addEventListener('change', function() {
|
||||
const checkboxes = document.querySelectorAll('#project-list-table-body input[type="checkbox"]');
|
||||
if (this.checked) {
|
||||
checkboxes.forEach(checkbox => checkbox.checked = true);
|
||||
} else {
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
}
|
||||
updateFormVisibility();
|
||||
});
|
||||
document.getElementById('select-all').addEventListener('change', function() {
|
||||
const checkboxes = document.querySelectorAll('#project-list-table-body input[type="checkbox"]');
|
||||
if (this.checked) {
|
||||
checkboxes.forEach(checkbox => checkbox.checked = true);
|
||||
} else {
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
}
|
||||
updateFormVisibility();
|
||||
});
|
||||
|
||||
const cbox = document.querySelectorAll('.car-checkbox');
|
||||
cbox.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
updateFormVisibility();
|
||||
});
|
||||
});
|
||||
const cbox = document.querySelectorAll('.car-checkbox');
|
||||
cbox.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
updateFormVisibility();
|
||||
});
|
||||
});
|
||||
|
||||
function updateFormVisibility() {
|
||||
const form = document.querySelector('.update-price-form');
|
||||
const checkedCount = document.querySelectorAll('.car-checkbox:checked').length;
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
if (checkedCount > 0) {
|
||||
form.classList.remove('d-none');
|
||||
submitButton.textContent = `Update Cost Price (${checkedCount})`;
|
||||
} else {
|
||||
form.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock customJS %}
|
||||
function updateFormVisibility() {
|
||||
const form = document.querySelector('.update-price-form');
|
||||
const checkedCount = document.querySelectorAll('.car-checkbox:checked').length;
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
if (checkedCount > 0) {
|
||||
form.classList.remove('d-none');
|
||||
submitButton.textContent = `Update Cost Price (${checkedCount})`;
|
||||
} else {
|
||||
form.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock customJS %}
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
#065535
|
||||
#000000
|
||||
#133337
|
||||
#ffc0cb</a>
|
||||
#ffc0cb</a>
|
||||
<br>
|
||||
<span class="glyphicon glyphicon-star"></span>707
|
||||
</div>
|
||||
|
||||
@ -184,141 +184,141 @@
|
||||
}
|
||||
</script>
|
||||
</main>
|
||||
{% else %}
|
||||
<div class="button-row">
|
||||
<button id="download-pdf" class="btn btn-phoenix-primary">
|
||||
<i class="fas fa-download"></i> {% trans 'Download transfer' %}
|
||||
</button>
|
||||
<button id="accept"
|
||||
class="btn btn-phoenix-success"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#acceptModal">
|
||||
<i class="fas fa-check-circle"></i> {% trans 'Accept transfer' %}
|
||||
</button>
|
||||
<button id="reject"
|
||||
class="btn btn-phoenix-danger"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#rejectModal">
|
||||
<i class="fas fa-times-circle"></i> {% trans 'Reject transfer' %}
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="button-row">
|
||||
<button id="download-pdf" class="btn btn-phoenix-primary">
|
||||
<i class="fas fa-download"></i> {% trans 'Download transfer' %}
|
||||
</button>
|
||||
<button id="accept"
|
||||
class="btn btn-phoenix-success"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#acceptModal">
|
||||
<i class="fas fa-check-circle"></i> {% trans 'Accept transfer' %}
|
||||
</button>
|
||||
<button id="reject"
|
||||
class="btn btn-phoenix-danger"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#rejectModal">
|
||||
<i class="fas fa-times-circle"></i> {% trans 'Reject transfer' %}
|
||||
</button>
|
||||
</div>
|
||||
<!-- Accept Modal -->
|
||||
<div class="modal fade"
|
||||
id="acceptModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="acceptModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="acceptModalLabel">{% trans 'Accept transfer' %}</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">{% trans 'Are you sure you want to accept this transfer?' %}</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-phoenix-secondary" data-bs-dismiss="modal">{% trans 'Cancel' %}</button>
|
||||
<a class="btn btn-phoenix-success"
|
||||
href="{% url 'transfer_accept_reject' transfer.car.pk transfer.pk %}?status=accepted">Confirm</a>
|
||||
</div>
|
||||
<div class="modal fade"
|
||||
id="acceptModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="acceptModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="acceptModalLabel">{% trans 'Accept transfer' %}</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">{% trans 'Are you sure you want to accept this transfer?' %}</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-phoenix-secondary" data-bs-dismiss="modal">{% trans 'Cancel' %}</button>
|
||||
<a class="btn btn-phoenix-success"
|
||||
href="{% url 'transfer_accept_reject' transfer.car.pk transfer.pk %}?status=accepted">Confirm</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Reject Modal -->
|
||||
<div class="modal fade"
|
||||
id="rejectModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="rejectModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="rejectModalLabel">{% trans 'Reject transfer' %}</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">{% trans 'Are you sure you want to reject this transfer?' %}</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-phoenix-secondary" data-bs-dismiss="modal">{% trans 'Cancel' %}</button>
|
||||
<a class="btn btn-phoenix-success"
|
||||
href="{% url 'transfer_accept_reject' transfer.car.pk transfer.pk %}?status=rejected">Confirm</a>
|
||||
</div>
|
||||
<div class="modal fade"
|
||||
id="rejectModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="rejectModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="rejectModalLabel">{% trans 'Reject transfer' %}</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">{% trans 'Are you sure you want to reject this transfer?' %}</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-phoenix-secondary" data-bs-dismiss="modal">{% trans 'Cancel' %}</button>
|
||||
<a class="btn btn-phoenix-success"
|
||||
href="{% url 'transfer_accept_reject' transfer.car.pk transfer.pk %}?status=rejected">Confirm</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transfer-row" id="transfer-content">
|
||||
</div>
|
||||
<div class="transfer-row" id="transfer-content">
|
||||
<!-- Header -->
|
||||
<div class="transfer-header">
|
||||
<svg width="101"
|
||||
height="24"
|
||||
viewBox="0 0 101 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<div class="transfer-header">
|
||||
<svg width="101"
|
||||
height="24"
|
||||
viewBox="0 0 101 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- SVG Paths -->
|
||||
</svg>
|
||||
<h1 style="margin-top: 10px;">
|
||||
<b>{% trans "Transfer" %}</b>
|
||||
</h1>
|
||||
<p>{% trans "Thank you for choosing us. We appreciate your business" %}</p>
|
||||
</div>
|
||||
<!-- Details -->
|
||||
<div class="transfer-details">
|
||||
<p>
|
||||
<strong>{% trans "Date" %} :</strong> {{ transfer.created_at }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% trans "From" %} :</strong> {{ transfer.from_dealer }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% trans "To" %} :</strong> {{ transfer.to_dealer }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Items Table -->
|
||||
<div class="transfer-table">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Item" %}</th>
|
||||
<th class="text-center">{% trans "Quantity" %}</th>
|
||||
<th class="text-center">{% trans "Unit Price" %}</th>
|
||||
<th class="text-center">{% trans "Total" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ transfer.car }}</td>
|
||||
<td class="text-center">{{ transfer.quantity }}</td>
|
||||
<td class="text-center">{{ transfer.car.finances.selling_price }}</td>
|
||||
<td class="text-center">{{ transfer.total_price }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Total -->
|
||||
<div class="transfer-total">
|
||||
<p>
|
||||
<strong>{% trans "Total Amount" %}:</strong> <span class="highlight">${{ transfer.total_price }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- Footer Note -->
|
||||
<div class="footer-note">
|
||||
<p>
|
||||
{% trans "If you have any questions, feel free to contact us at" %} <a href="mailto:support@example.com">support@tenhal.com</a>.
|
||||
</p>
|
||||
<p>{% trans "Thank you for your business" %}</p>
|
||||
</div>
|
||||
</svg>
|
||||
<h1 style="margin-top: 10px;">
|
||||
<b>{% trans "Transfer" %}</b>
|
||||
</h1>
|
||||
<p>{% trans "Thank you for choosing us. We appreciate your business" %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- Details -->
|
||||
<div class="transfer-details">
|
||||
<p>
|
||||
<strong>{% trans "Date" %} :</strong> {{ transfer.created_at }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% trans "From" %} :</strong> {{ transfer.from_dealer }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% trans "To" %} :</strong> {{ transfer.to_dealer }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Items Table -->
|
||||
<div class="transfer-table">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Item" %}</th>
|
||||
<th class="text-center">{% trans "Quantity" %}</th>
|
||||
<th class="text-center">{% trans "Unit Price" %}</th>
|
||||
<th class="text-center">{% trans "Total" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ transfer.car }}</td>
|
||||
<td class="text-center">{{ transfer.quantity }}</td>
|
||||
<td class="text-center">{{ transfer.car.finances.selling_price }}</td>
|
||||
<td class="text-center">{{ transfer.total_price }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Total -->
|
||||
<div class="transfer-total">
|
||||
<p>
|
||||
<strong>{% trans "Total Amount" %}:</strong> <span class="highlight">${{ transfer.total_price }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- Footer Note -->
|
||||
<div class="footer-note">
|
||||
<p>
|
||||
{% trans "If you have any questions, feel free to contact us at" %} <a href="mailto:support@example.com">support@tenhal.com</a>.
|
||||
</p>
|
||||
<p>{% trans "Thank you for your business" %}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- Bootstrap JS (Optional) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- jsPDF Library -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
|
||||
<script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
|
||||
<script>
|
||||
document.getElementById('download-pdf').addEventListener('click', function () {
|
||||
const element = document.getElementById('transfer-content');
|
||||
|
||||
@ -349,6 +349,6 @@
|
||||
// Handle the reject action here
|
||||
$('#rejectModal').modal('hide');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -13,9 +13,9 @@
|
||||
</div>
|
||||
{% include "partials/search_box.html" %}
|
||||
{% if page_obj.object_list %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class="table align-items-center table-flush">
|
||||
<thead>
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class="table align-items-center table-flush">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Item Number" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Name" %}</th>
|
||||
@ -24,50 +24,50 @@
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="list">
|
||||
<tbody class="list">
|
||||
|
||||
{% for expense in expenses %}
|
||||
{% for expense in expenses %}
|
||||
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ expense.item_number }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ expense.name }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ expense.uom }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<a href="{% url 'item_expense_update' expense.pk %}"
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ expense.item_number }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ expense.name }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ expense.uom }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<a href="{% url 'item_expense_update' expense.pk %}"
|
||||
class="btn btn-sm btn-phoenix-success">
|
||||
{% trans "Update" %}
|
||||
</a>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-start">
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-start">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -6,9 +6,9 @@
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Service'%}
|
||||
{% trans 'Update Service'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Service'%}
|
||||
{% trans 'Add New Service'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -7,68 +7,68 @@
|
||||
<div class="row mt-4">
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h3 class="">{% trans "Services" %}</h3>
|
||||
<a href="{% url 'item_service_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Service" %}</a>
|
||||
<h3 class="">{% trans "Services" %}</h3>
|
||||
<a href="{% url 'item_service_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Service" %}</a>
|
||||
</div>
|
||||
{% include "partials/search_box.html" %}
|
||||
{% if page_obj.object_list %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class="table align-items-center table-flush">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Item Number" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Name" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Unit of Measure" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Taxable" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="list">
|
||||
|
||||
{% for service in services %}
|
||||
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ service.pk }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ service.get_local_name|default:service.name }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ service.get_uom_display }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ service.taxable|yesno }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ service.item.co }}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-start">
|
||||
<a href="{% url 'item_service_update' service.pk %}"
|
||||
class="btn btn-sm btn-phoenix-success">
|
||||
{% trans "Update" %}
|
||||
</a>
|
||||
</td>
|
||||
{% include "partials/search_box.html" %}
|
||||
{% if page_obj.object_list %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class="table align-items-center table-flush">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Item Number" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Name" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Unit of Measure" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Taxable" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="list">
|
||||
|
||||
{% for service in services %}
|
||||
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ service.pk }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ service.get_local_name|default:service.name }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ service.get_uom_display }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ service.taxable|yesno }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ service.item.co }}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-start">
|
||||
<a href="{% url 'item_service_update' service.pk %}"
|
||||
class="btn btn-sm btn-phoenix-success">
|
||||
{% trans "Update" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -4,9 +4,9 @@
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Bank Account'%}
|
||||
{% trans 'Update Bank Account'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Bank Account'%}
|
||||
{% trans 'Add New Bank Account'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -7,59 +7,59 @@
|
||||
<div class="row mt-4">
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h3 class="">{% trans "Bank Accounts" %}</h3>
|
||||
<h3 class="">{% trans "Bank Accounts" %}</h3>
|
||||
<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>
|
||||
{% include "partials/search_box.html" %}
|
||||
{% if page_obj.object_list %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class="table align-items-center table-flush">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<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>
|
||||
|
||||
</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">
|
||||
{{ bank.account_number }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ bank.account_type|capfirst }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<a href="{% url 'bank_account_update' bank.pk %}"
|
||||
class="btn btn-sm btn-phoenix-success">
|
||||
{% trans "Update" %}
|
||||
</a>
|
||||
</td>
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class="table align-items-center table-flush">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<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>
|
||||
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</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">
|
||||
{{ bank.account_number }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ bank.account_type|capfirst }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<a href="{% url 'bank_account_update' bank.pk %}"
|
||||
class="btn btn-sm btn-phoenix-success">
|
||||
{% trans "Update" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}
|
||||
{% trans 'Bills' %}
|
||||
{% trans 'Bills' %}
|
||||
{% endblock %}
|
||||
{% block bills %}
|
||||
<a class="nav-link active fw-bold">
|
||||
{% trans 'Bills'|capfirst %}
|
||||
<span class="visually-hidden">(current)</span>
|
||||
</a>
|
||||
<a class="nav-link active fw-bold">
|
||||
{% trans 'Bills'|capfirst %}
|
||||
<span class="visually-hidden">(current)</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h3 class="">{% trans "Bills" %}</h3>
|
||||
<a href="{% url 'bill-create' entity.slug %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'New Bill' %}</a>
|
||||
<h3 class="">{% trans "Bills" %}</h3>
|
||||
<a href="{% url 'bill-create' entity.slug %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'New Bill' %}</a>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
@ -52,51 +52,51 @@
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="list">
|
||||
<tbody class="list">
|
||||
{% for bill in bills %}
|
||||
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ bill.bill_number }}
|
||||
{{ bill.bill_number }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if bill.is_draft %}
|
||||
{% if bill.is_draft %}
|
||||
<span class="badge badge-phoenix badge-phoenix-warning">
|
||||
{% elif bill.is_review %}
|
||||
{% elif bill.is_review %}
|
||||
<span class="badge badge-phoenix badge-phoenix-info">
|
||||
{% elif bill.is_approved %}
|
||||
<span class="badge badge-phoenix badge-phoenix-success">
|
||||
{% elif bill.is_paid %}
|
||||
{% elif bill.is_approved %}
|
||||
<span class="badge badge-phoenix badge-phoenix-success">
|
||||
{% elif bill.is_canceled %}
|
||||
{% elif bill.is_paid %}
|
||||
<span class="badge badge-phoenix badge-phoenix-success">
|
||||
{% elif bill.is_canceled %}
|
||||
<span class="badge badge-phoenix badge-phoenix-danger">
|
||||
{% endif %}
|
||||
{{ bill.bill_status }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{bill.vendor.vendor_name}}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a href="{% url 'bill-detail' entity_slug=entity.slug bill_pk=bill.pk %}" class="dropdown-item text-success-dark">{% trans 'View' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
{{ bill.bill_status }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{bill.vendor.vendor_name}}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a href="{% url 'bill-detail' entity_slug=entity.slug bill_pk=bill.pk %}" class="dropdown-item text-success-dark">{% trans 'View' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted">
|
||||
{% trans 'No bill found.' %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted">
|
||||
{% trans 'No bill found.' %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-3"><span class="d-none d-sm-inline-block" data-list-info="data-list-info">{{ page_obj.start_index }} {{ _("to") }} {{ page_obj.end_index }}<span class="text-body-tertiary"> {{ _("Items of")}} </span>{{ page_obj.paginator.count }}</span>
|
||||
<div class="d-flex justify-content-between mt-3"><span class="d-none d-sm-inline-block" data-list-info="data-list-info">{{ page_obj.start_index }} {{ _("to") }} {{ page_obj.end_index }}<span class="text-body-tertiary"> {{ _("Items of")}} </span>{{ page_obj.paginator.count }}</span>
|
||||
<div class="d-flex">
|
||||
|
||||
{% if is_paginated %}
|
||||
|
||||
@ -59,97 +59,97 @@
|
||||
|
||||
<div class="row mt-4">
|
||||
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class="table align-items-center table-flush">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('JE Number') }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Date') }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Debit') }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Credit') }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Description') }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% for tx in account.transactionmodel_set.all %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ tx.journal_entry.je_number }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ tx.journal_entry.timestamp }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if tx.tx_type == 'debit' %}
|
||||
<i class="fa-solid fa-circle-up"></i> {{ tx.amount }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if tx.tx_type == 'credit' %}
|
||||
<i class="fa-solid fa-circle-down"></i> {{ tx.amount }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if tx.description %}
|
||||
{{ tx.description }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-start">
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a class="dropdown-item" href="{% url 'payment_details' tx.journal_entry.pk %}">{% trans 'view'|capfirst %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('JE Number') }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Date') }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Debit') }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Credit') }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Description') }}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{{ _('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% for tx in account.transactionmodel_set.all %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ tx.journal_entry.je_number }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ tx.journal_entry.timestamp }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if tx.tx_type == 'debit' %}
|
||||
<i class="fa-solid fa-circle-up"></i> {{ tx.amount }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if tx.tx_type == 'credit' %}
|
||||
<i class="fa-solid fa-circle-down"></i> {{ tx.amount }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if tx.description %}
|
||||
{{ tx.description }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-start">
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a class="dropdown-item" href="{% url 'payment_details' tx.journal_entry.pk %}">{% trans 'view'|capfirst %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<span class="fw-bold fs-8">{{ _("Total") }}</span>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<span class="fw-bold fs-8 text-success">{{ total_debits }} <span class="icon-saudi_riyal"></span></span>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<span class="fw-bold fs-8 text-danger">{{ total_credits }} <span class="icon-saudi_riyal"></span></span>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<span class="fw-bold fs-8">{{ _("Total") }}</span>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<span class="fw-bold fs-8 text-success">{{ total_debits }} <span class="icon-saudi_riyal"></span></span>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<span class="fw-bold fs-8 text-danger">{{ total_credits }} <span class="icon-saudi_riyal"></span></span>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-start">
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-start">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-3 d-flex">
|
||||
<a class="btn btn-sm btn-phoenix-primary me-1" href="{% url 'account_update' account.pk %}">
|
||||
<div class="mt-3 d-flex">
|
||||
<a class="btn btn-sm btn-phoenix-primary me-1" href="{% url 'account_update' account.pk %}">
|
||||
<!-- <i class="bi bi-pencil-square"></i> -->
|
||||
<i class="fa-solid fa-pen-to-square"></i> {{ _('Edit') }}
|
||||
</a>
|
||||
<a class="btn btn-sm btn-phoenix-danger me-1" data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||||
<i class="fa-solid fa-pen-to-square"></i> {{ _('Edit') }}
|
||||
</a>
|
||||
<a class="btn btn-sm btn-phoenix-danger me-1" data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||||
<!-- <i class="bi bi-trash-fill"></i> -->
|
||||
<i class="fa-solid fa-trash"></i> {{ _('Delete') }}
|
||||
</a>
|
||||
<a class="btn btn-sm btn-phoenix-secondary" href="{% url 'account_list' %}">
|
||||
<i class="fa-solid fa-trash"></i> {{ _('Delete') }}
|
||||
</a>
|
||||
<a class="btn btn-sm btn-phoenix-secondary" href="{% url 'account_list' %}">
|
||||
<!-- <i class="bi bi-arrow-left-square-fill"></i> -->
|
||||
<i class="fa-regular fa-circle-left"></i> {% trans 'Back to List' %}
|
||||
</a>
|
||||
</div>
|
||||
<i class="fa-regular fa-circle-left"></i> {% trans 'Back to List' %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--test-->
|
||||
{% endblock %}
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Account'%}
|
||||
{% trans 'Update Account'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Account'%}
|
||||
{% trans 'Add New Account'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -10,8 +10,8 @@
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h3 class=""><i class="fa-solid fa-book"></i> {% trans "Accounts" %}</h3>
|
||||
<a href="{% url 'account_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'New Account' %}</a>
|
||||
<h3 class=""><i class="fa-solid fa-book"></i> {% trans "Accounts" %}</h3>
|
||||
<a href="{% url 'account_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'New Account' %}</a>
|
||||
</div>
|
||||
|
||||
<!-- Account Type Tabs -->
|
||||
@ -130,15 +130,15 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block customerJS %}
|
||||
<script>
|
||||
<script>
|
||||
// Handle delete modal for all tables
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var deleteModal = document.getElementById('deleteModal');
|
||||
deleteModal.addEventListener('show.bs.modal', function(event) {
|
||||
var button = event.relatedTarget;
|
||||
var accountId = button.closest('tr').getAttribute('data-account-id');
|
||||
document.getElementById('deleteAccountBtn').href = `/accounts/delete/${accountId}/`;
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var deleteModal = document.getElementById('deleteModal');
|
||||
deleteModal.addEventListener('show.bs.modal', function(event) {
|
||||
var button = event.relatedTarget;
|
||||
var accountId = button.closest('tr').getAttribute('data-account-id');
|
||||
document.getElementById('deleteAccountBtn').href = `/accounts/delete/${accountId}/`;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,69 +1,69 @@
|
||||
{% load i18n %}
|
||||
{% if accounts %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class="table align-items-center table-flush">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account Name" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Code" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Balance Type" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Active" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% for account in accounts %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static" data-account-id="{{ account.uuid }}">
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ account.name }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ account.code }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if account.balance_type == 'debit' %}
|
||||
<div class="badge badge-phoenix fs-10 badge-phoenix-success"><span class="fw-bold"><i class="fa-solid fa-circle-up"></i> {{ _("Debit") }}</span></div>
|
||||
{% else %}
|
||||
<div class="badge badge-phoenix fs-10 badge-phoenix-danger"><span class="fw-bold"><i class="fa-solid fa-circle-down"></i> {{ _("Credit") }}</span></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if account.active %}
|
||||
<span class="fw-bold text-success fas fa-check-circle"></span>
|
||||
{% else %}
|
||||
<span class="fw-bold text-danger far fa-times-circle"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-start">
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a href="{% url 'account_detail' account.uuid %}" class="dropdown-item text-success-dark">
|
||||
{% trans "View" %}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class="table align-items-center table-flush">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account Name" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Code" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Balance Type" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Active" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% for account in accounts %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static" data-account-id="{{ account.uuid }}">
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ account.name }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ account.code }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if account.balance_type == 'debit' %}
|
||||
<div class="badge badge-phoenix fs-10 badge-phoenix-success"><span class="fw-bold"><i class="fa-solid fa-circle-up"></i> {{ _("Debit") }}</span></div>
|
||||
{% else %}
|
||||
<div class="badge badge-phoenix fs-10 badge-phoenix-danger"><span class="fw-bold"><i class="fa-solid fa-circle-down"></i> {{ _("Credit") }}</span></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if account.active %}
|
||||
<span class="fw-bold text-success fas fa-check-circle"></span>
|
||||
{% else %}
|
||||
<span class="fw-bold text-danger far fa-times-circle"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-start">
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a href="{% url 'account_detail' account.uuid %}" class="dropdown-item text-success-dark">
|
||||
{% trans "View" %}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted">{% trans "No Accounts Found" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert ">
|
||||
{% trans "No accounts found in this category." %}
|
||||
</div>
|
||||
<div class="alert ">
|
||||
{% trans "No accounts found in this category." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -35,88 +35,88 @@
|
||||
<div class="row mt-4">
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h3 class="">{% trans "Journal Entries" %}</h3>
|
||||
<a href="{% url 'journalentry_create' ledger.pk %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Journal Entry" %}</a>
|
||||
<h3 class="">{% trans "Journal Entries" %}</h3>
|
||||
<a href="{% url 'journalentry_create' ledger.pk %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans "Add Journal Entry" %}</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="table-responsive px-1 scrollbar">
|
||||
<table class="table align-items-center table-flush">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Document Number" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Timestamp" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Activity" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Description" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Posted" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Locked" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Transaction Count" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% for je in journal_entries %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">{{ je.je_number }}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ je.timestamp }}</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if je.get_activity_display %}
|
||||
{{ je.get_activity_display }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ je.description }}</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if je.is_posted %}
|
||||
<i class="fa-solid fa-square-check text-success"></i>
|
||||
{% else %}
|
||||
<i class="fa-solid fa-circle-xmark text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if je.is_locked %}
|
||||
<i class="fa-solid fa-lock text-success"></i>
|
||||
{% else %}
|
||||
<i class="fa-solid fa-unlock text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ je.txs_count }}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-start">
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button
|
||||
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
data-boundary="window"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
data-bs-reference="parent">
|
||||
<span class="fas fa-ellipsis-h fs-10"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a class="dropdown-item" href="{% url 'journalentry_transactions' je.pk %}">{% trans "View" %}</a>
|
||||
<a class="dropdown-item" href="{% url 'journalentry_txs' je.entity_slug je.ledger_id je.pk %}">{% trans "Transactions" %}</a>
|
||||
{% if je.can_delete %}
|
||||
<a class="dropdown-item" href="{% url 'journalentry_delete' je.pk %}">{% trans "Delete" %}</a>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="table-responsive px-1 scrollbar">
|
||||
<table class="table align-items-center table-flush">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Document Number" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Timestamp" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Activity" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Description" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Posted" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Locked" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Transaction Count" %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% for je in journal_entries %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">{{ je.je_number }}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ je.timestamp }}</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if je.get_activity_display %}
|
||||
{{ je.get_activity_display }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ je.description }}</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if je.is_posted %}
|
||||
<i class="fa-solid fa-square-check text-success"></i>
|
||||
{% else %}
|
||||
<i class="fa-solid fa-circle-xmark text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if je.is_locked %}
|
||||
<i class="fa-solid fa-lock text-success"></i>
|
||||
{% else %}
|
||||
<i class="fa-solid fa-unlock text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ je.txs_count }}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-start">
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button
|
||||
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
data-boundary="window"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
data-bs-reference="parent">
|
||||
<span class="fas fa-ellipsis-h fs-10"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a class="dropdown-item" href="{% url 'journalentry_transactions' je.pk %}">{% trans "View" %}</a>
|
||||
<a class="dropdown-item" href="{% url 'journalentry_txs' je.entity_slug je.ledger_id je.pk %}">{% trans "Transactions" %}</a>
|
||||
{% if je.can_delete %}
|
||||
<a class="dropdown-item" href="{% url 'journalentry_delete' je.pk %}">{% trans "Delete" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="8" class="text-center">{% trans "No Bank Accounts Found" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="8" class="text-center">{% trans "No Bank Accounts Found" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!--test-->
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
@ -9,8 +9,8 @@
|
||||
<div class="row mt-4">
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h3 class="">{% trans "Ledger" %}</h3>
|
||||
<a href="{% url 'ledger_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'Create Ledger' %}</a>
|
||||
<h3 class="">{% trans "Ledger" %}</h3>
|
||||
<a href="{% url 'ledger_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'Create Ledger' %}</a>
|
||||
</div>
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class="table align-items-center table-flush">
|
||||
@ -31,7 +31,7 @@
|
||||
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if ledger.invoicemodel %}
|
||||
{% if ledger.invoicemodel %}
|
||||
<a href="{% url 'invoice_detail' ledger.invoicemodel.pk %}">{{ ledger.get_wrapped_model_instance }}</a>
|
||||
{% elif ledger.billmodel %}
|
||||
<a href="{% url 'bill_detail' ledger.billmodel.pk %}">{{ ledger.get_wrapped_model_instance }}</a>
|
||||
@ -40,7 +40,7 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<a class="btn btn-sm btn-phoenix-primary"
|
||||
<a class="btn btn-sm btn-phoenix-primary"
|
||||
href="{% url 'journalentry_list' ledger.pk %}">
|
||||
<i class="fa-solid fa-right-left"></i>
|
||||
<span>
|
||||
@ -50,17 +50,17 @@
|
||||
</a>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{{ ledger.created |date }}
|
||||
{{ ledger.created |date }}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if ledger.is_posted %}
|
||||
{% if ledger.is_posted %}
|
||||
<i class="fa-solid fa-square-check text-success"></i>
|
||||
{% else %}
|
||||
<i class="fa-solid fa-circle-xmark text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if ledger.is_locked %}
|
||||
{% if ledger.is_locked %}
|
||||
<i class="fa-solid fa-lock text-success"></i>
|
||||
{% else %}
|
||||
<i class="fa-solid fa-unlock text-danger"></i>
|
||||
@ -121,12 +121,12 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -4,9 +4,9 @@
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Organization'%}
|
||||
{% trans 'Update Organization'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Organization'%}
|
||||
{% trans 'Add New Organization'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
@ -132,12 +132,12 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@ -2,134 +2,134 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>{{ title }}</h2>
|
||||
<div class="container mt-4">
|
||||
<h2>{{ title }}</h2>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
<hr>
|
||||
<h4>Items</h4>
|
||||
{{ formset.management_form }}
|
||||
<hr>
|
||||
<h4>Items</h4>
|
||||
{{ formset.management_form }}
|
||||
|
||||
<div id="formset-container">
|
||||
{% for form in formset %}
|
||||
<div class="item-form row g-3 mb-3 align-items-end">
|
||||
{{ form.id }}
|
||||
<div class="col-md-2">
|
||||
{{ form.make.label_tag }}
|
||||
{{ form.make }}
|
||||
<div id="formset-container">
|
||||
{% for form in formset %}
|
||||
<div class="item-form row g-3 mb-3 align-items-end">
|
||||
{{ form.id }}
|
||||
<div class="col-md-2">
|
||||
{{ form.make.label_tag }}
|
||||
{{ form.make }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.model.label_tag }}
|
||||
{{ form.model }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.serie.label_tag }}
|
||||
{{ form.serie }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.trim.label_tag }}
|
||||
{{ form.trim }}
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
{{ form.year.label_tag }}
|
||||
{{ form.year }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.color.label_tag }}
|
||||
{{ form.color }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.expected_cost.label_tag }}
|
||||
{{ form.expected_cost }}
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="button" class="btn btn-phoenix-danger btn-sm remove-row">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.model.label_tag }}
|
||||
{{ form.model }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.serie.label_tag }}
|
||||
{{ form.serie }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.trim.label_tag }}
|
||||
{{ form.trim }}
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
{{ form.year.label_tag }}
|
||||
{{ form.year }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.color.label_tag }}
|
||||
{{ form.color }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.expected_cost.label_tag }}
|
||||
{{ form.expected_cost }}
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="button" class="btn btn-phoenix-danger btn-sm remove-row">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<button type="button" id="add-item" class="btn btn-phoenix-secondary btn-sm mb-3">+ Add Item</button>
|
||||
<button type="button" id="add-item" class="btn btn-phoenix-secondary btn-sm mb-3">+ Add Item</button>
|
||||
|
||||
<div class="mt-3">
|
||||
<button type="submit" class="btn btn-phoenix-primary">Save</button>
|
||||
<a href="{% url 'purchase_order_list' %}" class="btn btn-phoenix-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<button type="submit" class="btn btn-phoenix-primary">Save</button>
|
||||
<a href="{% url 'purchase_order_list' %}" class="btn btn-phoenix-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const container = document.getElementById('formset-container');
|
||||
const addBtn = document.getElementById('add-item');
|
||||
const totalForms = document.querySelector('#id_form-TOTAL_FORMS');
|
||||
const emptyForm = document.querySelector('.empty-form').cloneNode(true);
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const container = document.getElementById('formset-container');
|
||||
const addBtn = document.getElementById('add-item');
|
||||
const totalForms = document.querySelector('#id_form-TOTAL_FORMS');
|
||||
const emptyForm = document.querySelector('.empty-form').cloneNode(true);
|
||||
|
||||
function updateFormIndex(formRow, index) {
|
||||
formRow.querySelectorAll('input, select').forEach(input => {
|
||||
input.name = input.name.replace('__prefix__', index);
|
||||
input.id = input.id.replace('__prefix__', index);
|
||||
});
|
||||
}
|
||||
function updateFormIndex(formRow, index) {
|
||||
formRow.querySelectorAll('input, select').forEach(input => {
|
||||
input.name = input.name.replace('__prefix__', index);
|
||||
input.id = input.id.replace('__prefix__', index);
|
||||
});
|
||||
}
|
||||
|
||||
addBtn.addEventListener('click', function () {
|
||||
const currentCount = parseInt(totalForms.value);
|
||||
const newForm = emptyForm.cloneNode(true);
|
||||
updateFormIndex(newForm, currentCount);
|
||||
newForm.classList.remove('empty-form');
|
||||
addBtn.addEventListener('click', function () {
|
||||
const currentCount = parseInt(totalForms.value);
|
||||
const newForm = emptyForm.cloneNode(true);
|
||||
updateFormIndex(newForm, currentCount);
|
||||
newForm.classList.remove('empty-form');
|
||||
|
||||
// Attach remove handler
|
||||
const removeBtn = newForm.querySelector('.remove-row');
|
||||
removeBtn.addEventListener('click', function () {
|
||||
newForm.remove();
|
||||
totalForms.value = parseInt(totalForms.value) - 1;
|
||||
});
|
||||
const removeBtn = newForm.querySelector('.remove-row');
|
||||
removeBtn.addEventListener('click', function () {
|
||||
newForm.remove();
|
||||
totalForms.value = parseInt(totalForms.value) - 1;
|
||||
});
|
||||
|
||||
container.appendChild(newForm);
|
||||
totalForms.value = currentCount + 1;
|
||||
});
|
||||
container.appendChild(newForm);
|
||||
totalForms.value = currentCount + 1;
|
||||
});
|
||||
|
||||
document.querySelectorAll('.remove-row').forEach(btn => {
|
||||
btn.addEventListener('click', function () {
|
||||
const row = this.closest('.item-form');
|
||||
row.remove();
|
||||
totalForms.value = parseInt(totalForms.value) - 1;
|
||||
document.querySelectorAll('.remove-row').forEach(btn => {
|
||||
btn.addEventListener('click', function () {
|
||||
const row = this.closest('.item-form');
|
||||
row.remove();
|
||||
totalForms.value = parseInt(totalForms.value) - 1;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<!-- Empty form template -->
|
||||
<div class="empty-form d-none">
|
||||
<div class="item-form row g-3 mb-3 align-items-end">
|
||||
<div class="col-md-2">
|
||||
<select name="form-__prefix__-make" class="form-select make-select"></select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="form-__prefix__-model" class="form-select model-select"></select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="form-__prefix__-serie" class="form-select serie-select"></select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="form-__prefix__-trim" class="form-select trim-select"></select>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<input type="number" name="form-__prefix__-year" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="text" name="form-__prefix__-color" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="number" step="0.01" name="form-__prefix__-expected_cost" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="button" class="btn btn-phoenix-danger btn-sm remove-row">Remove</button>
|
||||
<div class="empty-form d-none">
|
||||
<div class="item-form row g-3 mb-3 align-items-end">
|
||||
<div class="col-md-2">
|
||||
<select name="form-__prefix__-make" class="form-select make-select"></select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="form-__prefix__-model" class="form-select model-select"></select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="form-__prefix__-serie" class="form-select serie-select"></select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="form-__prefix__-trim" class="form-select trim-select"></select>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<input type="number" name="form-__prefix__-year" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="text" name="form-__prefix__-color" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="number" step="0.01" name="form-__prefix__-expected_cost" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="button" class="btn btn-phoenix-danger btn-sm remove-row">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@ -26,7 +26,7 @@
|
||||
<p class="lead">{{ _("Your payment was successful")}}. {{ _("Your order is being processed")}}.</p>
|
||||
{% if invoice %}
|
||||
<a href="{% url 'invoice_preview_html' invoice.pk %}" class="btn btn-phoenix-primary mt-3"><i class="fas fa-eye"></i>
|
||||
{{ _("View Invoice")}}</a>
|
||||
{{ _("View Invoice")}}</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'home' %}" class="btn btn-phoenix-success mt-3">
|
||||
<i class="fas fa-home"></i> {{ _("Back to Home")}}
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
<i class="fa fa-save me-1"></i>{{ _("Save") }}
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% if object_list %}
|
||||
|
||||
|
||||
|
||||
{% block order_table %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
|
||||
@ -1,116 +1,116 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n crispy_forms_tags %}
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
<style>
|
||||
.color-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
width: 80px; /* Increased from 3rem for better visibility */
|
||||
height: 80px; /* Increased from 3rem for better visibility */
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
width: 80px; /* Increased from 3rem for better visibility */
|
||||
height: 80px; /* Increased from 3rem for better visibility */
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.color-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.color-option {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.color-radio {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.color-radio:checked + .color-display {
|
||||
border: 2px solid #0d6efd;
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||
border: 2px solid #0d6efd;
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
.color-radio:focus + .color-display {
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
.color-display {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
border-radius: 0.25rem;
|
||||
transition: all 0.2s ease;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
border-radius: 0.25rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.color-name {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 0.8rem;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Added for better layout of color options */
|
||||
.color-options-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
{{form.vendor.label}}
|
||||
{{form.vendor}}
|
||||
</div>
|
||||
<div class="col">
|
||||
{{form.year.label}}
|
||||
{{form.year}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
{{form.vendor.label}}
|
||||
{{form.vendor}}
|
||||
</div>
|
||||
<div class="col">
|
||||
{{form.year.label}}
|
||||
{{form.year}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="account">Account</label>
|
||||
<select class="form-control" name="account" id="account">
|
||||
{% for account in inventory_accounts %}
|
||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="row g-4 mt-4">
|
||||
<div class="col">
|
||||
<p class="fs-5 mb-2">{% trans 'Exterior Colors' %}</p>
|
||||
<div class="color-options-container">
|
||||
{% for color in form.fields.exterior.queryset %}
|
||||
<div class="form-group">
|
||||
<label for="account">Account</label>
|
||||
<select class="form-control" name="account" id="account">
|
||||
{% for account in inventory_accounts %}
|
||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="row g-4 mt-4">
|
||||
<div class="col">
|
||||
<p class="fs-5 mb-2">{% trans 'Exterior Colors' %}</p>
|
||||
<div class="color-options-container">
|
||||
{% for color in form.fields.exterior.queryset %}
|
||||
<div class="color-card">
|
||||
<label class="color-option">
|
||||
<input class="color-radio" type="radio" name="exterior" value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
||||
@ -119,26 +119,26 @@
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<p class="fs-5 mb-2">{% trans 'Interior Colors' %}</p>
|
||||
<div class="color-options-container">
|
||||
{% for color in form.fields.interior.queryset %}
|
||||
<div class="color-card">
|
||||
<label class="color-option">
|
||||
<input class="color-radio" type="radio" name="interior" value="{{ color.id }}" {% if color.id == form.instance.interior.id %}checked{% endif %}>
|
||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||
<span class="color-name">{{ color.get_local_name }}</span>
|
||||
</div>
|
||||
</label>
|
||||
<div class="col">
|
||||
<p class="fs-5 mb-2">{% trans 'Interior Colors' %}</p>
|
||||
<div class="color-options-container">
|
||||
{% for color in form.fields.interior.queryset %}
|
||||
<div class="color-card">
|
||||
<label class="color-option">
|
||||
<input class="color-radio" type="radio" name="interior" value="{{ color.id }}" {% if color.id == form.instance.interior.id %}checked{% endif %}>
|
||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||
<span class="color-name">{{ color.get_local_name }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mt-5">Add New Item To Inventory</button>
|
||||
</form>
|
||||
<button type="submit" class="btn btn-primary mt-5">Add New Item To Inventory</button>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
@ -2,18 +2,18 @@
|
||||
{% load django_ledger %}
|
||||
{% load widget_tweaks %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||
const modalEl = document.getElementById('POModal');
|
||||
if (!modalEl) {
|
||||
console.error('Modal element not found');
|
||||
return;
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||
const modalEl = document.getElementById('POModal');
|
||||
if (!modalEl) {
|
||||
console.error('Modal element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
document.getElementById('POModalTitle').textContent = title;
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
document.getElementById('POModalTitle').textContent = title;
|
||||
|
||||
document.getElementById('POModalBody').innerHTML = `
|
||||
document.getElementById('POModalBody').innerHTML = `
|
||||
<div class="d-flex justify-content-center gap-3 py-3">
|
||||
<a class="btn btn-phoenix-primary px-4" href="${actionUrl}">
|
||||
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
||||
@ -24,9 +24,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.show();
|
||||
};
|
||||
});
|
||||
modal.show();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
{% if not create_po %}
|
||||
@ -171,7 +171,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
{# Status Action Buttons #}
|
||||
{% if po_model.can_draft %}
|
||||
<button class="btn btn-phoenix-secondary"
|
||||
onclick="showPOModal('Draft PO', '{% url 'po-action-mark-as-draft' entity_slug po_model.pk %}', 'Mark As Draft')">
|
||||
onclick="showPOModal('Draft PO', '{% url 'po-action-mark-as-draft' entity_slug po_model.pk %}', 'Mark As Draft')">
|
||||
<i class="fas fa-file me-2"></i>{% trans 'Mark as Draft' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
@ -199,23 +199,23 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
{% endif %}
|
||||
|
||||
{# Danger Action Buttons #}
|
||||
{% if po_model.can_delete %}
|
||||
{% if po_model.can_delete %}
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="showPOModal('Cancel PO', '{% url 'po-delete' entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
||||
onclick="showPOModal('Cancel PO', '{% url 'po-delete' entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
||||
<i class="fas fa-ban me-2"></i>{% trans 'Delete' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.can_void %}
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="showPOModal('Void PO', '{% url 'po-action-mark-as-void' entity_slug po_model.pk %}', 'Mark As Void')">
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="showPOModal('Void PO', '{% url 'po-action-mark-as-void' entity_slug po_model.pk %}', 'Mark As Void')">
|
||||
<i class="fas fa-times-circle me-2"></i>{% trans 'Void' %}
|
||||
</button>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.can_cancel %}
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="showPOModal('Cancel PO', '{% url 'po-action-mark-as-canceled' entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
||||
onclick="showPOModal('Cancel PO', '{% url 'po-action-mark-as-canceled' entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
||||
<i class="fas fa-ban me-2"></i>{% trans 'Cancel' %}
|
||||
</button>
|
||||
|
||||
|
||||
@ -3,25 +3,25 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form action="{% url 'inventory_item_create' po_model.pk %}" method="post">
|
||||
{% csrf_token %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||
<div class="form-group">
|
||||
<label for="account">Account</label>
|
||||
<select class="form-control" name="account" id="account">
|
||||
{% for account in inventory_accounts %}
|
||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quantity">Quantity</label>
|
||||
<input type="number" class="form-control" id="quantity" name="quantity" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-phoenix-primary">Add New Item To Inventory</button>
|
||||
</form>
|
||||
<form action="{% url 'inventory_item_create' po_model.pk %}" method="post">
|
||||
{% csrf_token %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||
<div class="form-group">
|
||||
<label for="account">Account</label>
|
||||
<select class="form-control" name="account" id="account">
|
||||
{% for account in inventory_accounts %}
|
||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quantity">Quantity</label>
|
||||
<input type="number" class="form-control" id="quantity" name="quantity" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-phoenix-primary">Add New Item To Inventory</button>
|
||||
</form>
|
||||
|
||||
{% endblock content %}
|
||||
@ -3,21 +3,21 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid mt-4">
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label for="account">Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" required>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label for="account">Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account">Account</label>
|
||||
<select class="form-control" name="account" id="account">
|
||||
{% for account in inventory_accounts %}
|
||||
<option value="{{ account.pk }}">{{ account }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-phoenix-primary mt-2">Add New Item To Inventory</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account">Account</label>
|
||||
<select class="form-control" name="account" id="account">
|
||||
{% for account in inventory_accounts %}
|
||||
<option value="{{ account.pk }}">{{ account }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-phoenix-primary mt-2">Add New Item To Inventory</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@ -1,16 +1,16 @@
|
||||
<div class="form-group" id="form-{{name}}">
|
||||
<label for="{{name}}">{{name|capfirst}}</label>
|
||||
<select class="form-control"
|
||||
name="{{name}}" id="{{name}}"
|
||||
{% if name != "trim" %}
|
||||
hx-get="{% url 'inventory_items_filter' %}"
|
||||
hx-target="#form-{{target}}"
|
||||
hx-select="#form-{{target}}"
|
||||
hx-swap="outerHTML"
|
||||
{% endif %}
|
||||
>
|
||||
name="{{name}}" id="{{name}}"
|
||||
{% if name != "trim" %}
|
||||
hx-get="{% url 'inventory_items_filter' %}"
|
||||
hx-target="#form-{{target}}"
|
||||
hx-select="#form-{{target}}"
|
||||
hx-swap="outerHTML"
|
||||
{% endif %}
|
||||
>
|
||||
{% for item in data %}
|
||||
<option value="{{ item.pk }}">{{ item.name }}</option>
|
||||
<option value="{{ item.pk }}">{{ item.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
@ -6,14 +6,14 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>Confirm Deletion</h2>
|
||||
<p>Are you sure you want to delete the Purchase Order <strong>"{{ po.po_number }}"</strong>?</p>
|
||||
<div class="container mt-4">
|
||||
<h2>Confirm Deletion</h2>
|
||||
<p>Are you sure you want to delete the Purchase Order <strong>"{{ po.po_number }}"</strong>?</p>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-phoenix-danger">Yes, Delete</button>
|
||||
<a href="{% url 'purchase_order_detail' po.id %}" class="btn btn-phoenix-secondary">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-phoenix-danger">Yes, Delete</button>
|
||||
<a href="{% url 'purchase_order_detail' po.id %}" class="btn btn-phoenix-secondary">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -5,84 +5,84 @@
|
||||
{% load django_ledger %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row g-4">
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row g-4">
|
||||
<!-- Left Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<div class="d-flex flex-column gap-3">
|
||||
<div class="col-lg-4">
|
||||
<div class="d-flex flex-column gap-3">
|
||||
<!-- PO Card -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{% include 'purchase_orders/includes/card_po.html' with po_model=po_model entity_slug=entity_slug style='po-detail' %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{% include 'purchase_orders/includes/card_po.html' with po_model=po_model entity_slug=entity_slug style='po-detail' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PO List Button -->
|
||||
<a class="btn btn-phoenix-primary w-100 py-2"
|
||||
href="{% url 'purchase_order_list' %}">
|
||||
<i class="fas fa-list me-2"></i>{% trans 'PO List' %}
|
||||
</a>
|
||||
<a class="btn btn-phoenix-primary w-100 py-2"
|
||||
href="{% url 'purchase_order_list' %}">
|
||||
<i class="fas fa-list me-2"></i>{% trans 'PO List' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="col-lg-8">
|
||||
<div class="col-lg-8">
|
||||
<!-- Stats Cards -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row text-center">
|
||||
<div class="col-md-6 border-end">
|
||||
<div class="p-3">
|
||||
<h6 class="text-muted mb-2">{% trans 'PO Amount' %}</h6>
|
||||
<h3 class="fw-light mb-0">
|
||||
{% currency_symbol %}{{ po_model.po_amount | absolute | currency_format }}
|
||||
</h3>
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row text-center">
|
||||
<div class="col-md-6 border-end">
|
||||
<div class="p-3">
|
||||
<h6 class="text-muted mb-2">{% trans 'PO Amount' %}</h6>
|
||||
<h3 class="fw-light mb-0">
|
||||
{% currency_symbol %}{{ po_model.po_amount | absolute | currency_format }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="p-3">
|
||||
<h6 class="text-muted mb-2">{% trans 'Amount Received' %}</h6>
|
||||
<h3 class="fw-light mb-0 text-success">
|
||||
{% currency_symbol %}{{ po_model.po_amount_received | currency_format }}
|
||||
</h3>
|
||||
<div class="col-md-6">
|
||||
<div class="p-3">
|
||||
<h6 class="text-muted mb-2">{% trans 'Amount Received' %}</h6>
|
||||
<h3 class="fw-light mb-0 text-success">
|
||||
{% currency_symbol %}{{ po_model.po_amount_received | currency_format }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PO Details -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h3 class="h4 fw-light mb-4">{{ po_model.po_title }}</h3>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h3 class="h4 fw-light mb-4">{{ po_model.po_title }}</h3>
|
||||
|
||||
<!-- PO Items Table -->
|
||||
<div class="table-responsive">
|
||||
{% po_item_table1 po_items %}
|
||||
<div class="table-responsive">
|
||||
{% po_item_table1 po_items %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "purchase_orders/includes/mark_as.html" %}
|
||||
{% include "purchase_orders/includes/mark_as.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||
const modalEl = document.getElementById('POModal');
|
||||
if (!modalEl) {
|
||||
console.error('Modal element not found');
|
||||
return;
|
||||
}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||
const modalEl = document.getElementById('POModal');
|
||||
if (!modalEl) {
|
||||
console.error('Modal element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
document.getElementById('POModalTitle').textContent = title;
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
document.getElementById('POModalTitle').textContent = title;
|
||||
|
||||
document.getElementById('POModalBody').innerHTML = `
|
||||
document.getElementById('POModalBody').innerHTML = `
|
||||
<div class="d-flex justify-content-center gap-3">
|
||||
<a class="btn btn-phoenix-primary px-4" href="${actionUrl}">
|
||||
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
||||
@ -93,8 +93,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.show();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
modal.show();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
{% endblock customJS %}
|
||||
@ -6,90 +6,90 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>Purchase Order: {{ po.po_number }}</h2>
|
||||
<p><strong>Status:</strong>
|
||||
<span class="">
|
||||
{{ po.po_status }}
|
||||
</span>
|
||||
</p>
|
||||
<div class="container mt-4">
|
||||
<h2>Purchase Order: {{ po.po_number }}</h2>
|
||||
<p><strong>Status:</strong>
|
||||
<span class="">
|
||||
{{ po.po_status }}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4">Supplier</dt>
|
||||
<dd class="col-sm-8">{{ po.supplier.name }}</dd>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4">Supplier</dt>
|
||||
<dd class="col-sm-8">{{ po.supplier.name }}</dd>
|
||||
|
||||
<dt class="col-sm-4">Created At</dt>
|
||||
<dd class="col-sm-8">{{ po.created|date:"M d, Y H:i" }}</dd>
|
||||
<dt class="col-sm-4">Created At</dt>
|
||||
<dd class="col-sm-8">{{ po.created|date:"M d, Y H:i" }}</dd>
|
||||
|
||||
<dt class="col-sm-4">Total Amount</dt>
|
||||
<dd class="col-sm-8">${{ po.total_amount|floatformat:2 }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="mt-4">Ordered Items</h4>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item Number</th>
|
||||
<th>Item</th>
|
||||
<th>Unit of Measure</th>
|
||||
<th>Account</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in inventory_items %}
|
||||
<tr>
|
||||
<td>{{ item.item_number }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>${{ item.uom.name }}</td>
|
||||
<td>${{ item.inventory_account }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4" class="text-center">No items found.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="mt-3">
|
||||
|
||||
|
||||
<button class="btn btn-phoenix-primary" data-bs-toggle="modal" data-bs-target="#POModal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-receipt"></i> {% trans 'View Purchase Order' %}</span></button>
|
||||
<a href="{% url 'purchase_order_list' %}" class="btn btn-phoenix-secondary">Back to List</a>
|
||||
|
||||
<dt class="col-sm-4">Total Amount</dt>
|
||||
<dd class="col-sm-8">${{ po.total_amount|floatformat:2 }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="mt-4">Ordered Items</h4>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item Number</th>
|
||||
<th>Item</th>
|
||||
<th>Unit of Measure</th>
|
||||
<th>Account</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in inventory_items %}
|
||||
<tr>
|
||||
<td>{{ item.item_number }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>${{ item.uom.name }}</td>
|
||||
<td>${{ item.inventory_account }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4" class="text-center">No items found.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="mt-3">
|
||||
|
||||
|
||||
<button class="btn btn-phoenix-primary" data-bs-toggle="modal" data-bs-target="#POModal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-receipt"></i> {% trans 'View Purchase Order' %}</span></button>
|
||||
<a href="{% url 'purchase_order_list' %}" class="btn btn-phoenix-secondary">Back to List</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="POModal" data-bs-keyboard="true" tabindex="-1" aria-labelledby="POModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
|
||||
<h4 class="mb-0 me-2 text-primary"><i class="fas text-info ms-2"></i></h4>
|
||||
<button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close"><span class="fas fa-times"></span></button>
|
||||
<div class="modal fade" id="POModal" data-bs-keyboard="true" tabindex="-1" aria-labelledby="POModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
|
||||
<h4 class="mb-0 me-2 text-primary"><i class="fas text-info ms-2"></i></h4>
|
||||
<button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close"><span class="fas fa-times"></span></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<form action="{% url 'inventory_item_create' po.pk %}" method="post">
|
||||
{% csrf_token %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po.pk %}
|
||||
<div class="form-group">
|
||||
<label for="account">Account</label>
|
||||
<select class="form-control" name="account" id="account">
|
||||
{% for account in inventory_accounts %}
|
||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quantity">Quantity</label>
|
||||
<input type="number" class="form-control" id="quantity" name="quantity" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-phoenix-primary">Add New Item To Inventory</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<form action="{% url 'inventory_item_create' po.pk %}" method="post">
|
||||
{% csrf_token %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po.pk %}
|
||||
<div class="form-group">
|
||||
<label for="account">Account</label>
|
||||
<select class="form-control" name="account" id="account">
|
||||
{% for account in inventory_accounts %}
|
||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quantity">Quantity</label>
|
||||
<input type="number" class="form-control" id="quantity" name="quantity" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-phoenix-primary">Add New Item To Inventory</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
{% load crispy_forms_filters %}
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Vendor'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Vendor'%}
|
||||
{% endif %}
|
||||
{% if object %}
|
||||
{% trans 'Update Vendor'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Vendor'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
|
||||
@ -8,10 +8,10 @@
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<!-- Success Message -->
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-success">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-success">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<!-- Add New PO Button -->
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
@ -19,76 +19,76 @@
|
||||
{{ _("Purchase Orders") |capfirst }}
|
||||
</h2>
|
||||
<div>
|
||||
<a href="{% url 'purchase_order_create' %}"
|
||||
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create New PO") }}</a>
|
||||
<a href="{% url 'inventory_item_create' %}?for_po=1"
|
||||
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create Inventory Item for PO") }}</a>
|
||||
<a href="{% url 'purchase_order_create' %}"
|
||||
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create New PO") }}</a>
|
||||
<a href="{% url 'inventory_item_create' %}?for_po=1"
|
||||
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create Inventory Item for PO") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% include "partials/search_box.html" %}
|
||||
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class= "table align-items-center table-flush table-hover">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">PO Number</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:40%">Description</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Status</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Created At</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% if purchase_orders %}
|
||||
{% for po in purchase_orders %}
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class= "table align-items-center table-flush table-hover">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">PO Number</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:40%">Description</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Status</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Created At</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% if purchase_orders %}
|
||||
{% for po in purchase_orders %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">{{ po.po_number }}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ po.po_title }}</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<span class="">
|
||||
{% if po.po_status == 'draft' %}
|
||||
<span class="text-warning me-2" data-feather="file"></span>
|
||||
{% elif po.po_status == 'in_review' %}
|
||||
<span class="text-info me-2" data-feather="search"></span>
|
||||
{% elif po.po_status == 'paid' %}
|
||||
<span class="text-success me-2" data-feather="dollar-sign"></span>
|
||||
{% elif po.po_status == 'canceled' %}
|
||||
<span class="text-danger me-2" data-feather="x-circle"></span>
|
||||
{% elif po.po_status == 'fulfilled' %}
|
||||
<span class="text-success me-2" data-feather="check-circle"></span>
|
||||
{% elif po.po_status == 'approved' %}
|
||||
<span class="text-success me-2" data-feather="thumbs-up"></span>
|
||||
{% endif %}
|
||||
{{ po.po_status|capfirst }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ po.created|date:"M d, Y" }}</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a href="{% url 'purchase_order_detail' po.pk %}" class="dropdown-item text-success-dark">{% trans 'Detail' %}</a>
|
||||
<a href="{% url 'view_items_inventory' entity_slug=entity_slug po_pk=po.pk %}" class="dropdown-item text-success-dark">{% trans 'View Inventory Items' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ po.po_title }}</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<span class="">
|
||||
{% if po.po_status == 'draft' %}
|
||||
<span class="text-warning me-2" data-feather="file"></span>
|
||||
{% elif po.po_status == 'in_review' %}
|
||||
<span class="text-info me-2" data-feather="search"></span>
|
||||
{% elif po.po_status == 'paid' %}
|
||||
<span class="text-success me-2" data-feather="dollar-sign"></span>
|
||||
{% elif po.po_status == 'canceled' %}
|
||||
<span class="text-danger me-2" data-feather="x-circle"></span>
|
||||
{% elif po.po_status == 'fulfilled' %}
|
||||
<span class="text-success me-2" data-feather="check-circle"></span>
|
||||
{% elif po.po_status == 'approved' %}
|
||||
<span class="text-success me-2" data-feather="thumbs-up"></span>
|
||||
{% endif %}
|
||||
{{ po.po_status|capfirst }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ po.created|date:"M d, Y" }}</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a href="{% url 'purchase_order_detail' po.pk %}" class="dropdown-item text-success-dark">{% trans 'Detail' %}</a>
|
||||
<a href="{% url 'view_items_inventory' entity_slug=entity_slug po_pk=po.pk %}" class="dropdown-item text-success-dark">{% trans 'View Inventory Items' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{%endfor%}
|
||||
{% else%}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">No purchase orders found.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{%endfor%}
|
||||
{% else%}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">No purchase orders found.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'modal/delete_modal.html' %}
|
||||
{% endblock %}
|
||||
@ -29,8 +29,8 @@
|
||||
<a href="{% url 'purchase_order_detail' po_model.uuid %}"
|
||||
class="btn btn-phoenix-secondary w-100 my-2">{% trans 'Back to PO Detail' %}</a>
|
||||
<a href="{% url 'purchase_order_list' %}"
|
||||
class="btn btn-phoenix-info
|
||||
info w-100 my-2">{% trans 'PO List' %}</a>
|
||||
class="btn btn-phoenix-info
|
||||
info w-100 my-2">{% trans 'PO List' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -49,30 +49,30 @@
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Item' %}</th>
|
||||
<th>{% trans 'Quantity' %}</th>
|
||||
<th>{% trans 'Avg Unit Price' %}</th>
|
||||
<th>{% trans 'Total Contracted Cost' %}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans 'Item' %}</th>
|
||||
<th>{% trans 'Quantity' %}</th>
|
||||
<th>{% trans 'Avg Unit Price' %}</th>
|
||||
<th>{% trans 'Total Contracted Cost' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th>{% currency_symbol %}{{ ce_cost_estimate__sum | currency_format }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th>{% currency_symbol %}{{ ce_cost_estimate__sum | currency_format }}</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
{% for i in ce_itemtxs_agg %}
|
||||
<tr>
|
||||
<td>{{ i.item_model__name }}</td>
|
||||
<td>{{ i.ce_quantity__sum }}</td>
|
||||
<td>{% currency_symbol %}{{ i.avg_unit_cost | currency_format }}</td>
|
||||
<td>{% currency_symbol %}{{ i.ce_cost_estimate__sum | currency_format }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% for i in ce_itemtxs_agg %}
|
||||
<tr>
|
||||
<td>{{ i.item_model__name }}</td>
|
||||
<td>{{ i.ce_quantity__sum }}</td>
|
||||
<td>{% currency_symbol %}{{ i.avg_unit_cost | currency_format }}</td>
|
||||
<td>{% currency_symbol %}{{ i.ce_cost_estimate__sum | currency_format }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -93,18 +93,18 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
function showPOModal(title, actionUrl, buttonText) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('POModal'));
|
||||
document.getElementById('POModalTitle').innerText = title;
|
||||
<script>
|
||||
function showPOModal(title, actionUrl, buttonText) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('POModal'));
|
||||
document.getElementById('POModalTitle').innerText = title;
|
||||
|
||||
// Set the modal body content
|
||||
document.getElementById('POModalBody').innerHTML = `
|
||||
document.getElementById('POModalBody').innerHTML = `
|
||||
<a class="btn btn-phoenix-primary" href="${actionUrl}">
|
||||
${buttonText}
|
||||
</a>
|
||||
`;
|
||||
modal.show();
|
||||
}
|
||||
</script>
|
||||
modal.show();
|
||||
}
|
||||
</script>
|
||||
{% endblock customJS %}
|
||||
@ -1,60 +1,60 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="mt-4">
|
||||
<h1><i class="fa-solid fa-cart-shopping me-1"></i>{{po.po_number}}</h1>
|
||||
<div class="mt-4">
|
||||
<h1><i class="fa-solid fa-cart-shopping me-1"></i>{{po.po_number}}</h1>
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<h4>Status:</h4>
|
||||
<div class="d-flex align-items-center">
|
||||
<h4>Status:</h4>
|
||||
{% comment %} Apply appropriate text color based on po.po_status {% endcomment %}
|
||||
{% if po.po_status == 'draft' %}
|
||||
{% if po.po_status == 'draft' %}
|
||||
<h4 class="ms-2 text-warning mb-0">{{ po.po_status|capfirst }}</h4>
|
||||
{% elif po.po_status == 'in_review' %}
|
||||
{% elif po.po_status == 'in_review' %}
|
||||
<h4 class="ms-2 text-primary mb-0">{{ po.po_status|capfirst }}</h4>
|
||||
{% elif po.po_status == 'approved' %}
|
||||
{% elif po.po_status == 'approved' %}
|
||||
<h4 class="ms-2 text-info mb-0">{{ po.po_status|capfirst }}</h4>
|
||||
{% elif po.po_status == 'fulfilled' %}
|
||||
{% elif po.po_status == 'fulfilled' %}
|
||||
<h4 class="ms-2 text-success mb-0">{{ po.po_status|capfirst }}</h4>
|
||||
{% elif po.po_status == 'void' %}
|
||||
{% elif po.po_status == 'void' %}
|
||||
<h4 class="ms-2 text-danger mb-0">{{ po.po_status|capfirst }}</h4>
|
||||
{% else %}
|
||||
{% else %}
|
||||
<h4 class="ms-2 text-muted mb-0">{{ po.po_status|capfirst }}</h4> {# Use muted for unknown/default status #}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="table-responsive mt-3">
|
||||
<table class="table table-striped table-hover align-middle">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Quatnity</th>
|
||||
<th scope="col">Unit Cost</th>
|
||||
<th scope="col">Is Data Uploaded ?</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<th scope="row">{{ item.item_model }}</th>
|
||||
<th scope="row">{{ item.po_quantity }}</th>
|
||||
<th scope="row">{{ item.po_unit_cost }}</th>
|
||||
<th scope="row">
|
||||
{% if item.item_model.additional_info.uploaded %}
|
||||
<i data-feather="check-circle" class="text-success"></i>
|
||||
{% else %}
|
||||
<a href="{% url 'upload_cars' item.pk %}" class="btn btn-sm btn-phoenix-primary">
|
||||
<i data-feather="upload" class="me-2"></i>Upload Data
|
||||
</a>
|
||||
{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
|
||||
<div class="table-responsive mt-3">
|
||||
<table class="table table-striped table-hover align-middle">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Quatnity</th>
|
||||
<th scope="col">Unit Cost</th>
|
||||
<th scope="col">Is Data Uploaded ?</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<th scope="row">{{ item.item_model }}</th>
|
||||
<th scope="row">{{ item.po_quantity }}</th>
|
||||
<th scope="row">{{ item.po_unit_cost }}</th>
|
||||
<th scope="row">
|
||||
{% if item.item_model.additional_info.uploaded %}
|
||||
<i data-feather="check-circle" class="text-success"></i>
|
||||
{% else %}
|
||||
<a href="{% url 'upload_cars' item.pk %}" class="btn btn-sm btn-phoenix-primary">
|
||||
<i data-feather="upload" class="me-2"></i>Upload Data
|
||||
</a>
|
||||
{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
{% endblock content %}
|
||||
@ -60,11 +60,11 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
{% load i18n static %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% block title %}
|
||||
{% trans 'Sale Order' %}
|
||||
{% trans 'Sale Order' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
@ -68,146 +68,146 @@
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
{% endblock customCSS %}
|
||||
{% endblock customCSS %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h2 class="h4 mb-0">
|
||||
<i class="fas fa-file-invoice me-2"></i> New Sale Order
|
||||
</h2>
|
||||
<div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h2 class="h4 mb-0">
|
||||
<i class="fas fa-file-invoice me-2"></i> New Sale Order
|
||||
</h2>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<form id="saleOrderForm" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="card-body">
|
||||
<form id="saleOrderForm" method="post">
|
||||
{% csrf_token %}
|
||||
<!-- Basic Information Section -->
|
||||
<div class="form-section">
|
||||
<h4 class="form-section-header">
|
||||
<i class="fas fa-info-circle me-2"></i> Basic Information
|
||||
</h4>
|
||||
<div class="form-section">
|
||||
<h4 class="form-section-header">
|
||||
<i class="fas fa-info-circle me-2"></i> Basic Information
|
||||
</h4>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="row g-3">
|
||||
<!-- Estimate -->
|
||||
<div class="col-md-6">
|
||||
{{form.estimate|as_crispy_field}}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{form.estimate|as_crispy_field}}
|
||||
</div>
|
||||
|
||||
<!-- Opportunity -->
|
||||
<div class="col-md-6">
|
||||
{{form.opportunity|as_crispy_field}}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{form.opportunity|as_crispy_field}}
|
||||
</div>
|
||||
|
||||
<!-- Customer -->
|
||||
<div class="col-md-6">
|
||||
{% if form.customer %}
|
||||
<div class="col-md-6">
|
||||
{% if form.customer %}
|
||||
{{form.customer}}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Vehicle -->
|
||||
<div class="col-md-6">
|
||||
<label for="car" class="form-label required-field">Vehicles</label>
|
||||
<ul class="list-group">
|
||||
{% for car in data.cars %}
|
||||
<div class="col-md-6">
|
||||
<label for="car" class="form-label required-field">Vehicles</label>
|
||||
<ul class="list-group">
|
||||
{% for car in data.cars %}
|
||||
<li class="list-group-item d-flex justify-content-around align-items-center">
|
||||
<span class="badge bg-info rounded-pill">{{ car.make }}</span>
|
||||
<span class="badge bg-info rounded-pill">{{ car.model }}</span>
|
||||
<span class="badge bg-info rounded-pill">{{ car.year }}</span>
|
||||
<span class="badge bg-info rounded-pill">{{ car.vin }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Payment Method -->
|
||||
<div class="col-md-6">
|
||||
{{form.payment_method|as_crispy_field}}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{form.payment_method|as_crispy_field}}
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="col-md-6">
|
||||
{{form.status|as_crispy_field}}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
{{form.status|as_crispy_field}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Financial Details Section -->
|
||||
<div class="form-section">
|
||||
<h4 class="form-section-header">
|
||||
<i class="fas fa-money-bill-wave me-2"></i> Financial Details
|
||||
</h4>
|
||||
<div class="form-section">
|
||||
<h4 class="form-section-header">
|
||||
<i class="fas fa-money-bill-wave me-2"></i> Financial Details
|
||||
</h4>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="row g-3">
|
||||
<!-- Agreed Price -->
|
||||
<div class="col-md-6">
|
||||
{{form.agreed_price|as_crispy_field}}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{form.agreed_price|as_crispy_field}}
|
||||
</div>
|
||||
|
||||
<!-- Down Payment Amount -->
|
||||
<div class="col-md-6">
|
||||
<div class="currency-input">
|
||||
{{form.down_payment_amount|as_crispy_field}}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="currency-input">
|
||||
{{form.down_payment_amount|as_crispy_field}}
|
||||
</div>
|
||||
<!-- Loan Amount -->
|
||||
<div class="col-md-6">
|
||||
{{form.loan_amount|as_crispy_field}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Loan Amount -->
|
||||
<div class="col-md-6">
|
||||
{{form.loan_amount|as_crispy_field}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delivery Information Section -->
|
||||
<div class="form-section">
|
||||
<h4 class="form-section-header">
|
||||
<i class="fas fa-truck me-2"></i> Delivery Information
|
||||
</h4>
|
||||
<div class="form-section">
|
||||
<h4 class="form-section-header">
|
||||
<i class="fas fa-truck me-2"></i> Delivery Information
|
||||
</h4>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="row g-3">
|
||||
|
||||
<!-- Expected Delivery Date -->
|
||||
<div class="col-md-6">
|
||||
{{form.expected_delivery_date|as_crispy_field}}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
{{form.expected_delivery_date|as_crispy_field}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Comments -->
|
||||
<div class="col-12">
|
||||
<label for="comments" class="form-label">Comments</label>
|
||||
<textarea class="form-control" id="comments" rows="3" placeholder="Enter any additional comments..."></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="comments" class="form-label">Comments</label>
|
||||
<textarea class="form-control" id="comments" rows="3" placeholder="Enter any additional comments..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="form-actions mt-4">
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'estimate_detail' estimate.pk %}" type="button" class="btn btn-phoenix-secondary">
|
||||
<i class="fas fa-times me-2"></i> Cancel
|
||||
</a>
|
||||
<div>
|
||||
<div class="form-actions mt-4">
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'estimate_detail' estimate.pk %}" type="button" class="btn btn-phoenix-secondary">
|
||||
<i class="fas fa-times me-2"></i> Cancel
|
||||
</a>
|
||||
<div>
|
||||
|
||||
<button type="submit" class="btn btn-phoenix-primary">
|
||||
<i class="fas fa-check-circle me-2"></i> Submit Order
|
||||
</button>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-phoenix-primary">
|
||||
<i class="fas fa-check-circle me-2"></i> Submit Order
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block customJS %}
|
||||
|
||||
@ -72,11 +72,11 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
File diff suppressed because it is too large
Load Diff
@ -27,21 +27,21 @@
|
||||
<td class="align-middle product white-space-nowrap py-0">{{ order.formatted_order_id }}</td>
|
||||
<td class="align-middle product white-space-nowrap py-0">{{ order.estimate.customer.customer_name }}</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<a href="{% url 'estimate_detail' order.estimate.pk %}">
|
||||
{{ order.estimate }}
|
||||
</a>
|
||||
<a href="{% url 'estimate_detail' order.estimate.pk %}">
|
||||
{{ order.estimate }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
{% if order.invoice %}
|
||||
<a href="{% url 'invoice_detail' order.invoice.pk %}">
|
||||
{{ order.invoice }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if order.invoice %}
|
||||
<a href="{% url 'invoice_detail' order.invoice.pk %}">
|
||||
{{ order.invoice }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap py-0">{{ order.status }}</td>
|
||||
<td class="align-middle product white-space-nowrap py-0">{{ order.expected_delivery_date|naturalday|capfirst }}</td>
|
||||
<td class="align-middle product white-space-nowrap py-0">
|
||||
<a class="btn btn-sm btn-phoenix-success" href="{% url 'sale_order_details' order.estimate.pk order.pk %}">View</a>
|
||||
<a class="btn btn-sm btn-phoenix-success" href="{% url 'sale_order_details' order.estimate.pk order.pk %}">View</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
@ -53,11 +53,11 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@ -39,11 +39,11 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -54,11 +54,11 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -217,9 +217,9 @@
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,265 +4,265 @@
|
||||
{% block title %}{{ _("Terms of use and privacy policy")}}{% endblock title %}
|
||||
{% block content %}
|
||||
<style>
|
||||
h2 {
|
||||
font-size: 1.7em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.7em;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
font-size: 1.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="content fs-9">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="content fs-9">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<!-- English Section -->
|
||||
<h2>Date: 1/1/2025</h2>
|
||||
<section id="terms">
|
||||
<h2>Terms of Service</h2>
|
||||
<p>Welcome to <strong>Haikal</strong>, an advanced car inventory management platform owned and operated by <strong>Tenhal Information Technology Company</strong> ("we", "our", "us"). By accessing or using the Haikal system ("the Service"), you agree to be legally bound by the terms outlined below.</p>
|
||||
<h3>1. Acceptance of Terms</h3>
|
||||
<p>By using the Service, you confirm that you are authorized to act on behalf of a business entity, agree to these Terms of Service, and comply with all applicable laws and regulations.</p>
|
||||
<h2>Date: 1/1/2025</h2>
|
||||
<section id="terms">
|
||||
<h2>Terms of Service</h2>
|
||||
<p>Welcome to <strong>Haikal</strong>, an advanced car inventory management platform owned and operated by <strong>Tenhal Information Technology Company</strong> ("we", "our", "us"). By accessing or using the Haikal system ("the Service"), you agree to be legally bound by the terms outlined below.</p>
|
||||
<h3>1. Acceptance of Terms</h3>
|
||||
<p>By using the Service, you confirm that you are authorized to act on behalf of a business entity, agree to these Terms of Service, and comply with all applicable laws and regulations.</p>
|
||||
|
||||
<h3>2. Description of Service</h3>
|
||||
<p>Haikal provides car dealers and authorized users with tools for managing car inventory, sales, branches, financial transactions, and analytics. Additional services may include integration with government systems, API access, and reporting modules.</p>
|
||||
<h3>2. Description of Service</h3>
|
||||
<p>Haikal provides car dealers and authorized users with tools for managing car inventory, sales, branches, financial transactions, and analytics. Additional services may include integration with government systems, API access, and reporting modules.</p>
|
||||
|
||||
<h3>3. Account Registration & Security</h3>
|
||||
<ul>
|
||||
<li>You must register and maintain a secure account with accurate information.</li>
|
||||
<li>You are solely responsible for any activity under your account.</li>
|
||||
<li>You must notify us immediately if you suspect unauthorized access or breach of your account.</li>
|
||||
</ul>
|
||||
<h3>3. Account Registration & Security</h3>
|
||||
<ul>
|
||||
<li>You must register and maintain a secure account with accurate information.</li>
|
||||
<li>You are solely responsible for any activity under your account.</li>
|
||||
<li>You must notify us immediately if you suspect unauthorized access or breach of your account.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. License and Restrictions</h3>
|
||||
<ul>
|
||||
<li>We grant you a non-exclusive, non-transferable, revocable license to use the Service in accordance with these terms.</li>
|
||||
<li>You may not copy, modify, distribute, resell, reverse-engineer, or decompile any part of the Service.</li>
|
||||
</ul>
|
||||
<h3>4. License and Restrictions</h3>
|
||||
<ul>
|
||||
<li>We grant you a non-exclusive, non-transferable, revocable license to use the Service in accordance with these terms.</li>
|
||||
<li>You may not copy, modify, distribute, resell, reverse-engineer, or decompile any part of the Service.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. User Obligations</h3>
|
||||
<ul>
|
||||
<li>You agree not to upload illegal, harmful, or offensive data to the system.</li>
|
||||
<li>You are responsible for maintaining compliance with data privacy regulations when inputting customer data.</li>
|
||||
<li>You must not attempt to access systems or data not explicitly made available to you.</li>
|
||||
</ul>
|
||||
<h3>5. User Obligations</h3>
|
||||
<ul>
|
||||
<li>You agree not to upload illegal, harmful, or offensive data to the system.</li>
|
||||
<li>You are responsible for maintaining compliance with data privacy regulations when inputting customer data.</li>
|
||||
<li>You must not attempt to access systems or data not explicitly made available to you.</li>
|
||||
</ul>
|
||||
|
||||
<h3>6. Intellectual Property</h3>
|
||||
<p>All content, software, user interface designs, databases, and trademarks within Haikal are the intellectual property of Tenhal Information Technology Company and are protected under local and international IP laws.</p>
|
||||
<h3>6. Intellectual Property</h3>
|
||||
<p>All content, software, user interface designs, databases, and trademarks within Haikal are the intellectual property of Tenhal Information Technology Company and are protected under local and international IP laws.</p>
|
||||
|
||||
<h3>7. Service Availability & Modifications</h3>
|
||||
<ul>
|
||||
<li>We aim to provide 99.9% uptime but do not guarantee uninterrupted access.</li>
|
||||
<li>We may modify or discontinue parts of the service at any time with or without notice.</li>
|
||||
</ul>
|
||||
<h3>7. Service Availability & Modifications</h3>
|
||||
<ul>
|
||||
<li>We aim to provide 99.9% uptime but do not guarantee uninterrupted access.</li>
|
||||
<li>We may modify or discontinue parts of the service at any time with or without notice.</li>
|
||||
</ul>
|
||||
|
||||
<h3>8. Third-Party Integrations</h3>
|
||||
<p>We may integrate with external services such as VIN databases, payment processors, or government systems. Use of those services is subject to their own terms and privacy policies.</p>
|
||||
<h3>8. Third-Party Integrations</h3>
|
||||
<p>We may integrate with external services such as VIN databases, payment processors, or government systems. Use of those services is subject to their own terms and privacy policies.</p>
|
||||
|
||||
<h3>9. Limitation of Liability</h3>
|
||||
<p>To the fullest extent permitted by law, Tenhal is not liable for indirect, incidental, punitive, or consequential damages resulting from your use of the Service. Our total liability is limited to the amount you paid us in the last 12 months.</p>
|
||||
<h3>9. Limitation of Liability</h3>
|
||||
<p>To the fullest extent permitted by law, Tenhal is not liable for indirect, incidental, punitive, or consequential damages resulting from your use of the Service. Our total liability is limited to the amount you paid us in the last 12 months.</p>
|
||||
|
||||
<h3>10. Termination</h3>
|
||||
<p>We may suspend or terminate your access if you violate these terms. Upon termination, your access to the Service and associated data may be revoked or deleted.</p>
|
||||
<h3>10. Termination</h3>
|
||||
<p>We may suspend or terminate your access if you violate these terms. Upon termination, your access to the Service and associated data may be revoked or deleted.</p>
|
||||
|
||||
<h3>11. Governing Law</h3>
|
||||
<p>These terms are governed by the laws of the Kingdom of Saudi Arabia. Any disputes will be resolved exclusively in courts located in Riyadh.</p>
|
||||
</section>
|
||||
<h3>11. Governing Law</h3>
|
||||
<p>These terms are governed by the laws of the Kingdom of Saudi Arabia. Any disputes will be resolved exclusively in courts located in Riyadh.</p>
|
||||
</section>
|
||||
|
||||
<hr>
|
||||
<hr>
|
||||
|
||||
<section id="privacy">
|
||||
<h2>Privacy Policy</h2>
|
||||
<section id="privacy">
|
||||
<h2>Privacy Policy</h2>
|
||||
|
||||
<p>We value your privacy and are committed to protecting your personal and business data. This Privacy Policy explains how we collect, use, and protect your information when you use Haikal.</p>
|
||||
<p>We value your privacy and are committed to protecting your personal and business data. This Privacy Policy explains how we collect, use, and protect your information when you use Haikal.</p>
|
||||
|
||||
<h3>1. Information We Collect</h3>
|
||||
<ul>
|
||||
<li><strong>Account Information:</strong> Name, email, phone number, user role, and login credentials.</li>
|
||||
<li><strong>Business Data:</strong> Inventory details, financial transactions, customer and supplier records.</li>
|
||||
<li><strong>Technical Data:</strong> IP addresses, browser types, login timestamps, session logs, device identifiers.</li>
|
||||
</ul>
|
||||
<h3>1. Information We Collect</h3>
|
||||
<ul>
|
||||
<li><strong>Account Information:</strong> Name, email, phone number, user role, and login credentials.</li>
|
||||
<li><strong>Business Data:</strong> Inventory details, financial transactions, customer and supplier records.</li>
|
||||
<li><strong>Technical Data:</strong> IP addresses, browser types, login timestamps, session logs, device identifiers.</li>
|
||||
</ul>
|
||||
|
||||
<h3>2. How We Use Your Information</h3>
|
||||
<ul>
|
||||
<li>To operate and improve the Service.</li>
|
||||
<li>To secure accounts and prevent misuse or fraud.</li>
|
||||
<li>To provide customer support and respond to inquiries.</li>
|
||||
<li>To comply with legal obligations and cooperate with regulators when required.</li>
|
||||
</ul>
|
||||
<h3>2. How We Use Your Information</h3>
|
||||
<ul>
|
||||
<li>To operate and improve the Service.</li>
|
||||
<li>To secure accounts and prevent misuse or fraud.</li>
|
||||
<li>To provide customer support and respond to inquiries.</li>
|
||||
<li>To comply with legal obligations and cooperate with regulators when required.</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. Data Sharing</h3>
|
||||
<ul>
|
||||
<li>We do not sell your information to third parties.</li>
|
||||
<li>We may share data with trusted processors (e.g., hosting, support tools) under strict confidentiality terms.</li>
|
||||
<li>We may disclose data to authorities when legally required.</li>
|
||||
</ul>
|
||||
<h3>3. Data Sharing</h3>
|
||||
<ul>
|
||||
<li>We do not sell your information to third parties.</li>
|
||||
<li>We may share data with trusted processors (e.g., hosting, support tools) under strict confidentiality terms.</li>
|
||||
<li>We may disclose data to authorities when legally required.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Data Storage and Security</h3>
|
||||
<ul>
|
||||
<li>Your data is stored securely on encrypted servers with access control policies in place.</li>
|
||||
<li>We apply firewalls, intrusion detection, and regular audits to safeguard information.</li>
|
||||
</ul>
|
||||
<h3>4. Data Storage and Security</h3>
|
||||
<ul>
|
||||
<li>Your data is stored securely on encrypted servers with access control policies in place.</li>
|
||||
<li>We apply firewalls, intrusion detection, and regular audits to safeguard information.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Your Rights</h3>
|
||||
<ul>
|
||||
<li>You have the right to access, correct, or request deletion of your data.</li>
|
||||
<li>You may contact us to object to processing or request data portability.</li>
|
||||
</ul>
|
||||
<h3>5. Your Rights</h3>
|
||||
<ul>
|
||||
<li>You have the right to access, correct, or request deletion of your data.</li>
|
||||
<li>You may contact us to object to processing or request data portability.</li>
|
||||
</ul>
|
||||
|
||||
<h3>6. Data Retention</h3>
|
||||
<p>We retain data as long as necessary to provide the service, comply with legal obligations, or enforce agreements. Upon request, we may anonymize or delete your data.</p>
|
||||
<h3>6. Data Retention</h3>
|
||||
<p>We retain data as long as necessary to provide the service, comply with legal obligations, or enforce agreements. Upon request, we may anonymize or delete your data.</p>
|
||||
|
||||
<h3>7. Cookies and Tracking</h3>
|
||||
<p>We may use cookies to enhance your experience. These may include session cookies, authentication tokens, and analytics tools.</p>
|
||||
<h3>7. Cookies and Tracking</h3>
|
||||
<p>We may use cookies to enhance your experience. These may include session cookies, authentication tokens, and analytics tools.</p>
|
||||
|
||||
<h3>8. International Data Transfers</h3>
|
||||
<p>If data is processed outside of Saudi Arabia, we ensure adequate protection via agreements and security standards aligned with applicable laws.</p>
|
||||
<h3>8. International Data Transfers</h3>
|
||||
<p>If data is processed outside of Saudi Arabia, we ensure adequate protection via agreements and security standards aligned with applicable laws.</p>
|
||||
|
||||
<h3>9. Changes to this Policy</h3>
|
||||
<p>We may revise this Privacy Policy from time to time. Updates will be posted here with a revised effective date.</p>
|
||||
</section>
|
||||
<h3>9. Changes to this Policy</h3>
|
||||
<p>We may revise this Privacy Policy from time to time. Updates will be posted here with a revised effective date.</p>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-6" dir="rtl">
|
||||
<h2>التاريخ: ١/١/٢٠٢٥</h2>
|
||||
</div>
|
||||
<div class="col-6" dir="rtl">
|
||||
<h2>التاريخ: ١/١/٢٠٢٥</h2>
|
||||
<!-- Arabic Section -->
|
||||
<section class="arabic">
|
||||
<h2>شروط الخدمة</h2>
|
||||
<p>مرحبًا بك في <strong>هيكل</strong>، منصة متقدمة لإدارة مخزون السيارات، مملوكة وتديرها <strong>شركة تنحل لتقنية المعلومات</strong> ("نحن"، "خاصتنا"). باستخدامك لنظام هيكل، فإنك توافق على الالتزام القانوني بالشروط التالية:</p>
|
||||
<section class="arabic">
|
||||
<h2>شروط الخدمة</h2>
|
||||
<p>مرحبًا بك في <strong>هيكل</strong>، منصة متقدمة لإدارة مخزون السيارات، مملوكة وتديرها <strong>شركة تنحل لتقنية المعلومات</strong> ("نحن"، "خاصتنا"). باستخدامك لنظام هيكل، فإنك توافق على الالتزام القانوني بالشروط التالية:</p>
|
||||
|
||||
<h3>١. قبول الشروط</h3>
|
||||
<p>باستخدامك للخدمة، فإنك تؤكد أنك مفوض بالتصرف نيابة عن كيان تجاري، وتوافق على شروط الخدمة هذه، وتلتزم بجميع القوانين والأنظمة المعمول بها.</p>
|
||||
<h3>١. قبول الشروط</h3>
|
||||
<p>باستخدامك للخدمة، فإنك تؤكد أنك مفوض بالتصرف نيابة عن كيان تجاري، وتوافق على شروط الخدمة هذه، وتلتزم بجميع القوانين والأنظمة المعمول بها.</p>
|
||||
|
||||
<h3>٢. وصف الخدمة</h3>
|
||||
<p>يوفر هيكل أدوات لتجار السيارات والمستخدمين المخولين لإدارة المخزون، المبيعات، الفروع، المعاملات المالية، والتحليلات. تشمل الخدمات الإضافية تكاملات مع أنظمة حكومية، وصول API، وتقارير.</p>
|
||||
<h3>٢. وصف الخدمة</h3>
|
||||
<p>يوفر هيكل أدوات لتجار السيارات والمستخدمين المخولين لإدارة المخزون، المبيعات، الفروع، المعاملات المالية، والتحليلات. تشمل الخدمات الإضافية تكاملات مع أنظمة حكومية، وصول API، وتقارير.</p>
|
||||
|
||||
<h3>٣. التسجيل والحماية</h3>
|
||||
<ul>
|
||||
<li>يجب تسجيل حساب دقيق وآمن.</li>
|
||||
<li>أنت مسؤول عن كل نشاط يتم عبر حسابك.</li>
|
||||
<li>يجب إبلاغنا فورًا عند الاشتباه في اختراق الحساب.</li>
|
||||
</ul>
|
||||
<h3>٣. التسجيل والحماية</h3>
|
||||
<ul>
|
||||
<li>يجب تسجيل حساب دقيق وآمن.</li>
|
||||
<li>أنت مسؤول عن كل نشاط يتم عبر حسابك.</li>
|
||||
<li>يجب إبلاغنا فورًا عند الاشتباه في اختراق الحساب.</li>
|
||||
</ul>
|
||||
|
||||
<h3>٤. الترخيص والقيود</h3>
|
||||
<ul>
|
||||
<li>نمنحك ترخيصًا غير حصري وقابل للإلغاء لاستخدام الخدمة.</li>
|
||||
<li>لا يحق لك نسخ، تعديل، توزيع، أو عكس هندسة أي جزء من الخدمة.</li>
|
||||
</ul>
|
||||
<h3>٤. الترخيص والقيود</h3>
|
||||
<ul>
|
||||
<li>نمنحك ترخيصًا غير حصري وقابل للإلغاء لاستخدام الخدمة.</li>
|
||||
<li>لا يحق لك نسخ، تعديل، توزيع، أو عكس هندسة أي جزء من الخدمة.</li>
|
||||
</ul>
|
||||
|
||||
<h3>٥. التزامات المستخدم</h3>
|
||||
<ul>
|
||||
<li>عدم تحميل بيانات غير قانونية أو ضارة.</li>
|
||||
<li>أنت مسؤول عن الامتثال لقوانين خصوصية البيانات.</li>
|
||||
<li>لا تحاول الوصول لبيانات أو أنظمة غير مصرّح بها.</li>
|
||||
</ul>
|
||||
<h3>٥. التزامات المستخدم</h3>
|
||||
<ul>
|
||||
<li>عدم تحميل بيانات غير قانونية أو ضارة.</li>
|
||||
<li>أنت مسؤول عن الامتثال لقوانين خصوصية البيانات.</li>
|
||||
<li>لا تحاول الوصول لبيانات أو أنظمة غير مصرّح بها.</li>
|
||||
</ul>
|
||||
|
||||
<h3>٦. الملكية الفكرية</h3>
|
||||
<p>جميع المحتويات، البرمجيات، قواعد البيانات، والتصاميم تخص تنحل وتخضع للقوانين المحلية والدولية.</p>
|
||||
<h3>٦. الملكية الفكرية</h3>
|
||||
<p>جميع المحتويات، البرمجيات، قواعد البيانات، والتصاميم تخص تنحل وتخضع للقوانين المحلية والدولية.</p>
|
||||
|
||||
<h3>٧. توفر الخدمة والتعديلات</h3>
|
||||
<ul>
|
||||
<li>نهدف لتوفير الخدمة بنسبة تشغيل 99.9٪ ولكن لا نضمن عدم الانقطاع.</li>
|
||||
<li>قد نقوم بتحديث أو تعديل أو إيقاف الخدمة في أي وقت.</li>
|
||||
</ul>
|
||||
<h3>٧. توفر الخدمة والتعديلات</h3>
|
||||
<ul>
|
||||
<li>نهدف لتوفير الخدمة بنسبة تشغيل 99.9٪ ولكن لا نضمن عدم الانقطاع.</li>
|
||||
<li>قد نقوم بتحديث أو تعديل أو إيقاف الخدمة في أي وقت.</li>
|
||||
</ul>
|
||||
|
||||
<h3>٨. تكامل الأطراف الخارجية</h3>
|
||||
<p>قد نتكامل مع خدمات خارجية مثل قواعد بيانات VIN، ومعالجات الدفع، والأنظمة الحكومية. يخضع استخدام هذه الخدمات لشروطها الخاصة.</p>
|
||||
<h3>٨. تكامل الأطراف الخارجية</h3>
|
||||
<p>قد نتكامل مع خدمات خارجية مثل قواعد بيانات VIN، ومعالجات الدفع، والأنظمة الحكومية. يخضع استخدام هذه الخدمات لشروطها الخاصة.</p>
|
||||
|
||||
<h3>٩. حدود المسؤولية</h3>
|
||||
<p>أقصى مسؤولية لنا عن أي ضرر غير مباشر أو عرضي تقتصر على ما دفعته خلال الـ 12 شهرًا الماضية.</p>
|
||||
<h3>٩. حدود المسؤولية</h3>
|
||||
<p>أقصى مسؤولية لنا عن أي ضرر غير مباشر أو عرضي تقتصر على ما دفعته خلال الـ 12 شهرًا الماضية.</p>
|
||||
|
||||
<h3>١٠. الإنهاء</h3>
|
||||
<p>يجوز لنا إنهاء أو تعليق حسابك إذا انتهكت هذه الشروط. وقد يتم حذف بياناتك بعد الإنهاء.</p>
|
||||
<h3>١٠. الإنهاء</h3>
|
||||
<p>يجوز لنا إنهاء أو تعليق حسابك إذا انتهكت هذه الشروط. وقد يتم حذف بياناتك بعد الإنهاء.</p>
|
||||
|
||||
<h3>١١. القانون الحاكم</h3>
|
||||
<p>تخضع هذه الشروط لقوانين المملكة العربية السعودية، ويكون الاختصاص القضائي لمحاكم الرياض فقط.</p>
|
||||
</section>
|
||||
<h3>١١. القانون الحاكم</h3>
|
||||
<p>تخضع هذه الشروط لقوانين المملكة العربية السعودية، ويكون الاختصاص القضائي لمحاكم الرياض فقط.</p>
|
||||
</section>
|
||||
|
||||
<hr>
|
||||
<hr>
|
||||
|
||||
<section class="arabic">
|
||||
<h2>سياسة الخصوصية</h2>
|
||||
<section class="arabic">
|
||||
<h2>سياسة الخصوصية</h2>
|
||||
|
||||
<p>نحن نهتم بخصوصيتك وملتزمون بحماية بياناتك الشخصية والتجارية. توضح هذه السياسة كيفية جمع واستخدام وحماية بياناتك عند استخدام نظام هيكل.</p>
|
||||
<p>نحن نهتم بخصوصيتك وملتزمون بحماية بياناتك الشخصية والتجارية. توضح هذه السياسة كيفية جمع واستخدام وحماية بياناتك عند استخدام نظام هيكل.</p>
|
||||
|
||||
<h3>١. المعلومات التي نجمعها</h3>
|
||||
<ul>
|
||||
<li><strong>بيانات الحساب:</strong> الاسم، البريد الإلكتروني، الهاتف، الدور، بيانات تسجيل الدخول.</li>
|
||||
<li><strong>بيانات الأعمال:</strong> تفاصيل السيارات، المعاملات المالية، سجلات العملاء والموردين.</li>
|
||||
<li><strong>بيانات تقنية:</strong> عناوين IP، أنواع المتصفحات، أوقات الدخول، سجلات الجلسات، معرفات الأجهزة.</li>
|
||||
</ul>
|
||||
<h3>١. المعلومات التي نجمعها</h3>
|
||||
<ul>
|
||||
<li><strong>بيانات الحساب:</strong> الاسم، البريد الإلكتروني، الهاتف، الدور، بيانات تسجيل الدخول.</li>
|
||||
<li><strong>بيانات الأعمال:</strong> تفاصيل السيارات، المعاملات المالية، سجلات العملاء والموردين.</li>
|
||||
<li><strong>بيانات تقنية:</strong> عناوين IP، أنواع المتصفحات، أوقات الدخول، سجلات الجلسات، معرفات الأجهزة.</li>
|
||||
</ul>
|
||||
|
||||
<h3>٢. استخدام البيانات</h3>
|
||||
<ul>
|
||||
<li>لتشغيل الخدمة وتحسينها.</li>
|
||||
<li>لحماية الحسابات ومنع الاحتيال.</li>
|
||||
<li>لدعم العملاء والاستجابة للاستفسارات.</li>
|
||||
<li>للالتزام بالقوانين والتعاون مع الجهات التنظيمية.</li>
|
||||
</ul>
|
||||
<h3>٢. استخدام البيانات</h3>
|
||||
<ul>
|
||||
<li>لتشغيل الخدمة وتحسينها.</li>
|
||||
<li>لحماية الحسابات ومنع الاحتيال.</li>
|
||||
<li>لدعم العملاء والاستجابة للاستفسارات.</li>
|
||||
<li>للالتزام بالقوانين والتعاون مع الجهات التنظيمية.</li>
|
||||
</ul>
|
||||
|
||||
<h3>٣. مشاركة البيانات</h3>
|
||||
<ul>
|
||||
<li>لا نبيع بياناتك لأي طرف ثالث.</li>
|
||||
<li>قد نشارك البيانات مع مزودين موثوقين بموجب اتفاقيات سرية.</li>
|
||||
<li>قد نكشف عن البيانات للجهات المختصة عند الطلب القانوني.</li>
|
||||
</ul>
|
||||
<h3>٣. مشاركة البيانات</h3>
|
||||
<ul>
|
||||
<li>لا نبيع بياناتك لأي طرف ثالث.</li>
|
||||
<li>قد نشارك البيانات مع مزودين موثوقين بموجب اتفاقيات سرية.</li>
|
||||
<li>قد نكشف عن البيانات للجهات المختصة عند الطلب القانوني.</li>
|
||||
</ul>
|
||||
|
||||
<h3>٤. التخزين والحماية</h3>
|
||||
<ul>
|
||||
<li>تُخزن البيانات على خوادم مشفرة مع سياسات وصول صارمة.</li>
|
||||
<li>نطبق جدران حماية، واكتشاف التسلل، ومراجعات دورية.</li>
|
||||
</ul>
|
||||
<h3>٤. التخزين والحماية</h3>
|
||||
<ul>
|
||||
<li>تُخزن البيانات على خوادم مشفرة مع سياسات وصول صارمة.</li>
|
||||
<li>نطبق جدران حماية، واكتشاف التسلل، ومراجعات دورية.</li>
|
||||
</ul>
|
||||
|
||||
<h3>٥. حقوقك</h3>
|
||||
<ul>
|
||||
<li>لك الحق في الوصول إلى بياناتك أو تعديلها أو طلب حذفها.</li>
|
||||
<li>يمكنك الاعتراض على المعالجة أو طلب نقل البيانات.</li>
|
||||
</ul>
|
||||
<h3>٥. حقوقك</h3>
|
||||
<ul>
|
||||
<li>لك الحق في الوصول إلى بياناتك أو تعديلها أو طلب حذفها.</li>
|
||||
<li>يمكنك الاعتراض على المعالجة أو طلب نقل البيانات.</li>
|
||||
</ul>
|
||||
|
||||
<h3>٦. الاحتفاظ بالبيانات</h3>
|
||||
<p>نحتفظ بالبيانات طالما كانت ضرورية لتقديم الخدمة أو للامتثال للأنظمة. يمكننا إزالتها أو إخفاؤها حسب الطلب.</p>
|
||||
<h3>٦. الاحتفاظ بالبيانات</h3>
|
||||
<p>نحتفظ بالبيانات طالما كانت ضرورية لتقديم الخدمة أو للامتثال للأنظمة. يمكننا إزالتها أو إخفاؤها حسب الطلب.</p>
|
||||
|
||||
<h3>٧. الكوكيز والتتبع</h3>
|
||||
<p>قد نستخدم الكوكيز لتحسين تجربتك، بما في ذلك جلسات التوثيق والتحليلات.</p>
|
||||
<h3>٧. الكوكيز والتتبع</h3>
|
||||
<p>قد نستخدم الكوكيز لتحسين تجربتك، بما في ذلك جلسات التوثيق والتحليلات.</p>
|
||||
|
||||
<h3>٨. نقل البيانات خارجياً</h3>
|
||||
<p>إذا تم نقل البيانات خارج السعودية، نضمن حمايتها وفق اتفاقيات ومعايير قانونية مناسبة.</p>
|
||||
<h3>٨. نقل البيانات خارجياً</h3>
|
||||
<p>إذا تم نقل البيانات خارج السعودية، نضمن حمايتها وفق اتفاقيات ومعايير قانونية مناسبة.</p>
|
||||
|
||||
<h3>٩. التحديثات</h3>
|
||||
<p>قد نُجري تغييرات على هذه السياسة، وسيتم نشر التعديلات مع تاريخ سريان جديد.</p>
|
||||
</section>
|
||||
<h3>٩. التحديثات</h3>
|
||||
<p>قد نُجري تغييرات على هذه السياسة، وسيتم نشر التعديلات مع تاريخ سريان جديد.</p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row border-top">
|
||||
<div class="col-6">
|
||||
<section id="contact">
|
||||
<h2>Contact Information</h2>
|
||||
<p>If you have any questions or concerns about these Terms or Privacy practices, please contact us:</p>
|
||||
<p>
|
||||
<strong>Tenhal Information Technology Company</strong><br>
|
||||
Riyadh, Saudi Arabia<br>
|
||||
📧 <a href="mailto:info@tenhal.sa">info@tenhal.sa</a><br>
|
||||
🌐 <a href="https://www.tenhal.sa" target="_blank">www.tenhal.sa</a>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6" dir="rtl">
|
||||
<section class="arabic">
|
||||
<h2>معلومات التواصل</h2>
|
||||
<p>لأي استفسار حول هذه الشروط أو سياسة الخصوصية، يرجى التواصل معنا:</p>
|
||||
<p>
|
||||
<strong>شركة تنحل لتقنية المعلومات</strong><br>
|
||||
الرياض، المملكة العربية السعودية<br>
|
||||
📧 <a href="mailto:info@tenhal.sa">info@tenhal.sa</a><br>
|
||||
🌐 <a href="https://www.tenhal.sa" target="_blank">tenhal.sa</a>
|
||||
</p>
|
||||
</section>
|
||||
<div class="row border-top">
|
||||
<div class="col-6">
|
||||
<section id="contact">
|
||||
<h2>Contact Information</h2>
|
||||
<p>If you have any questions or concerns about these Terms or Privacy practices, please contact us:</p>
|
||||
<p>
|
||||
<strong>Tenhal Information Technology Company</strong><br>
|
||||
Riyadh, Saudi Arabia<br>
|
||||
📧 <a href="mailto:info@tenhal.sa">info@tenhal.sa</a><br>
|
||||
🌐 <a href="https://www.tenhal.sa" target="_blank">www.tenhal.sa</a>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-6" dir="rtl">
|
||||
<section class="arabic">
|
||||
<h2>معلومات التواصل</h2>
|
||||
<p>لأي استفسار حول هذه الشروط أو سياسة الخصوصية، يرجى التواصل معنا:</p>
|
||||
<p>
|
||||
<strong>شركة تنحل لتقنية المعلومات</strong><br>
|
||||
الرياض، المملكة العربية السعودية<br>
|
||||
📧 <a href="mailto:info@tenhal.sa">info@tenhal.sa</a><br>
|
||||
🌐 <a href="https://www.tenhal.sa" target="_blank">tenhal.sa</a>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
@ -3,29 +3,29 @@
|
||||
{% block title %}{{ tour.name }} - Interactive Guide{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-4">
|
||||
<h1>{{ tour.name }}</h1>
|
||||
<p class="lead">{{ tour.description }}</p>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Ready to Start</h5>
|
||||
<p>This interactive guide will walk you through each step of the process.</p>
|
||||
<button class="btn btn-primary" onclick="window.tourManager.loadTour('{{ tour.slug }}')">
|
||||
Start Guide Now
|
||||
</button>
|
||||
<div class="container my-4">
|
||||
<h1>{{ tour.name }}</h1>
|
||||
<p class="lead">{{ tour.description }}</p>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Ready to Start</h5>
|
||||
<p>This interactive guide will walk you through each step of the process.</p>
|
||||
<button class="btn btn-primary" onclick="window.tourManager.loadTour('{{ tour.slug }}')">
|
||||
Start Guide Now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
<script>
|
||||
// Auto-start the tour after a short delay
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(() => {
|
||||
window.tourManager.loadTour('{{ tour.slug }}');
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(() => {
|
||||
window.tourManager.loadTour('{{ tour.slug }}');
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -3,30 +3,30 @@
|
||||
{% block title %}Interactive Guides{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-4">
|
||||
<h1>Interactive Guides</h1>
|
||||
<p class="lead">Learn how to use the car inventory system with these interactive step-by-step guides.</p>
|
||||
|
||||
<div class="row mt-4">
|
||||
{% for tour in tours %}
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ tour.name }}</h5>
|
||||
<p class="card-text">{{ tour.description }}</p>
|
||||
<div class="container my-4">
|
||||
<h1>Interactive Guides</h1>
|
||||
<p class="lead">Learn how to use the car inventory system with these interactive step-by-step guides.</p>
|
||||
|
||||
<div class="row mt-4">
|
||||
{% for tour in tours %}
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ tour.name }}</h5>
|
||||
<p class="card-text">{{ tour.description }}</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="{% url 'start_tour' tour.slug %}" class="btn btn-primary">Start Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="{% url 'start_tour' tour.slug %}" class="btn btn-primary">Start Guide</a>
|
||||
{% empty %}
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
No interactive guides available at this time.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
No interactive guides available at this time.
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
{% load crispy_forms_filters %}
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Staff'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Staff'%}
|
||||
{% endif %}
|
||||
{% if object %}
|
||||
{% trans 'Update Staff'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Staff'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
<td class="align-middle white-space-nowrap ps-1">
|
||||
<div>
|
||||
<a class="fs-8 fw-bold" href="{% url 'user_detail' user.slug%}">{{ user.arabic_name }}</a>
|
||||
|
||||
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap align-items-center">{{ user.email }}</td>
|
||||
|
||||
10
templates/vendors/vendor_form.html
vendored
10
templates/vendors/vendor_form.html
vendored
@ -3,11 +3,11 @@
|
||||
{% load crispy_forms_filters %}
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Vendor'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Vendor'%}
|
||||
{% endif %}
|
||||
{% if object %}
|
||||
{% trans 'Update Vendor'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Vendor'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user