style offering listing
This commit is contained in:
parent
3efa5326c6
commit
06c532c0ad
2 changed files with 193 additions and 153 deletions
|
@ -1,167 +1,204 @@
|
||||||
{% extends 'services/base.html' %}
|
{% extends 'services/base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<section class="section bg-primary-subtle">
|
||||||
<div class="col-md-3">
|
<div class="container mx-auto px-20 px-lg-0 pt-40 pb-60">
|
||||||
<div class="card">
|
<header class="section-primary__header text-center">
|
||||||
<div class="card-body">
|
<h2 class="section-h1 fs-40 fs-lg-64 mb-24">Service Offerings</h2>
|
||||||
<h5 class="card-title">Filters</h5>
|
<div class="text-gray-300 w-lg-37 mx-auto">
|
||||||
<form method="get">
|
<p class="mb-0">Explore our available service offerings</p>
|
||||||
<div class="mb-3">
|
|
||||||
<label for="search" class="form-label">Search</label>
|
|
||||||
<input type="text" class="form-control" id="search" name="search" value="{{ request.GET.search }}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="cloud_provider" class="form-label">Cloud Provider</label>
|
|
||||||
<select class="form-select" id="cloud_provider" name="cloud_provider">
|
|
||||||
<option value="">All Providers</option>
|
|
||||||
{% for provider in cloud_providers %}
|
|
||||||
<option value="{{ provider.id }}" {% if request.GET.cloud_provider == provider.id|stringformat:'i' %}selected{% endif %}>
|
|
||||||
{{ provider.name }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="category" class="form-label">Category</label>
|
|
||||||
<select class="form-select" id="category" name="category">
|
|
||||||
<option value="">All Categories</option>
|
|
||||||
{% for category in categories %}
|
|
||||||
<option value="{{ category.id }}" {% if request.GET.category == category.id|stringformat:'i' %}selected{% endif %}>
|
|
||||||
{{ category.name }}
|
|
||||||
</option>
|
|
||||||
{% for subcategory in category.children.all %}
|
|
||||||
<option value="{{ subcategory.id }}" {% if request.GET.category == subcategory.id|stringformat:'i' %}selected{% endif %}>
|
|
||||||
{{ subcategory.name }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Apply Filters</button>
|
|
||||||
<a href="{% url 'services:offering_list' %}" class="btn btn-secondary">Clear</a>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
<div class="col-md-9">
|
|
||||||
<div class="row row-cols-1 row-cols-md-2 g-4">
|
<section class="section">
|
||||||
{% for offering in offerings %}
|
<div class="container-xl mx-auto px-3 px-lg-0 pt-60 pt-lg-80 pb-40">
|
||||||
<div class="col">
|
<div x-data="{ open: window.innerWidth > 1024 }"
|
||||||
<div class="card h-100">
|
@resize.window="open = window.innerWidth > 1024 ? open : true" class="d-lg-flex">
|
||||||
<div class="card-body">
|
<!-- Filters -->
|
||||||
<div class="d-flex align-items-start mb-3">
|
<div class="w-lg-20 flex-none">
|
||||||
<div class="me-3">
|
<!-- Mobile Menu -->
|
||||||
{% if offering.service.logo %}
|
<div class="page-action d-lg-none mb-40">
|
||||||
<img src="{{ offering.service.logo.url }}"
|
<button @click="open = !open"
|
||||||
alt="{{ offering.service.name }}"
|
class="btn btn-outline-primary btn-md w-100 d-flex justify-content-center align-items-center"
|
||||||
style="max-height: 50px; max-width: 100px; object-fit: contain;">
|
type="button">
|
||||||
{% endif %}
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<mask id="mask0_115_3182" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0"
|
||||||
|
y="0" width="24" height="24">
|
||||||
|
<rect width="24" height="24" fill="#D9D9D9" />
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_115_3182)">
|
||||||
|
<path d="M7 18V16H17V18H7ZM5 13V11H19V13H5ZM3 8V6H21V8H3Z" fill="#9A63EC" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<span class="ms-2">Filters</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Desktop View -->
|
||||||
|
<div x-cloak x-show="open || window.innerWidth >= 1024" class="w-lg-85" x-collapse>
|
||||||
|
<div class="d-flex d-lg-none justify-content-between align-items-center mb-24"
|
||||||
|
role="button">
|
||||||
|
<h3 class="sidebar-dropdown__title mb-0">Filters</h3>
|
||||||
|
<span @click="open = false">
|
||||||
|
<svg width="24" height="25" viewBox="0 0 24 25" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<mask id="mask0_115_3177" style="mask-type:alpha" maskUnits="userSpaceOnUse"
|
||||||
|
x="0" y="0" width="24" height="25">
|
||||||
|
<rect y="0.5" width="24" height="24" fill="#D9D9D9" />
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_115_3177)">
|
||||||
|
<path
|
||||||
|
d="M6.4 19.5L5 18.1L10.6 12.5L5 6.9L6.4 5.5L12 11.1L17.6 5.5L19 6.9L13.4 12.5L19 18.1L17.6 19.5L12 13.9L6.4 19.5Z"
|
||||||
|
fill="#160037" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-form position-relative mb-24">
|
||||||
|
<form method="get" x-data="{submitForm() { $refs.filterForm.submit(); } }" x-ref="filterForm">
|
||||||
|
<!-- Search -->
|
||||||
|
<div class="mb-24">
|
||||||
|
<label for="search" class="d-none">Search</label>
|
||||||
|
<input type="text" id="search" class="input-search" placeholder="Search" name="search" value="{{ request.GET.search }}">
|
||||||
|
<button class="search-button position-absolute top-0 start-0 d-flex justify-content-center align-items-center border-0 bg-transparent p-0"
|
||||||
|
type="button" title="search">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17.5 17.5L22 22" stroke="#9A63EC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
|
<path d="M20 11C20 6.02944 15.9706 2 11 2C6.02944 2 2 6.02944 2 11C2 15.9706 6.02944 20 11 20C15.9706 20 20 15.9706 20 11Z"
|
||||||
|
stroke="#9A63EC" stroke-width="1.5" stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Service Filter -->
|
||||||
|
<div class="pt-24 mb-24">
|
||||||
|
<div class="d-flex justify-content-between align-items-center h-33 mb-5px" role="button">
|
||||||
|
<h3 class="sidebar-title mb-0">Service</h3>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h5 class="card-title mb-1">
|
<select class="form-select" id="service" name="service" @change="submitForm()">
|
||||||
<a href="{{ offering.service.get_absolute_url }}" class="text-decoration-none">
|
<option value="">All Services</option>
|
||||||
{{ offering.service.name }}
|
{% for service in services %}
|
||||||
</a>
|
<option value="{{ service.id }}" {% if request.GET.service == service.id|stringformat:'i' %}selected{% endif %}>
|
||||||
</h5>
|
{{ service.name }}
|
||||||
<div class="d-flex align-items-center">
|
</option>
|
||||||
{% if offering.cloud_provider.logo %}
|
{% endfor %}
|
||||||
<a href="{{ offering.cloud_provider.get_absolute_url }}" class="me-2">
|
</select>
|
||||||
<img src="{{ offering.cloud_provider.logo.url }}"
|
</div>
|
||||||
alt="{{ offering.cloud_provider.name }}"
|
</div>
|
||||||
style="max-height: 25px; max-width: 50px; object-fit: contain;">
|
|
||||||
</a>
|
<!-- Cloud Provider Filter -->
|
||||||
{% endif %}
|
<div class="pt-24 mb-24">
|
||||||
<small class="text-muted">
|
<div class="d-flex justify-content-between align-items-center h-33 mb-5px" role="button">
|
||||||
{{ offering.cloud_provider.name }}
|
<h3 class="sidebar-title mb-0">Cloud Provider</h3>
|
||||||
</small>
|
</div>
|
||||||
|
<div>
|
||||||
|
<select class="form-select" id="cloud_provider" name="cloud_provider" @change="submitForm()">
|
||||||
|
<option value="">All Providers</option>
|
||||||
|
{% for provider in cloud_providers %}
|
||||||
|
<option value="{{ provider.id }}" {% if request.GET.cloud_provider == provider.id|stringformat:'i' %}selected{% endif %}>
|
||||||
|
{{ provider.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Category Filter -->
|
||||||
|
<div class="pt-24 mb-24">
|
||||||
|
<div class="d-flex justify-content-between align-items-center h-33 mb-5px" role="button">
|
||||||
|
<h3 class="sidebar-title mb-0">Category</h3>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<select class="form-select" id="category" name="category" @change="submitForm()">
|
||||||
|
<option value="">All Categories</option>
|
||||||
|
{% for category in categories %}
|
||||||
|
<option value="{{ category.id }}" {% if request.GET.category == category.id|stringformat:'i' %}selected{% endif %}>
|
||||||
|
{{ category.name }}
|
||||||
|
</option>
|
||||||
|
{% for subcategory in category.children.all %}
|
||||||
|
<option value="{{ subcategory.id }}" {% if request.GET.category == subcategory.id|stringformat:'i' %}selected{% endif %}>
|
||||||
|
{{ subcategory.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filter Actions -->
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a href="{% url 'services:offering_list' %}" class="btn btn-outline-secondary btn-sm">Clear</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Offerings Listing -->
|
||||||
|
<div class="section__grid flex-1">
|
||||||
|
<div class="row">
|
||||||
|
{% for offering in offerings %}
|
||||||
|
<div class="col-12 col-md-6 col-lg-4 mb-30">
|
||||||
|
<div class="card h-100 d-flex flex-column">
|
||||||
|
<div class="card__content d-flex flex-column flex-grow-1">
|
||||||
|
<div class="card__header">
|
||||||
|
<div class="d-flex align-items-start mb-3">
|
||||||
|
<div class="me-3">
|
||||||
|
{% if offering.service.logo %}
|
||||||
|
<img src="{{ offering.service.logo.url }}"
|
||||||
|
alt="{{ offering.service.name }}"
|
||||||
|
style="max-height: 50px; max-width: 100px; object-fit: contain;">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="card__title">
|
||||||
|
<a href="{{ offering.get_absolute_url }}" class="text-decoration-none">
|
||||||
|
{{ offering.service.name }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
{% if offering.cloud_provider.logo %}
|
||||||
|
<a href="{{ offering.get_absolute_url }}" class="me-2">
|
||||||
|
<img src="{{ offering.cloud_provider.logo.url }}"
|
||||||
|
alt="{{ offering.cloud_provider.name }}"
|
||||||
|
style="max-height: 30px; max-width: 100px; object-fit: contain;">
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<small class="text-muted">
|
||||||
|
{{ offering.cloud_provider.name }}
|
||||||
|
</small>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 card__subtitle">
|
||||||
|
{% for category in offering.service.categories.all %}
|
||||||
|
<span>{{ category.full_path }}</span>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-2">
|
<div class="card__desc flex-grow-1 rich-text-content">
|
||||||
{% for category in offering.service.categories.all %}
|
{{ offering.description|safe|truncatewords_html:30 }}
|
||||||
<span class="badge bg-secondary me-1">{{ category.full_path }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rich-text-content mb-3">
|
|
||||||
{{ offering.description|safe|truncatewords_html:30 }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if offering.plans.exists %}
|
|
||||||
<div class="mb-3">
|
|
||||||
<strong>Available Plans:</strong>
|
|
||||||
<ul class="list-unstyled small">
|
|
||||||
{% for plan in offering.plans.all %}
|
|
||||||
<li>• {{ plan.name }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="mt-auto d-flex gap-2">
|
|
||||||
<a href="{{ offering.get_absolute_url }}" class="btn btn-primary">View Details</a>
|
|
||||||
{% if offering.plans.exists %}
|
|
||||||
{% if offering.status == 'available' %}
|
|
||||||
{% if offering.plans.count == 1 %}
|
|
||||||
{% with plan=offering.plans.first %}
|
|
||||||
<a href="{% url 'services:create_lead' offering.service.slug %}?offering={{ offering.id }}&plan={{ plan.id }}"
|
|
||||||
class="btn btn-success">Order</a>
|
|
||||||
{% endwith %}
|
|
||||||
{% else %}
|
|
||||||
<div class="dropdown">
|
|
||||||
<button class="btn btn-success dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
|
||||||
Order
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
{% for plan in offering.plans.all %}
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item"
|
|
||||||
href="{% url 'services:create_lead' offering.service.slug %}?offering={{ offering.id }}&plan={{ plan.id }}">
|
|
||||||
{{ plan.name }}
|
|
||||||
{% if plan.is_default %}(Recommended){% endif %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% elif offering.status == 'planned' %}
|
|
||||||
<a href="{% url 'services:create_lead' offering.service.slug %}?offering={{ offering.id }}"
|
|
||||||
class="btn btn-success">Show Interest</a>
|
|
||||||
{% else %}
|
|
||||||
<a href="{% url 'services:create_lead' offering.service.slug %}?offering={{ offering.id }}"
|
|
||||||
class="btn btn-success">Request Information</a>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{% if offering.status == 'available' %}
|
|
||||||
<a href="{% url 'services:create_lead' offering.service.slug %}?offering={{ offering.id }}"
|
|
||||||
class="btn btn-success">Order</a>
|
|
||||||
{% elif offering.status == 'planned' %}
|
|
||||||
<a href="{% url 'services:create_lead' offering.service.slug %}?offering={{ offering.id }}"
|
|
||||||
class="btn btn-success">Show Interest</a>
|
|
||||||
{% else %}
|
|
||||||
<a href="{% url 'services:create_lead' offering.service.slug %}?offering={{ offering.id }}"
|
|
||||||
class="btn btn-success">Request Information</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% empty %}
|
||||||
{% empty %}
|
<div class="col-12">
|
||||||
<div class="col">
|
<div class="alert alert-info">
|
||||||
<div class="alert alert-info">
|
No service offerings found matching your criteria.
|
||||||
No service offerings found matching your criteria.
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,10 +1,6 @@
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from hub.services.models import (
|
from hub.services.models import ServiceOffering, CloudProvider, Category, Service
|
||||||
ServiceOffering,
|
|
||||||
CloudProvider,
|
|
||||||
Category,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def offering_list(request):
|
def offering_list(request):
|
||||||
|
@ -22,6 +18,7 @@ def offering_list(request):
|
||||||
|
|
||||||
cloud_providers = CloudProvider.objects.all()
|
cloud_providers = CloudProvider.objects.all()
|
||||||
categories = Category.objects.filter(parent=None).prefetch_related("children")
|
categories = Category.objects.filter(parent=None).prefetch_related("children")
|
||||||
|
services = Service.objects.all().order_by("name") # Add this line
|
||||||
|
|
||||||
# Handle cloud provider filter
|
# Handle cloud provider filter
|
||||||
if request.GET.get("cloud_provider"):
|
if request.GET.get("cloud_provider"):
|
||||||
|
@ -37,6 +34,11 @@ def offering_list(request):
|
||||||
Q(service__categories=category) | Q(service__categories__in=subcategories)
|
Q(service__categories=category) | Q(service__categories__in=subcategories)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
|
# Add service filter handling
|
||||||
|
if request.GET.get("service"):
|
||||||
|
service_id = request.GET.get("service")
|
||||||
|
offerings = offerings.filter(service_id=service_id)
|
||||||
|
|
||||||
# Handle search
|
# Handle search
|
||||||
if request.GET.get("search"):
|
if request.GET.get("search"):
|
||||||
query = request.GET.get("search")
|
query = request.GET.get("search")
|
||||||
|
@ -50,6 +52,7 @@ def offering_list(request):
|
||||||
"offerings": offerings,
|
"offerings": offerings,
|
||||||
"cloud_providers": cloud_providers,
|
"cloud_providers": cloud_providers,
|
||||||
"categories": categories,
|
"categories": categories,
|
||||||
|
"services": services,
|
||||||
}
|
}
|
||||||
return render(request, "services/offering_list.html", context)
|
return render(request, "services/offering_list.html", context)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue