This commit is contained in:
Marwan Alwali 2025-02-17 15:06:46 +03:00
parent 692ee44504
commit 96e3d3af01
13 changed files with 211 additions and 24 deletions

Binary file not shown.

View File

@ -18,5 +18,6 @@ router.register(r'car-option-values', views.CarOptionValueViewSet)
urlpatterns = [
path('', include(router.urls)),
path('cars/vin/', views.CarVINViewSet.as_view(), name='car_vin'),
path("cars/", views.car_list, name="car-list"),
path('login/', views.LoginView.as_view(), name='login'),
]

View File

@ -1,10 +1,14 @@
from django.core.paginator import Paginator
from django.db.models import Q
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework import permissions, status, viewsets, generics
from rest_framework.views import APIView
from rest_framework.response import Response
from django.contrib.auth import authenticate
from django.shortcuts import render
from inventory.utils import get_user_type
from . import models, serializers
from .services import get_car_data, get_from_cardatabase
from rest_framework.authtoken.models import Token
@ -96,3 +100,28 @@ class CarOptionViewSet(viewsets.ModelViewSet):
class CarOptionValueViewSet(viewsets.ModelViewSet):
queryset = inventory_models.CarOptionValue.objects.all()
serializer_class = serializers.CarOptionValueSerializer
def car_list(request):
dealer = get_user_type(request)
page = request.GET.get("page", 1)
per_page = 10
cars = inventory_models.Car.objects.filter(dealer=dealer).values(
"vin",
"id_car_make__name",
"id_car_model__name",
"status"
)
paginator = Paginator(cars, per_page)
page_obj = paginator.get_page(page)
return JsonResponse({
"data": list(page_obj), # Convert QuerySet to list
"page": page_obj.number, # Current page number
"per_page": per_page,
"total_pages": paginator.num_pages, # Total pages
"total_items": paginator.count # Total records
})

View File

@ -328,7 +328,7 @@ class HomeView(TemplateView):
return context
class TestView(TemplateView):
template_name = "test.html"
template_name = "inventory/cars_list_api.html"
class AccountingDashboard(LoginRequiredMixin, TemplateView):
@ -4384,4 +4384,6 @@ def apply_search_filters(queryset, query):
for field in model._meta.get_fields():
if hasattr(field, 'attname') and field.get_internal_type() in ["CharField", "TextField", "EmailField"]:
search_filters |= Q(**{f"{field.name}__icontains": query})
return queryset.filter(search_filters).distinct()
return queryset.filter(search_filters).distinct()

View File

