enhance and fix htmx with modal integration

This commit is contained in:
ismail 2025-07-24 17:38:32 +03:00
parent 4815ae4f6a
commit 6c2b6b1588
13 changed files with 426 additions and 240 deletions

View File

@ -1620,7 +1620,7 @@ class CarUpdateView(
permission_required = ["inventory.change_car"] permission_required = ["inventory.change_car"]
def get_success_url(self): def get_success_url(self):
return reverse("car_detail", kwargs={"slug": self.object.slug}) return reverse("car_detail", kwargs={"dealer_slug": self.request.dealer.slug,"slug": self.object.slug})
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
@ -4446,7 +4446,7 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
related_content_type=ContentType.objects.get_for_model(models.Staff), related_content_type=ContentType.objects.get_for_model(models.Staff),
related_object_id=self.request.staff.pk, related_object_id=self.request.staff.pk,
) )
context["staff_estimates"] = qs context["staff_estimates"] = EstimateModel.objects.filter(pk__in=[x.content_object.pk for x in qs])
return context return context
def get_queryset(self): def get_queryset(self):
@ -4771,9 +4771,10 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
kwargs["data"] = finance_data kwargs["data"] = finance_data
kwargs["invoice"] = invoice_obj kwargs["invoice"] = invoice_obj
try: try:
cf = estimate.get_itemtxs_data()[0].first().item_model.car.finances car_finances = estimate.get_itemtxs_data()[0].first().item_model.car.finances
selected_items = cf.additional_services.filter(dealer=dealer) selected_items = car_finances.additional_services.filter(dealer=dealer)
form = forms.AdditionalFinancesForm() form = forms.AdditionalFinancesForm()
form.fields["additional_finances"].queryset = form.fields["additional_finances"].queryset.filter(dealer=dealer)
form.initial["additional_finances"] = selected_items form.initial["additional_finances"] = selected_items
kwargs["additionals_form"] = form kwargs["additionals_form"] = form
except Exception as e: except Exception as e:
@ -5044,6 +5045,7 @@ def estimate_mark_as(request, dealer_slug, pk):
dealer = get_object_or_404(models.Dealer, slug=dealer_slug) dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
estimate = get_object_or_404(EstimateModel, pk=pk) estimate = get_object_or_404(EstimateModel, pk=pk)
mark = request.GET.get("mark") mark = request.GET.get("mark")
print(mark)
if mark: if mark:
if mark == "review": if mark == "review":
if not estimate.can_review(): if not estimate.can_review():
@ -6485,7 +6487,7 @@ def schedule_event(request, dealer_slug, content_type, slug):
if not request.is_staff: if not request.is_staff:
messages.error(request, _("You do not have permission to schedule.")) messages.error(request, _("You do not have permission to schedule."))
return redirect(request.META.get("HTTP_REFERER")) return redirect(f"{content_type}_detail", dealer_slug=dealer_slug, slug=slug)
if request.method == "POST": if request.method == "POST":
form = forms.ScheduleForm(request.POST) form = forms.ScheduleForm(request.POST)

View File

