385 lines
28 KiB
HTML
385 lines
28 KiB
HTML
{% extends 'base.html' %}
|
|
{% load i18n static humanize %}
|
|
{% block title %}{{ _('Leads')|capfirst }}{% endblock title %}
|
|
|
|
{% block content %}
|
|
<div class="row g-3 mt-4">
|
|
<h2 class="mb-2">{{ _("Leads")|capfirst }}</h2>
|
|
<!-- Action Tracking Modal -->
|
|
{% include "crm/leads/partials/update_action.html" %}
|
|
|
|
<div class="row g-3 justify-content-between mb-4">
|
|
<div class="col-auto">
|
|
<div class="d-md-flex justify-content-between">
|
|
{% if perms.inventory.add_lead %}
|
|
<div>
|
|
<a href="{% url 'lead_create' request.dealer.slug %}" class="btn btn-sm btn-phoenix-primary"><span class="fas fa-plus me-2"></span>{{ _("Add Lead") }}</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="d-flex">
|
|
{% include 'partials/search_box.html' %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row g-3">
|
|
<div class="col-12">
|
|
{% if page_obj.object_list %}
|
|
<div class="table-responsive scrollbar mx-n1 px-1">
|
|
<table class="table align-items-center table-flush table-hover">
|
|
<thead>
|
|
<tr class="bg-body-highlight">
|
|
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 20%;">{{ _("Lead Name")|capfirst }}</th>
|
|
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
|
|
<div class="d-inline-flex flex-center">
|
|
<div class="d-flex align-items-center px-1 py-1 bg-success-subtle rounded me-2"><i class="text-success-dark fas fa-car"></i></div>
|
|
<span>{{ _("Car")|capfirst }}</span>
|
|
</div>
|
|
</th>
|
|
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
|
|
<div class="d-inline-flex flex-center">
|
|
<div class="d-flex align-items-center px-1 py-1 bg-success-subtle rounded me-2"><span class="text-success-dark" data-feather="mail"></span></div>
|
|
<span>{{ _("email")|capfirst }}</span>
|
|
</div>
|
|
</th>
|
|
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
|
|
<div class="d-inline-flex flex-center">
|
|
<div class="d-flex align-items-center px-1 py-1 bg-primary-subtle rounded me-2"><span class="text-primary-dark" data-feather="phone"></span></div>
|
|
<div class="" dir="ltr">{{ _("Phone Number") }}</div>
|
|
</div>
|
|
</th>
|
|
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
|
|
<div class="d-inline-flex flex-center">
|
|
<div class="d-flex align-items-center px-1 py-1 bg-primary-subtle rounded me-2"><span class="text-primary-dark" data-feather="phone"></span></div>
|
|
<span>{{ _("Schedule") }}</span>
|
|
</div>
|
|
</th>
|
|
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
|
|
<div class="d-inline-flex flex-center">
|
|
<div class="d-flex align-items-center bg-warning-subtle rounded me-2"><span class="text-warning-dark" data-feather="zap"></span></div>
|
|
<span>{{ _("Action")|capfirst }}</span>
|
|
</div>
|
|
</th>
|
|
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
|
|
<div class="d-inline-flex flex-center">
|
|
<div class="d-flex align-items-center bg-success-subtle rounded me-2"><span class="text-success-dark" data-feather="user-check"></span></div>
|
|
<span>{{ _("Assigned To")|capfirst }}</span>
|
|
</div>
|
|
</th>
|
|
|
|
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
|
|
<div class="d-inline-flex flex-center">
|
|
<div class="d-flex align-items-center bg-warning-subtle rounded me-2"><span class="text-warning-dark" data-feather="grid"></span></div>
|
|
<span>{{ _("Opportunity")|capfirst }}</span>
|
|
</div>
|
|
</th>
|
|
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
|
|
{{ _("Action") }}
|
|
</th>
|
|
<th class="text-end white-space-nowrap align-middle" scope="col"></th>
|
|
</tr>
|
|
{% for lead in leads %}
|
|
<!-- Delete Modal -->
|
|
<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-sm">
|
|
<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-danger">{{ _("Delete")}}<i class="fas fa-exclamation-circle text-danger 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">
|
|
<p>{% trans "Are you sure you want to delete this lead?" %}</p>
|
|
</div>
|
|
<div class="modal-footer flex justify-content-center border-top-0">
|
|
<a type="button" class="btn btn-sm btn-phoenix-danger w-100" href="{% url 'lead_delete' request.dealer.slug lead.slug %}">
|
|
{% trans "Yes" %}
|
|
</a>
|
|
</div>
|
|
|
|
</div>
|
|
</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' request.dealer.slug 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>
|
|
</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>
|
|
{% elif lead.opportunity.stage == "proposal" %}
|
|
<span class="badge text-bg-info">{{ lead.opportunity.stage|upper }}</span>
|
|
{% elif lead.opportunity.stage == "negotiation" %}
|
|
<span class="badge text-bg-warning">{{ lead.opportunity.stage|upper }}</span>
|
|
{% elif lead.opportunity.stage == "closed_won" %}
|
|
<span class="badge text-bg-success">{{ lead.opportunity.stage|upper }}</span>
|
|
{% elif lead.opportunity.stage == "closed_lost" %}
|
|
<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' request.dealer.slug 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' request.dealer.slug lead.slug %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a>
|
|
<a href="{% url 'schedule_lead' request.dealer.slug lead.slug %}" class="dropdown-item text-success-dark">{% trans "Schedule Event" %}</a>
|
|
{% if not lead.opportunity %}
|
|
<a href="{% url 'lead_opportunity_create' request.dealer.slug 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>
|
|
|
|
</table>
|
|
</div>
|
|
{% if page_obj.paginator.num_pages > 1 %}
|
|
|
|
<div class="d-flex justify-content-end mt-3">
|
|
|
|
<div class="d-flex">
|
|
{% include 'partials/pagination.html'%}
|
|
</div>
|
|
|
|
</div>
|
|
{% endif %}
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="6" class="text-center">{% trans "No Lead Yet" %}</td>
|
|
</tr>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{% endblock %}
|
|
|
|
{% block customJS %}
|
|
<script>
|
|
// Initialize SweetAlert Toast for general messages
|
|
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: 2000,
|
|
timerProgressBar: false,
|
|
didOpen: (toast) => {
|
|
toast.onmouseenter = Swal.stopTimer;
|
|
toast.onmouseleave = Swal.resumeTimer;
|
|
}
|
|
});
|
|
|
|
fetch("{% url 'update_lead_actions' request.dealer.slug %}", {
|
|
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 {
|
|
// 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',
|
|
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
|
|
});
|
|
}
|
|
</script>
|
|
|
|
{% endblock customJS %} |