@ -29,6 +29,7 @@
<div id="projectSummary">
<div class="row g-3 justify-content-between align-items-end mb-4">
<div class="col-12 col-sm-auto">
<ul class="nav nav-links mx-n2" hx-boost="true" hx-push-url='false' hx-target=".table-responsive" hx-select=".table-responsive" hx-swap="innerHTML show:window:top" hx-indicator=".htmx-indicator"
hx-on::before-request="on_before_request()"
hx-on::after-request="on_after_request()"
@ -104,39 +105,40 @@
<table class="table table-sm fs-9 mb-0 border-translucent">
<thead>
<tr>
<th class="sort white-space-nowrap align-middle ps-0" scope="col" data-sort="projectName" style="width:10%;">
{{ _("Make") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="assignees" style="width:10%;">{{ _("Model") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="start" style="width:10%;">{{ _("Year") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="deadline" style="width:15%;">{{ _("Trim") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:12%;">{{ _("VIN") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:12%;">{{ _("Age") }}</th>
<th class="sort align-middle text-end" scope="col" data-sort="statuses" style="width:10%;">{{ _("Status") }}</th>
<th class="sort align-middle text-end" scope="col" style="width:10%;"></th>
<th class="align-middle ps-1" scope="col">{{ _("VIN") }}</th>
<th class="align-middle" scope="col">{{ _("Make") }}</th>
<th class="align-middle" scope="col">{{ _("Model") }}</th>
<th class="align-middle" scope="col">{{ _("Year") }}</th>
<th class="align-middle" scope="col">{{ _("Trim") }}</th>
<th class="align-middle" scope="col">{{ _("Age") }}</th>
<th class="align-middle" scope="col">{{ _("Status") }}</th>
<th class="align-middle" scope="col"></th>
</tr>
</thead>
<tbody class="list" id="project-list-table-body">
{% for car in page_obj %}
<tr class="position-static">
<td class="align-middle time white-space-nowrap ps-0 projectName"><a class="fw-bold fs-8" href="{% url 'car_detail' car.pk %}">{{car.id_car_make.get_local_name|default:car.id_car_make.name}}</a></td>
<td class="align-middle white-space-nowrap start">
<p class="mb-0 fs-9 text-body">{{car.id_car_model.get_local_name|default:car.id_car_model.name}}</p>
<td class="align-middle white-space-nowrap ps-1">
<a class="fw-bold" href="{% url 'car_detail' car.pk %}">{{car.vin}}</a>
</td>
<td class="align-middle white-space-nowrap deadline">
<p class="mb-0 fs-9 text-body">{{car.year}}</p>
<td class="align-middle white-space-nowrap">
<p class="text-body mb-0">{{car.id_car_make.get_local_name|default:car.id_car_make.name}}</p>
</td>
<td class="align-middle white-space-nowrap task">
<p class="fw-bo text-body fs-9 mb-0">{{car.id_car_trim }}</p>
<td class="align-middle white-space-nowrap">
<p class="text-body mb-0">{{car.id_car_model.get_local_name|default:car.id_car_model.name}}</p>
</td>
<td class="align-middle white-space-nowrap task">
<p class="fw-bo text-body fs-9 mb-0">{{car.vin}}</p>
<td class="align-middle white-space-nowrap">
<p class="text-body mb-0">{{car.year}}</p>
</td>
<td class="align-middle white-space-nowrap task">
<p class="fw-bo text-body fs-9 mb-0">{{car.receiving_date|timesince}}</p>
<td class="align-middle white-space-nowrap">
<p class="fw-bold text-body mb-0">{{car.id_car_trim }}</p>
</td>
<td class="align-middle white-space-nowrap">
<p class="fw-bold text-body mb-0">{{car.receiving_date|timesince}}</p>
</td>
<td class="align-middle white-space-nowrap text-end statuses">
<td class="align-middle white-space-nowrap statuses">
{% if car.status == "available" %}
<span class="badge badge-phoenix fs-11 badge-phoenix-success">{{car.status}}</span>
{% elif car.status == "reserved" %}

View File

@ -0,0 +1,150 @@
{% extends "base.html" %}
{% load static i18n %}
{% block content %}
<div class="container">
<div id="car_list" data-list="">
<div class="search-box mb-3 mx-auto">
<form class="position-relative">
<input class="form-control search-input search form-control-sm" type="search" placeholder="Search" aria-label="Search" />
<span class="fas fa-search search-box-icon"></span>
</form>
</div>
<div class="row justify-content-end g-0">
<div class="col-auto px-3">
<select class="form-select form-select-sm mb-3" data-list-filter="data-list-filter">
<option selected="" value="">{{ _("Status") }}</option>
<option value="available">{{ _("Available") }}</option>
<option value="reserved">{{ _("Reserved") }}</option>
<option value="sold">{{ _("Sold") }}</option>
<option value="transfer">{{ _("Transfer") }}</option>
<option value="damaged">{{ _("Damaged") }}</option>
</select>
</div>
</div>
<div class="table-responsive">
<table class="table table-sm fs-9 mb-0">
<thead>
<tr class="bg-body-highlight">
<th class="sort border-top border-translucent ps-1" data-sort="vin">{{ _("VIN") }}</th>
<th class="sort border-top border-translucent" data-sort="make">{{ _("Make") }}</th>
<th class="sort border-top border-translucent" data-sort="model">{{ _("Model") }}</th>
<th class="sort border-top border-translucent" data-sort="status">{{ _("Status") }}</th>
</tr>
</thead>
<tbody class="list">
<tr></tr>
</tbody>
</table>
</div>
<div class="d-flex justify-content-between mt-3">
<span class="d-none d-sm-inline-block fs-9 fw-bold" data-list-info="data-list-info"><span class="text-body-tertiary fs-9 fw-bold"></span></span>
<div class="d-flex">
<button class="page-link disabled" data-list-pagination="prev" disabled=""><span class="fas fa-chevron-left"></span></button>
<ul class="mb-0 pagination"><li class="active"><button class="page" type="button" data-i="1" data-page="10">1</button></li><li><button class="page" type="button" data-i="2" data-page="5">2</button></li></ul>
<button class="page-link pe-0" data-list-pagination="next"><span class="fas fa-chevron-right"></span></button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
let currentPage = 1;
function loadPage(page) {
fetch(`/api/cars/?page=${page}`)
.then(response => response.json())
.then(data => {
let tbody = document.querySelector("#car_list .list");
tbody.innerHTML = ""; // Clear table content
data.data.forEach(car => {
let row = document.createElement("tr");
row.innerHTML = `
<td class="align-middle ps-1 vin">${car.vin || "N/A"}</td>
<td class="align-middle make">${car.id_car_make__name}</td>
<td class="align-middle model">${car.id_car_model__name}</td>
<td class="align-middle status text-end py-3 pe-3">
<div class="badge badge-phoenix fs-10 badge-phoenix-${getBadgeClass(car.status)}">
<span class="fw-bold">${car.status}</span>
</div>
</td>
`;
tbody.appendChild(row);
});
updatePagination(data.page, data.total_pages, data.total_items, data.per_page);
})
.catch(error => console.error("Error loading car data:", error));
}
function updatePagination(currentPage, totalPages, totalItems, per_page) {
let pagination = document.querySelector(".pagination");
let paginationInfo = document.querySelector("[data-list-info]");
let prevButton = document.querySelector("[data-list-pagination='prev']");
let nextButton = document.querySelector("[data-list-pagination='next']");
// Update info text
let startItem = (currentPage - 1) * per_page + 1;
let endItem = Math.min(currentPage * per_page, totalItems);
paginationInfo.innerHTML = `${startItem} to ${endItem} <span class="text-body-tertiary fs-9 fw-bold"> Items of </span>${totalItems}`;
// Clear previous pagination
pagination.innerHTML = "";
// Generate page buttons dynamically
for (let i = 1; i <= totalPages; i++) {
let li = document.createElement("li");
li.className = currentPage === i ? "active" : "";
li.innerHTML = `<button class="page" type="button" data-i="${i}" data-page="${per_page}">${i}</button>`;
li.querySelector("button").addEventListener("click", () => {
currentPage = i;
loadPage(currentPage);
});
pagination.appendChild(li);
}
// Update previous & next buttons
prevButton.disabled = currentPage === 1;
nextButton.disabled = currentPage === totalPages;
prevButton.onclick = () => {
if (currentPage > 1) {
currentPage--;
loadPage(currentPage);
}
};
nextButton.onclick = () => {
if (currentPage < totalPages) {
currentPage++;
loadPage(currentPage);
}
};
}
function getBadgeClass(status) {
switch (status.toLowerCase()) {
case "available":
return "success";
case "sold":
return "primary";
case "reserved":
return "danger";
case "transfer":
return "info";
case "damaged":
return "secondary";
default:
return "secondary";
}
}
// Load the first page when the document is ready
loadPage(currentPage);
});
</script>
{% endblock %}

View File

@ -18,6 +18,9 @@
<p id="result"></p>
</div>
<script>
let captureBtn = document.getElementById('capture-btn');
let cameraContainer = document.getElementById('camera-container');