@ -82,7 +82,7 @@
{% include "plans/expiration_messages.html" %} {% include "plans/expiration_messages.html" %}
{% block period_navigation %} {% block period_navigation %}
{% endblock period_navigation %} {% endblock period_navigation %}
<div id="main_content" class="fade-me-in" hx-boost="false" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML transition:true" hx-select-oob="#toast-container" hx-history-elt> <div id="main_content" class="fade-me-in" hx-boost="true" hx-target="#main_content" hx-select="#main_content" hx-swap="innerHTML transition:true" hx-select-oob="#toast-container" hx-history-elt>
<div id="spinner" class="htmx-indicator spinner-bg"> <div id="spinner" class="htmx-indicator spinner-bg">
<img src="{% static 'spinner.svg' %}" width="100" height="100" alt=""> <img src="{% static 'spinner.svg' %}" width="100" height="100" alt="">
</div> </div>
@ -93,8 +93,8 @@
{% comment %} <script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script> {% comment %} <script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script> <script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
<script src="{% static 'vendors/popper/popper.min.js' %}"></script> <script src="{% static 'vendors/popper/popper.min.js' %}"></script>
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script> <script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script> {% endcomment %}
<script src="{% static 'js/phoenix.js' %}"></script> {% endcomment %} <script src="{% static 'js/phoenix.js' %}"></script>
</div> </div>
{% block body %} {% block body %}
@ -186,7 +186,25 @@ document.addEventListener('htmx:afterRequest', function(evt) {
notify("error", "Unexpected Error"); notify("error", "Unexpected Error");
} }
}); });
document.body.addEventListener('htmx:beforeSwap', function(evt) {
if (evt.detail.target.id === 'main_content') {
var backdrops = document.querySelectorAll('.modal-backdrop');
backdrops.forEach(function(backdrop) {
backdrop.remove();
});
}
});
// Close modal after successful form submission
document.body.addEventListener('htmx:afterSwap', function(evt) {
if (evt.detail.target.id === 'main_content') {
document.querySelectorAll('.modal').forEach(function(m) {
var modal = bootstrap.Modal.getInstance(m);
if (modal) {
modal.hide();
}
});
}
});
</script> </script>
{% comment %} {% block customJS %}{% endblock %} {% endcomment %} {% comment %} {% block customJS %}{% endblock %} {% endcomment %}
</body> </body>

View File

@ -40,6 +40,5 @@
document.querySelector('#id_note').value = note document.querySelector('#id_note').value = note
let form = document.querySelector('.add_note_form') let form = document.querySelector('.add_note_form')
form.action = url form.action = url
} }
</script> </script>

View File

@ -16,6 +16,13 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form action="{% url 'schedule_event' request.dealer.slug content_type slug %}" <form action="{% url 'schedule_event' request.dealer.slug content_type slug %}"
hx-select=".taskTable"
hx-target=".taskTable"
hx-on::after-request="{
resetSubmitButton(document.querySelector('.add_schedule_form button[type=submit]'));
$('#scheduleModal').modal('hide');
}"
hx-swap="outerHTML"
method="post" method="post"
class="add_schedule_form"> class="add_schedule_form">
{% csrf_token %} {% csrf_token %}

View File

@ -23,15 +23,12 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form action="{% url 'add_task' request.dealer.slug content_type slug %}" <form action="{% url 'add_task' request.dealer.slug content_type slug %}"
hx-boost="true"
hx-select-oob=".taskTable:outerHTML,#toast-container:outerHTML"
hx-swap="none"
hx-on::after-request="{
resetSubmitButton(document.querySelector('.add_task_form button[type=submit]'));
$('#taskModal').modal('hide');
}"
method="post" method="post"
class="add_task_form"> class="add_task_form"
hx-post="{% url 'add_task' request.dealer.slug content_type slug %}"
hx-target="#your-content-container"
hx-swap="innerHTML"
hx-boost="false">
{% csrf_token %} {% csrf_token %}
{{ staff_task_form|crispy }} {{ staff_task_form|crispy }}
<button type="submit" class="btn btn-phoenix-success w-100">{% trans 'Save' %}</button> <button type="submit" class="btn btn-phoenix-success w-100">{% trans 'Save' %}</button>

View File

