Merge branch 'main' of http://10.10.1.120:3000/tenhal_admin/haikal
This commit is contained in:
commit
2a8e584f30
Binary file not shown.
Binary file not shown.
@ -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'),
|
||||
]
|
||||
|
||||
29
api/views.py
29
api/views.py
@ -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
|
||||
})
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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):
|
||||
@ -4383,4 +4383,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()
|
||||
|
||||
|
||||
|
||||
@ -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" %}
|
||||
|
||||
150
templates/inventory/cars_list_api.html
Normal file
150
templates/inventory/cars_list_api.html
Normal 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 %}
|
||||
@ -18,6 +18,9 @@
|
||||
<p id="result"></p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
let captureBtn = document.getElementById('capture-btn');
|
||||
let cameraContainer = document.getElementById('camera-container');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user