feat: cart detail

This commit is contained in:
2024-12-05 13:33:17 +01:00
parent 03f37d304a
commit 5d04a33130
8 changed files with 201 additions and 31 deletions
+142
View File
@@ -0,0 +1,142 @@
{% load i18n %}
<section class="bg-white py-4 antialiased dark:bg-gray-900 md:py-16">
<div class="mx-auto max-w-screen-xl px-4 2xl:px-0">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">{% translate 'Carrito' %}</h2>
<div class="mt-4 sm:mt-6 md:gap-6 lg:flex lg:items-start xl:gap-8">
<div class="mx-auto w-full flex-none lg:max-w-2xl xl:max-w-4xl">
<div class="space-y-6">
{% for item in items %}
<div
class="rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800 md:p-6">
<div class="space-y-4 md:flex md:items-center md:justify-between md:gap-6 md:space-y-0">
<a href="#" class="shrink-0 md:order-1">
<img class="h-20 w-20 dark:hidden" src="{{ item.product.images.all|first }}"
alt="{{ item.product.name }}"/>
<img class="hidden h-20 w-20 dark:block" src="{{ item.product.images.all|first }}"
alt="{{ item.product.name }}"/>
</a>
<label for="counter-input" class="sr-only">{% translate 'Cantidad' %}</label>
<div class="flex items-center justify-between md:order-3 md:justify-end">
<div class="flex items-center">
<span
class="w-10 shrink-0 border-0 bg-transparent text-center text-sm font-medium text-gray-900 focus:outline-none focus:ring-0 dark:text-white">
{{ item.quantity }}x
</span>
</div>
<div class="text-end md:order-4 md:w-32">
<p class="text-base font-bold text-gray-900 dark:text-white">{{ item.product.price.price_with_tax }}
</p>
</div>
</div>
<div class="w-full min-w-0 flex-1 space-y-4 md:order-2 md:max-w-md">
<a href="#"
class="text-base font-medium text-gray-900 hover:underline dark:text-white">{{ item.product.name }}</a>
<div class="flex items-center gap-4">
<button type="button"
class="inline-flex items-center text-sm font-medium text-gray-500 hover:text-gray-900 hover:underline dark:text-gray-400 dark:hover:text-white">
{% include 'components/icons/like.svg' %}
{% translate 'Añadir a favoritos' %}
</button>
<form class="flex" hx-post="{% url 'web:delete_cart_item' pk=item.pk %}"
hx-swap="none">
{% csrf_token %}
<button
type="submit"
class="inline-flex items-center text-sm font-medium text-red-600 hover:underline dark:text-red-500"
>
{% include 'components/icons/x.svg' %}
{% translate 'Quitar' %}
</button>
</form>
</div>
</div>
</div>
</div>
{% endfor %}
{% if items.count == 0 %}
<span class="text-base font-normal text-gray-500 dark:text-gray-400">
{% translate 'No hay nada en el carrito...' %}
</span>
{% endif %}
</div>
</div>
<div class="mx-auto mt-6 max-w-4xl flex-1 space-y-6 lg:mt-0 lg:w-full">
<div
class="space-y-4 rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800 sm:p-6">
<p class="text-xl font-semibold text-gray-900 dark:text-white">{% translate 'Resumen del pedido' %}</p>
<div class="space-y-4">
<div class="space-y-2">
<dl class="flex items-center justify-between gap-4">
<dt class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Base imponible' %}</dt>
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ base_total }}</dd>
</dl>
<dl class="flex items-center justify-between gap-4">
<dt class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Descuentos' %}</dt>
<dd class="text-base font-medium text-green-600">-{{ savings }}</dd>
</dl>
<dl class="flex items-center justify-between gap-4">
<dt
class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Gastos de envío' %}</dt>
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ shipping_cost }} €</dd>
</dl>
<dl class="flex items-center justify-between gap-4">
<dt class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Impuestos' %}</dt>
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ tax_total }}</dd>
</dl>
</div>
<dl class="flex items-center justify-between gap-4 border-t border-gray-200 pt-2 dark:border-gray-700">
<dt class="text-base font-bold text-gray-900 dark:text-white">{% translate 'Total' %}</dt>
<dd class="text-base font-bold text-gray-900 dark:text-white">{{ total }}</dd>
</dl>
</div>
<form action="">
<button
class="flex w-full items-center justify-center rounded-lg bg-primary-700 px-5 py-2.5 text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
{% translate 'Ir a pagar' %}
</button>
</form>
<div class="flex items-center justify-center gap-2">
<span class="text-sm font-normal text-gray-500 dark:text-gray-400"> {% translate 'o' %} </span>
<a href="{% url 'web:index' %}" title=""
class="inline-flex items-center gap-2 text-sm font-medium text-primary-700 underline hover:no-underline dark:text-primary-500">
{% translate 'Seguir comprando' %}
</a>
</div>
</div>
{# <div#}
{# class="space-y-4 rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800 sm:p-6">#}
{# <form class="space-y-4">#}
{# <div>#}
{# <label for="voucher" class="mb-2 block text-sm font-medium text-gray-900 dark:text-white"> Do you have a#}
{# voucher or gift card? </label>#}
{# <input type="text" id="voucher"#}
{# class="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-400 dark:focus:border-primary-500 dark:focus:ring-primary-500"#}
{# placeholder="" required/>#}
{# </div>#}
{# <button type="submit"#}
{# class="flex w-full items-center justify-center rounded-lg bg-primary-700 px-5 py-2.5 text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">#}
{# Apply Code#}
{# </button>#}
{# </form>#}
{# </div>#}
</div>
</div>
</div>
</section>
@@ -70,8 +70,8 @@
{% if cart.items.count > 0 %}
<a href="{% url 'web:cart_detail' %}"
class="mb-2 me-2 inline-flex w-full items-center justify-center rounded-lg bg-gray-700 px-5 py-2.5 text-sm
font-medium text-white hover:bg-gray-800 focus:outline-none focus:ring-4 focus:ring-gray-300
class="mb-2 me-2 inline-flex w-full items-center justify-center rounded-lg bg-gray-200 px-5 py-2.5 text-sm
font-medium dark:text-white hover:bg-gray-300 focus:outline-none focus:ring-4 focus:ring-gray-300
dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800"
role="button"> {% translate 'Ver carrito' %}
</a>
+5
View File
@@ -0,0 +1,5 @@
<svg class="me-1.5 h-5 w-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"
viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18 17.94 6M18 18 6.06 6"/>
</svg>

After

Width:  |  Height:  |  Size: 288 B

+1 -1
View File
@@ -5,7 +5,7 @@
<section class="bg-gray-50 dark:bg-gray-900">
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<a href="#" class="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white">
<a href="{% url 'web:index' %}" class="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white">
<img class="w-8 h-8 mr-2" src="{% get_logo_image %}" alt="logo">
{{ title }}
</a>
+1 -22
View File
@@ -3,26 +3,5 @@
{% block main %}
{% include 'components/generic/navbar.html' %}
<section class="p-16 mx-auto justify-between mb-4 grid gap-4 sm:grid-cols-1 md:mb-8 lg:grid-cols-2 xl:grid-cols-2">
<section class="p-2 border border-gray-100 dark:border-gray-600 rounded-l">
<ul>
{% for item in items %}
<li class="p-4">
<a href="{% url 'web:product_detail' pk=item.product.pk slug=item.product.slug %}"
class="text-lg font-semibold leading-tight text-gray-900 hover:underline dark:text-white">
<img class="mx-auto h-24 w-auto dark:hidden" src="{{ item.product.images.all|first }}"
alt="{{ product.name }}"/>
<img class="mx-auto hidden h-24 w-auto dark:block" src="{{ item.product.images.all|first }}"
alt="{{ product.name }}"/>
{{ item.product.name }}
</a>
</li>
{% endfor %}
</ul>
</section>
<section class="p-2 border border-gray-100 dark:border-gray-600 rounded-l">
</section>
</section>
<section hx-get="{% url 'web:cart' %}" hx-trigger="load, updated-cart"></section>
{% endblock %}
+2 -2
View File
@@ -1,6 +1,6 @@
from django.urls import path
from web.views.components import cart_dropdown, list_products
from web.views.components import cart, cart_dropdown, list_products
from web.views.users import login, logout, register, reset_password
from web.views.web import (
add_cart_item,
@@ -12,7 +12,6 @@ from web.views.web import (
app_name = "web"
urlpatterns = [
path("", index, name="index"),
path("products/<int:pk>/<str:slug>/", product_detail, name="product_detail"),
@@ -25,6 +24,7 @@ urlpatterns = [
# components
path("web/components/products/", list_products, name="list_products"),
path("web/components/cart-dropdown/", cart_dropdown, name="cart_dropdown"),
path("web/components/cart/", cart, name="cart"),
# api
path("web/add-cart-item/", add_cart_item, name="add_cart_item"),
path("web/delete-cart-item/<int:pk>/", delete_cart_item, name="delete_cart_item"),
+11 -1
View File
@@ -1,4 +1,8 @@
from shop.models import Cart
from decimal import Decimal
from django.db.models import Sum
from shop.models import Cart, CartItem
from web.settings import ANONYMOUS_CART_ID_COOKIE_NAME
@@ -16,3 +20,9 @@ def get_or_create_cart(request) -> [Cart, bool]:
cart, created = Cart.objects.get_or_create(user=request.user)
return cart, created
def get_cart_total(cart: Cart):
return CartItem.objects.filter(cart=cart).annotate(cart_total=Sum("total")).get(
"cart_total"
) or Decimal("0.00")
+36 -2
View File
@@ -1,11 +1,14 @@
from decimal import Decimal
from django.db.models import Sum
from django.shortcuts import render
from django.views.generic import TemplateView
from shop.filters import ProductFilter
from shop.models import Product
from shop.models import CartItem, Product, ProductPrice
from web.mixins import FilteredQuerysetMixin, PaginatedQuerysetMixin
from web.settings import ANONYMOUS_CART_ID_COOKIE_NAME
from web.utils import get_or_create_cart
from web.utils import get_cart_total, get_or_create_cart
class ListProducts(TemplateView, FilteredQuerysetMixin, PaginatedQuerysetMixin):
@@ -42,4 +45,35 @@ def cart_dropdown(request, *args, **kwargs):
return response
def cart(request, *args, **kwargs):
cart, created = get_or_create_cart(request)
items = CartItem.objects.filter(cart=cart)
base_total = Decimal("0.00")
tax_total = Decimal("0.00")
for item in items:
price = ProductPrice.objects.filter(product=item.product, current=True).first()
base_total += price.price * item.quantity
tax_total += round(price.price * Decimal(price.tax.value / 100), 2)
total = base_total + tax_total
response = render(
request,
"components/cart/cart.html",
context={
"cart": cart,
"items": items,
"total": total,
"base_total": base_total,
"tax_total": tax_total,
},
)
if not request.user.is_authenticated:
response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid)
return response
list_products = ListProducts.as_view()