@ -971,17 +971,20 @@
} }
// Close modal after successful form submission // Close modal after successful form submission
document.body.addEventListener('htmx:afterSwap', function(evt) { /*document.body.addEventListener('htmx:afterSwap', function(evt) {
if (evt.detail.target.id === 'main_content') { if (evt.detail.target.id === 'main_content') {
var modal = bootstrap.Modal.getInstance(document.getElementById('exampleModal')); document.querySelectorAll('.modal').forEach(function(modal) {
var modal = bootstrap.Modal.getInstance();
if (modal) { if (modal) {
modal.hide(); modal.hide();
} }
});
} }
}); });
*/
// Cleanup modal backdrop if needed // Cleanup modal backdrop if needed
document.body.addEventListener('htmx:beforeSwap', function(evt) { /* document.body.addEventListener('htmx:beforeSwap', function(evt) {
if (evt.detail.target.id === 'main_content') { if (evt.detail.target.id === 'main_content') {
var backdrops = document.querySelectorAll('.modal-backdrop'); var backdrops = document.querySelectorAll('.modal-backdrop');
backdrops.forEach(function(backdrop) { backdrops.forEach(function(backdrop) {

View File

@ -1091,6 +1091,7 @@
</div> </div>
</div> </div>
{% include 'modal/delete_modal.html' %}
<!-- email Modal --> <!-- email Modal -->
{% include "components/email_modal.html" %} {% include "components/email_modal.html" %}
<!-- task Modal --> <!-- task Modal -->

View File

@ -611,12 +611,14 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endblock %}
{% block customJS %}
<script> <script>
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
const csrftoken = getCookie("csrftoken"); const csrftoken = getCookie("csrftoken");
const ajaxUrl = "{% url 'ajax_handler' request.dealer.slug %}"; const ajaxUrl = "{% url 'ajax_handler' request.dealer.slug %}";
const modalBody = customCardModal.querySelector(".modal-body"); const modalBody = customCardModal.querySelector(".modal-body");
const showSpecificationButton = document.getElementById("specification-btn"); const showSpecificationButton = document.getElementById("specification-btn");

View File

@ -24,6 +24,8 @@
</div> </div>
<div class="modal-footer flex justify-content-center border-top-0" > <div class="modal-footer flex justify-content-center border-top-0" >
<a id="deleteModalConfirm" <a id="deleteModalConfirm"
hx-select-oob="#notesTable:outerHTML,#toast-container:outerHTML"
hx-swap="none"
type="button" type="button"
class="btn btn-sm btn-phoenix-danger w-100" class="btn btn-sm btn-phoenix-danger w-100"
href="">{{ _("Delete") }}</a> href="">{{ _("Delete") }}</a>
@ -31,24 +33,47 @@
</div> </div>
</div> </div>
</div> </div>
<script> <script>
document.addEventListener("DOMContentLoaded", function () { // Initialize when page loads and after HTMX swaps
document.addEventListener('DOMContentLoaded', initDeleteModals);
document.addEventListener('htmx:afterSwap', initDeleteModals);
function initDeleteModals() {
const deleteModal = document.getElementById("deleteModal"); const deleteModal = document.getElementById("deleteModal");
const confirmDeleteBtn = document.getElementById("deleteModalConfirm"); const confirmDeleteBtn = document.getElementById("deleteModalConfirm");
const deleteModalMessage = document.getElementById("deleteModalText"); const deleteModalMessage = document.getElementById("deleteModalText");
document.querySelectorAll(".delete-btn").forEach(button => { // Clean up previous listeners if any
button.addEventListener("click", function () { document.querySelectorAll(".delete-btn").forEach(btn => {
let deleteUrl = this.getAttribute("data-url"); btn.removeEventListener("click", handleDeleteClick);
let deleteMessage = this.getAttribute("data-message"); });
// Add new listeners to all delete buttons
document.querySelectorAll(".delete-btn").forEach(button => {
button.addEventListener("click", handleDeleteClick);
});
function handleDeleteClick() {
if (!deleteModal || !confirmDeleteBtn || !deleteModalMessage) return;
const deleteUrl = this.getAttribute("data-url");
const deleteMessage = this.getAttribute("data-message") || "Are you sure you want to delete this item?";
// Update modal content
confirmDeleteBtn.setAttribute("href", deleteUrl); confirmDeleteBtn.setAttribute("href", deleteUrl);
confirmDeleteBtn.setAttribute("hx-boost", "true"); deleteModalMessage.textContent = deleteMessage; // Use textContent instead of innerHTML for security
confirmDeleteBtn.setAttribute("hx-select-oob", "#notesTable:outerHTML,#toast-container:outerHTML");
confirmDeleteBtn.setAttribute("hx-swap", "none"); // Process with HTMX if available
confirmDeleteBtn.setAttribute("hx-on::after-request", "$('#deleteModal').modal('hide');"); if (typeof htmx !== 'undefined') {
deleteModalMessage.innerHTML = deleteMessage; htmx.process(confirmDeleteBtn);
}); }
});
}); // Show the modal
/*if (typeof bootstrap !== 'undefined') {
const modal = new bootstrap.Modal(deleteModal);
modal.show();
}*/
}
}
</script> </script>

View File

@ -335,38 +335,89 @@
{% endblock %} {% endblock %}
{% block customJS %} {% block customJS %}
<script> <script>
// Initialize when page loads and after HTMX swaps
document.addEventListener('DOMContentLoaded', initEstimateFunctions);
document.addEventListener('htmx:afterSwap', initEstimateFunctions);
function initEstimateFunctions() {
// Initialize calculateTotals if estimate table exists
const estimateTable = document.getElementById('estimate-table');
if (estimateTable) {
calculateTotals();
// Optional: If you need to recalculate when table content changes
estimateTable.addEventListener('change', calculateTotals);
}
// Initialize form action setter if form exists
const confirmForm = document.getElementById('confirmForm');
if (confirmForm) {
// Remove old event listeners if any
document.querySelectorAll('[data-set-form-action]').forEach(button => {
button.removeEventListener('click', setFormActionHandler);
button.addEventListener('click', setFormActionHandler);
});
}
}
function calculateTotals() { function calculateTotals() {
const table = document.getElementById('estimate-table'); const table = document.getElementById('estimate-table');
const rows = table.getElementsByTagName('tbody')[0].rows; if (!table) return;
try {
const tbody = table.getElementsByTagName('tbody')[0];
if (!tbody) return;
const rows = tbody.rows;
let grandTotal = 0; let grandTotal = 0;
for (let row of rows) { for (let row of rows) {
// Ensure the row has the expected number of cells // Skip rows that don't have enough cells
if (row.cells.length >= 5) { if (row.cells.length < 5) continue;
const quantity = parseFloat(row.cells[2].textContent); // Quantity column
const unitPrice = parseFloat(row.cells[3].textContent); // Unit Price column // Get quantity and unit price
const quantityText = row.cells[2]?.textContent?.trim() || '0';
const unitPriceText = row.cells[3]?.textContent?.trim() || '0';
// Parse values, handling any formatting
const quantity = parseFloat(quantityText.replace(/[^0-9.-]/g, ''));
const unitPrice = parseFloat(unitPriceText.replace(/[^0-9.-]/g, ''));
if (!isNaN(quantity) && !isNaN(unitPrice)) { if (!isNaN(quantity) && !isNaN(unitPrice)) {
const total = quantity * unitPrice; const total = quantity * unitPrice;
row.cells[4].textContent = total.toFixed(2); // Populate Total column if (row.cells[4]) {
grandTotal += total; // Add to grand total row.cells[4].textContent = total.toFixed(2);
} }
grandTotal += total;
} }
} }
// Display the grand total // Update grand total display
document.getElementById('grand-total').textContent = grandTotal.toFixed(2); const grandTotalElement = document.getElementById('grand-total');
if (grandTotalElement) {
grandTotalElement.textContent = grandTotal.toFixed(2);
}
} catch (error) {
console.error('Error calculating totals:', error);
}
} }
function setFormActionHandler(event) {
// Run the function on page load const action = event.currentTarget.getAttribute('data-set-form-action');
//window.onload = calculateTotals; if (action) {
setFormAction(action);
}
}
function setFormAction(action) { function setFormAction(action) {
// Get the form element
const form = document.getElementById('confirmForm'); const form = document.getElementById('confirmForm');
// Set the form action with the query parameter if (!form) return;
form.action = "{% url 'estimate_mark_as' request.dealer.slug estimate.pk %}?mark=" + action;
const baseUrl = "{% url 'estimate_mark_as' request.dealer.slug estimate.pk %}";
form.action = `${baseUrl}?mark=${encodeURIComponent(action)}`;
// Optional: Submit form immediately after setting action
// form.submit();
} }
</script> </script>
{% endblock %} {% endblock %}

