improve listing

This commit is contained in:
2026-01-31 17:28:43 +01:00
parent c1144139b5
commit fd89040c4a
3 changed files with 110 additions and 18 deletions

View File

@@ -394,6 +394,14 @@ app-shell main {
margin-bottom: var(--space-md);
}
.empty-state p {
margin-bottom: var(--space-lg);
}
.empty-state .btn {
margin-top: var(--space-md);
}
/* Dropdown */
.dropdown {
position: relative;

View File

@@ -53,9 +53,12 @@ class PageListing extends HTMLElement {
return
}
const images = this.listing.images || []
const images = (this.listing.images || []).slice(0, 5)
const hasImages = images.length > 0
const firstImage = hasImages ? this.getImageUrl(images[0]) : null
const firstImage = hasImages ? this.getImageUrl(images[0], 800) : null
this.currentImageIndex = 0
this.allImages = images
const placeholderSvg = /* html */`
<svg class="placeholder-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
@@ -65,7 +68,6 @@ class PageListing extends HTMLElement {
`
const categoryName = this.listing.category?.name || this.listing.category?.slug || ''
const locationName = this.listing.location?.name || ''
const price = this.formatPrice(this.listing.price, this.listing.currency)
const createdDate = this.listing.date_created
? new Date(this.listing.date_created).toLocaleDateString()
@@ -74,16 +76,16 @@ class PageListing extends HTMLElement {
this.innerHTML = /* html */`
<article class="listing-detail">
<div class="listing-gallery">
<div class="listing-image-main">
<div class="listing-image-main" id="main-image">
${firstImage
? `<img src="${firstImage}" alt="${this.escapeHtml(this.listing.title)}">`
? `<img src="${firstImage}" alt="${this.escapeHtml(this.listing.title)}" id="main-img">`
: placeholderSvg}
</div>
${images.length > 1 ? `
<div class="listing-thumbnails">
${images.map((img, i) => `
<button class="thumbnail ${i === 0 ? 'active' : ''}" data-index="${i}">
<img src="${this.getImageUrl(img, 100)}" alt="">
<img src="${this.getImageUrl(img, 150)}" alt="">
</button>
`).join('')}
</div>
@@ -95,7 +97,6 @@ class PageListing extends HTMLElement {
${categoryName ? `<span class="badge badge-primary">${this.escapeHtml(categoryName)}</span>` : ''}
<h1>${this.escapeHtml(this.listing.title)}</h1>
<p class="listing-price">${price}</p>
${locationName ? `<p class="listing-location">📍 ${this.escapeHtml(locationName)}</p>` : ''}
${this.listing.condition ? `<p class="listing-condition">${this.getConditionLabel(this.listing.condition)}</p>` : ''}
</header>
@@ -220,6 +221,22 @@ class PageListing extends HTMLElement {
})
})
})
// Thumbnail gallery
const thumbnails = this.querySelectorAll('.thumbnail')
const mainImg = this.querySelector('#main-img')
thumbnails.forEach(thumb => {
thumb.addEventListener('click', () => {
const index = parseInt(thumb.dataset.index)
if (mainImg && this.allImages[index]) {
mainImg.src = this.getImageUrl(this.allImages[index], 800)
thumbnails.forEach(t => t.classList.remove('active'))
thumb.classList.add('active')
this.currentImageIndex = index
}
})
})
}
getImageUrl(image, size = null) {
@@ -297,6 +314,14 @@ style.textContent = /* css */`
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
page-listing .listing-image-main img {
width: 100%;
height: 100%;
object-fit: contain;
background: var(--color-bg-tertiary);
}
page-listing .listing-image-main .placeholder-icon {
@@ -305,6 +330,47 @@ style.textContent = /* css */`
color: var(--color-border);
}
page-listing .listing-thumbnails {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: var(--space-xs);
padding: var(--space-xs);
background: var(--color-bg-secondary);
}
@media (max-width: 480px) {
page-listing .listing-thumbnails {
grid-template-columns: repeat(5, 1fr);
gap: 4px;
padding: 4px;
}
}
page-listing .thumbnail {
aspect-ratio: 1;
border: 2px solid transparent;
border-radius: var(--radius-md);
overflow: hidden;
cursor: pointer;
transition: border-color var(--transition-fast), opacity var(--transition-fast);
padding: 0;
background: var(--color-bg-tertiary);
}
page-listing .thumbnail:hover {
opacity: 0.8;
}
page-listing .thumbnail.active {
border-color: var(--color-primary);
}
page-listing .thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
page-listing .listing-info header {
margin-bottom: var(--space-xl);
}

View File

@@ -253,16 +253,19 @@ class DirectusService {
async getListings(options = {}) {
const params = {
fields: options.fields || [
'*',
'id',
'status',
'title',
'slug',
'price',
'currency',
'condition',
'date_created',
'images.directus_files_id.id',
'images.directus_files_id.title',
'category.id',
'category.name',
'category.slug',
'category.icon',
'location.id',
'location.name',
'location.region'
'category.icon'
],
filter: options.filter || { status: { _eq: 'published' } },
sort: options.sort || ['-date_created'],
@@ -284,11 +287,26 @@ class DirectusService {
async getListing(id) {
const response = await this.get(`/items/listings/${id}`, {
fields: [
'*',
'images.directus_files_id.*',
'category.*',
'category.translations.*',
'location.*'
'id',
'status',
'title',
'slug',
'description',
'price',
'currency',
'price_mode',
'price_type',
'condition',
'shipping',
'shipping_cost',
'views',
'expires_at',
'monero_address',
'date_created',
'images.directus_files_id.id',
'category.id',
'category.name',
'category.slug'
]
})
return response.data