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); margin-bottom: var(--space-md);
} }
.empty-state p {
margin-bottom: var(--space-lg);
}
.empty-state .btn {
margin-top: var(--space-md);
}
/* Dropdown */ /* Dropdown */
.dropdown { .dropdown {
position: relative; position: relative;

View File

@@ -53,9 +53,12 @@ class PageListing extends HTMLElement {
return return
} }
const images = this.listing.images || [] const images = (this.listing.images || []).slice(0, 5)
const hasImages = images.length > 0 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 */` const placeholderSvg = /* html */`
<svg class="placeholder-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <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> <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 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 price = this.formatPrice(this.listing.price, this.listing.currency)
const createdDate = this.listing.date_created const createdDate = this.listing.date_created
? new Date(this.listing.date_created).toLocaleDateString() ? new Date(this.listing.date_created).toLocaleDateString()
@@ -74,16 +76,16 @@ class PageListing extends HTMLElement {
this.innerHTML = /* html */` this.innerHTML = /* html */`
<article class="listing-detail"> <article class="listing-detail">
<div class="listing-gallery"> <div class="listing-gallery">
<div class="listing-image-main"> <div class="listing-image-main" id="main-image">
${firstImage ${firstImage
? `<img src="${firstImage}" alt="${this.escapeHtml(this.listing.title)}">` ? `<img src="${firstImage}" alt="${this.escapeHtml(this.listing.title)}" id="main-img">`
: placeholderSvg} : placeholderSvg}
</div> </div>
${images.length > 1 ? ` ${images.length > 1 ? `
<div class="listing-thumbnails"> <div class="listing-thumbnails">
${images.map((img, i) => ` ${images.map((img, i) => `
<button class="thumbnail ${i === 0 ? 'active' : ''}" data-index="${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> </button>
`).join('')} `).join('')}
</div> </div>
@@ -95,7 +97,6 @@ class PageListing extends HTMLElement {
${categoryName ? `<span class="badge badge-primary">${this.escapeHtml(categoryName)}</span>` : ''} ${categoryName ? `<span class="badge badge-primary">${this.escapeHtml(categoryName)}</span>` : ''}
<h1>${this.escapeHtml(this.listing.title)}</h1> <h1>${this.escapeHtml(this.listing.title)}</h1>
<p class="listing-price">${price}</p> <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>` : ''} ${this.listing.condition ? `<p class="listing-condition">${this.getConditionLabel(this.listing.condition)}</p>` : ''}
</header> </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) { getImageUrl(image, size = null) {
@@ -297,6 +314,14 @@ style.textContent = /* css */`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: 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 { page-listing .listing-image-main .placeholder-icon {
@@ -305,6 +330,47 @@ style.textContent = /* css */`
color: var(--color-border); 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 { page-listing .listing-info header {
margin-bottom: var(--space-xl); margin-bottom: var(--space-xl);
} }

View File

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