View File

@ -209,10 +209,27 @@
</div> </div>
</div> </div>
{% endblock %}
{% block customJS %}
<script> <script>
document.addEventListener('DOMContentLoaded', function() { // Global variables
const Toast = Swal.mixin({ let Toast;
let customSelectsInitialized = false;
let formInitialized = false;
// Initialize when page loads and after HTMX swaps
document.addEventListener('DOMContentLoaded', initPage);
document.addEventListener('htmx:afterSwap', initPage);
function initPage() {
initToast();
initCustomSelects();
initFormSubmission();
}
function initToast() {
if (typeof Swal !== 'undefined') {
Toast = Swal.mixin({
toast: true, toast: true,
position: "top-end", position: "top-end",
showConfirmButton: false, showConfirmButton: false,
@ -223,20 +240,69 @@
toast.onmouseleave = Swal.resumeTimer; toast.onmouseleave = Swal.resumeTimer;
} }
}); });
function notify(tag,msg){Toast.fire({icon: tag,titleText: msg});} }
}
function notify(tag, msg) {
if (Toast) {
Toast.fire({icon: tag, titleText: msg});
} else if (typeof Swal !== 'undefined') {
Swal.fire({icon: tag, titleText: msg});
} else {
console.log(`${tag}: ${msg}`);
}
}
function initCustomSelects() {
// Clean up previous listeners if any
if (customSelectsInitialized) {
document.removeEventListener('click', handleDocumentClickForSelects);
}
const customSelects = document.querySelectorAll('.custom-select'); const customSelects = document.querySelectorAll('.custom-select');
if (!customSelects.length) return;
customSelects.forEach(select => { customSelects.forEach(select => {
const trigger = select.querySelector('.select-trigger'); const trigger = select.querySelector('.select-trigger');
const optionsContainer = select.querySelector('.options-container');
const options = select.querySelectorAll('.option'); const options = select.querySelectorAll('.option');
const nativeSelect = select.querySelector('.native-select'); const nativeSelect = select.querySelector('.native-select');
const selectedValue = select.querySelector('.selected-value'); const selectedValue = select.querySelector('.selected-value');
// Toggle dropdown // Clone elements to remove old event listeners
trigger.addEventListener('click', (e) => { if (trigger) trigger.replaceWith(trigger.cloneNode(true));
options.forEach(option => option.replaceWith(option.cloneNode(true)));
// Get fresh references
const newTrigger = select.querySelector('.select-trigger');
const newOptions = select.querySelectorAll('.option');
const newNativeSelect = select.querySelector('.native-select');
const newSelectedValue = select.querySelector('.selected-value');
// Add new listeners
if (newTrigger) {
newTrigger.addEventListener('click', (e) => handleSelectTriggerClick(e, select));
}
newOptions.forEach(option => {
option.addEventListener('click', () => handleOptionClick(option, select));
});
// Initialize with current value
if (newNativeSelect?.value && newSelectedValue) {
initializeSelectedOption(select);
}
});
// Add document click handler
document.addEventListener('click', handleDocumentClickForSelects);
customSelectsInitialized = true;
}
function handleSelectTriggerClick(e, select) {
e.stopPropagation(); e.stopPropagation();
const trigger = select.querySelector('.select-trigger');
// Toggle current select
select.classList.toggle('open'); select.classList.toggle('open');
trigger.classList.toggle('active'); trigger.classList.toggle('active');
@ -244,93 +310,103 @@
document.querySelectorAll('.custom-select').forEach(otherSelect => { document.querySelectorAll('.custom-select').forEach(otherSelect => {
if (otherSelect !== select) { if (otherSelect !== select) {
otherSelect.classList.remove('open'); otherSelect.classList.remove('open');
otherSelect.querySelector('.select-trigger').classList.remove('active'); const otherTrigger = otherSelect.querySelector('.select-trigger');
if (otherTrigger) otherTrigger.classList.remove('active');
} }
}); });
}); }
// Handle option selection function handleOptionClick(option, select) {
options.forEach(option => {
option.addEventListener('click', () => {
const value = option.getAttribute('data-value'); const value = option.getAttribute('data-value');
const image = option.getAttribute('data-image'); const image = option.getAttribute('data-image');
const text = option.querySelector('span').textContent; const text = option.querySelector('span')?.textContent || '';
const nativeSelect = select.querySelector('.native-select');
const selectedValue = select.querySelector('.selected-value');
const options = select.querySelectorAll('.option');
// Update selected display // Update display
if (selectedValue) {
selectedValue.innerHTML = ` selectedValue.innerHTML = `
<img src="${image}" alt="${text}"> <img src="${image}" alt="${text}">
<span>${text}</span> <span>${text}</span>
`; `;
}
// Update native select value // Update native select
if (nativeSelect) {
nativeSelect.value = value; nativeSelect.value = value;
// Trigger change event
const event = new Event('change');
nativeSelect.dispatchEvent(event);
}
// Mark as selected // Update selected state
options.forEach(opt => opt.classList.remove('selected')); options.forEach(opt => opt.classList.remove('selected'));
option.classList.add('selected'); option.classList.add('selected');
// Close dropdown // Close dropdown
select.classList.remove('open'); select.classList.remove('open');
trigger.classList.remove('active'); const trigger = select.querySelector('.select-trigger');
if (trigger) trigger.classList.remove('active');
}
// Trigger change event function handleDocumentClickForSelects() {
const event = new Event('change'); document.querySelectorAll('.custom-select').forEach(select => {
nativeSelect.dispatchEvent(event);
});
});
// Close when clicking outside
document.addEventListener('click', () => {
select.classList.remove('open'); select.classList.remove('open');
trigger.classList.remove('active'); const trigger = select.querySelector('.select-trigger');
if (trigger) trigger.classList.remove('active');
}); });
}
// Initialize with native select value function initializeSelectedOption(select) {
if (nativeSelect.value) { const nativeSelect = select.querySelector('.native-select');
const selectedOption = select.querySelector(`.option[data-value="${nativeSelect.value}"]`); const selectedOption = select.querySelector(`.option[data-value="${nativeSelect.value}"]`);
if (selectedOption) { if (selectedOption) {
selectedOption.click(); handleOptionClick(selectedOption, select);
} }
} }
});
// Form submission function initFormSubmission() {
/*const form = document.getElementById('demo-form'); const form = document.getElementById('mainForm');
form.addEventListener('submit', function(e) { if (!form) return;
// Remove old listener if exists
if (formInitialized) {
form.removeEventListener('submit', handleFormSubmit);
}
form.addEventListener('submit', handleFormSubmit);
formInitialized = true;
}
async function handleFormSubmit(e) {
e.preventDefault(); e.preventDefault();
const formData = new FormData(form); const form = e.target;
alert(`Selected value: ${formData.get('car')}`);
});*/
document.getElementById('mainForm').addEventListener('submit', async function(e) { // Validate title
e.preventDefault(); const titleInput = form.querySelector('[name="title"]');
if (titleInput && titleInput.value.length < 5) {
const titleInput = document.querySelector('[name="title"]');
if (titleInput.value.length < 5) {
notify("error", "Customer Estimate Title must be at least 5 characters long."); notify("error", "Customer Estimate Title must be at least 5 characters long.");
return; // Stop form submission return;
} }
// Collect all form data // Prepare form data
const formData = { const formData = {
csrfmiddlewaretoken: document.querySelector('[name=csrfmiddlewaretoken]').value, csrfmiddlewaretoken: document.querySelector('[name=csrfmiddlewaretoken]')?.value,
title: document.querySelector('[name=title]').value, title: form.querySelector('[name=title]')?.value,
customer: document.querySelector('[name=customer]').value, customer: form.querySelector('[name=customer]')?.value,
item: [], item: [],
quantity: [1], quantity: [1],
opportunity_id: "{{opportunity_id}}" opportunity_id: "{{opportunity_id}}"
}; };
// Collect items
// Collect multi-value fields (e.g., item[], quantity[]) form.querySelectorAll('[name="item"]').forEach(input => {
document.querySelectorAll('[name="item"]').forEach(input => { if (input.value) formData.item.push(input.value);
formData.item.push(input.value);
}); });
console.log(formData);
try { try {
// Send data to the server using fetch // Submit form
const response = await fetch("{% url 'estimate_create' request.dealer.slug %}", { const response = await fetch("{% url 'estimate_create' request.dealer.slug %}", {
method: 'POST', method: 'POST',
headers: { headers: {
@ -340,27 +416,32 @@
body: JSON.stringify(formData) body: JSON.stringify(formData)
}); });
// Parse the JSON response // Handle response
const data = await response.json(); const data = await response.json();
handleFormResponse(data);
} catch (error) {
console.error("Form submission error:", error);
notify("error", "An error occurred while submitting the form");
}
}
function handleFormResponse(data) {
if (!data) {
notify("error", "No response from server");
return;
}
// Handle the response
if (data.status === "error") { if (data.status === "error") {
notify("error", data.message); // Display an error message notify("error", data.message || "An error occurred");
} else if (data.status === "success") { } else if (data.status === "success") {
notify("success","Estimate created successfully"); notify("success", data.message || "Estimate created successfully");
setTimeout(() => { if (data.url) {
window.location.assign(data.url); // Redirect to the provided URL setTimeout(() => window.location.assign(data.url), 1000);
}, 1000); }
} else { } else {
notify("error", "Unexpected response from the server"); notify("error", "Unexpected response from the server");
} }
} catch (error) {
notify("error", error);
} }
});
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -30,31 +30,31 @@
</tr> </tr>
</thead> </thead>
<tbody class="list"> <tbody class="list">
{% for extra in staff_estimates %} {% for estimate in staff_estimates %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static"> <tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle product white-space-nowrap py-0 px-1">{{ extra.content_object.estimate_number }}</td> <td class="align-middle product white-space-nowrap py-0 px-1">{{ estimate.estimate_number }}</td>
<td class="align-middle product white-space-nowrap">{{ extra.content_object.customer.customer_name }}</td> <td class="align-middle product white-space-nowrap">{{ estimate.customer.customer_name }}</td>
<td class="align-middle product white-space-nowrap"> <td class="align-middle product white-space-nowrap">
{% if extra.content_object.status == 'draft' %} {% if estimate.status == 'draft' %}
<span class="badge badge-phoenix badge-phoenix-warning">{% trans "Draft" %}</span> <span class="badge badge-phoenix badge-phoenix-warning">{% trans "Draft" %}</span>
{% elif extra.content_object.status == 'in_review' %} {% elif estimate.status == 'in_review' %}
<span class="badge badge-phoenix badge-phoenix-info">{% trans "In Review" %}</span> <span class="badge badge-phoenix badge-phoenix-info">{% trans "In Review" %}</span>
{% elif extra.content_object.status == 'approved' %} {% elif estimate.status == 'approved' %}
<span class="badge badge-phoenix badge-phoenix-success">{% trans "Approved" %}</span> <span class="badge badge-phoenix badge-phoenix-success">{% trans "Approved" %}</span>
{% elif extra.content_object.status == 'declined' %} {% elif estimate.status == 'declined' %}
<span class="badge badge-phoenix badge-phoenix-danger">{% trans "Declined" %}</span> <span class="badge badge-phoenix badge-phoenix-danger">{% trans "Declined" %}</span>
{% elif extra.content_object.status == 'canceled' %} {% elif estimate.status == 'canceled' %}
<span class="badge badge-phoenix badge-phoenix-danger">{% trans "Canceled" %}</span> <span class="badge badge-phoenix badge-phoenix-danger">{% trans "Canceled" %}</span>
{% elif extra.content_object.status == 'completed' %} {% elif estimate.status == 'completed' %}
<span class="badge badge-phoenix badge-phoenix-success">{% trans "Completed" %}</span> <span class="badge badge-phoenix badge-phoenix-success">{% trans "Completed" %}</span>
{% elif extra.content_object.status == 'void' %} {% elif estimate.status == 'void' %}
<span class="badge badge-phoenix badge-phoenix-secondary">{% trans "Void" %}</span> <span class="badge badge-phoenix badge-phoenix-secondary">{% trans "Void" %}</span>
{% endif %} {% endif %}
</td> </td>
<td class="align-middle product white-space-nowrap">{{ extra.content_object.get_status_action_date }}</td> <td class="align-middle product white-space-nowrap">{{ estimate.get_status_action_date }}</td>
<td class="align-middle product white-space-nowrap">{{ extra.content_object.created }}</td> <td class="align-middle product white-space-nowrap">{{ estimate.created }}</td>
<td class="align-middle product white-space-nowrap"> <td class="align-middle product white-space-nowrap">
<a href="{% url 'estimate_detail' request.dealer.slug extra.content_object.pk %}" <a href="{% url 'estimate_detail' request.dealer.slug estimate.pk %}"
class="btn btn-sm btn-phoenix-success"> class="btn btn-sm btn-phoenix-success">
<i class="fa-regular fa-eye me-1"></i> <i class="fa-regular fa-eye me-1"></i>
{% trans "view"|capfirst %} {% trans "view"|capfirst %}