short and long description

This commit is contained in:
Priec
2026-06-23 11:13:26 +02:00
parent 031f86adb0
commit 1cf330e4e8
10 changed files with 49 additions and 3 deletions

View File

@@ -171,6 +171,8 @@ artist = Artist
release-date = Release date release-date = Release date
cover-image = Cover image cover-image = Cover image
description = Description description = Description
short-description = Short description
short-description-hint = Shown on product cards (max 200 characters).
songs-in-album = Songs in this album songs-in-album = Songs in this album
admin-new-album-desc = Fill in the details, then tick the songs to include. admin-new-album-desc = Fill in the details, then tick the songs to include.
cover-help = Optional - png, jpg, webp or gif; shown on the album page. cover-help = Optional - png, jpg, webp or gif; shown on the album page.

View File

@@ -171,6 +171,8 @@ artist = Interpret
release-date = Dátum vydania release-date = Dátum vydania
cover-image = Obrázok obalu cover-image = Obrázok obalu
description = Popis description = Popis
short-description = Krátky popis
short-description-hint = Zobrazuje sa na kartách produktov (max 200 znakov).
songs-in-album = Skladby v albume songs-in-album = Skladby v albume
admin-new-album-desc = Vyplň údaje a potom označ skladby, ktoré chceš zahrnúť. admin-new-album-desc = Vyplň údaje a potom označ skladby, ktoré chceš zahrnúť.
cover-help = Voliteľné - png, jpg, webp alebo gif; zobrazí sa na stránke albumu. cover-help = Voliteľné - png, jpg, webp alebo gif; zobrazí sa na stránke albumu.

File diff suppressed because one or more lines are too long

View File

@@ -18,9 +18,9 @@
{{ ui::csrf_field() }} {{ ui::csrf_field() }}
{% if product %} {% if product %}
{% set v_name = product.name %}{% set v_currency = product.currency %}{% set v_desc = product.description | default(value="") %}{% set v_pub = product.published %} {% set v_name = product.name %}{% set v_currency = product.currency %}{% set v_desc = product.description | default(value="") %}{% set v_short = product.short_description | default(value="") %}{% set v_pub = product.published %}
{% else %} {% else %}
{% set v_name = "" %}{% set v_currency = "EUR" %}{% set v_desc = "" %}{% set v_pub = false %} {% set v_name = "" %}{% set v_currency = "EUR" %}{% set v_desc = "" %}{% set v_short = "" %}{% set v_pub = false %}
{% endif %} {% endif %}
{% set inp = "w-full rounded-radius border border-outline bg-surface-alt px-3 py-2 text-sm text-on-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:focus-visible:outline-primary-dark" %} {% set inp = "w-full rounded-radius border border-outline bg-surface-alt px-3 py-2 text-sm text-on-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:focus-visible:outline-primary-dark" %}
{% set sublabel = "text-xs font-medium text-on-surface/70 dark:text-on-surface-dark/70" %} {% set sublabel = "text-xs font-medium text-on-surface/70 dark:text-on-surface-dark/70" %}
@@ -116,6 +116,12 @@
</div> </div>
</div> </div>
<div class="space-y-1.5">
<label for="short_description" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="short-description", lang=lang | default(value='sk')) }}</label>
<p class="{{ sublabel }}">{{ t(key="short-description-hint", lang=lang | default(value='sk')) }}</p>
{{ ui::textarea(name="short_description", id="short_description", rows="2", value=v_short, attrs='maxlength="200"') }}
</div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<label for="description" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="description", lang=lang | default(value='sk')) }}</label> <label for="description" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="description", lang=lang | default(value='sk')) }}</label>
{{ ui::textarea(name="description", id="description", rows="5", value=v_desc) }} {{ ui::textarea(name="description", id="description", rows="5", value=v_desc) }}

View File

@@ -27,6 +27,13 @@
:class="view === 'list' ? 'p-4 sm:p-5' : 'p-6 pb-2'"> :class="view === 'list' ? 'p-4 sm:p-5' : 'p-6 pb-2'">
<!-- Header: Title & Price (stacked so neither overflows the narrow card) --> <!-- Header: Title & Price (stacked so neither overflows the narrow card) -->
<h3 class="break-words text-lg font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ product.name }}</h3> <h3 class="break-words text-lg font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ product.name }}</h3>
{# Short blurb for the card; falls back to the full description (clamped)
for products without a dedicated short one. Overflow is truncated with an
ellipsis: 2 lines in the grid, 3 in the roomier list row. #}
{% if product.short_description or product.description %}
<p class="line-clamp-2 break-words text-sm text-on-surface/70 dark:text-on-surface-dark/70"
:class="view === 'list' && 'line-clamp-3'">{% if product.short_description %}{{ product.short_description }}{% else %}{{ product.description }}{% endif %}</p>
{% endif %}
{% if product.on_sale %} {% if product.on_sale %}
<div class="flex flex-wrap items-baseline gap-x-2 leading-tight"> <div class="flex flex-wrap items-baseline gap-x-2 leading-tight">
<span class="text-xl font-semibold text-danger"><span class="sr-only">Price</span>{% if product.has_options %}{{ t(key="from-price", price=product.price, lang=lang | default(value='sk')) }}{% else %}{{ product.price }}{% endif %} {{ product.currency }}</span> <span class="text-xl font-semibold text-danger"><span class="sr-only">Price</span>{% if product.has_options %}{{ t(key="from-price", price=product.price, lang=lang | default(value='sk')) }}{% else %}{{ product.price }}{% endif %} {{ product.currency }}</span>

View File

@@ -45,6 +45,7 @@ mod m20260622_000003_variant_stock_nullable;
mod m20260622_000004_product_search; mod m20260622_000004_product_search;
mod m20260622_000005_product_search_aggregate; mod m20260622_000005_product_search_aggregate;
mod m20260622_000006_order_search_indexes; mod m20260622_000006_order_search_indexes;
mod m20260623_000001_add_short_description_to_products;
pub struct Migrator; pub struct Migrator;
#[async_trait::async_trait] #[async_trait::async_trait]
@@ -94,6 +95,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260622_000004_product_search::Migration), Box::new(m20260622_000004_product_search::Migration),
Box::new(m20260622_000005_product_search_aggregate::Migration), Box::new(m20260622_000005_product_search_aggregate::Migration),
Box::new(m20260622_000006_order_search_indexes::Migration), Box::new(m20260622_000006_order_search_indexes::Migration),
Box::new(m20260623_000001_add_short_description_to_products::Migration),
// inject-above (do not remove this comment) // inject-above (do not remove this comment)
] ]
} }

View File

@@ -0,0 +1,18 @@
use loco_rs::schema::*;
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
// A short blurb shown on product cards (grid/list), distinct from the full
// `description` rendered on the product detail page.
add_column(m, "products", "short_description", ColType::TextNull).await
}
async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
remove_column(m, "products", "short_description").await
}
}

View File

@@ -52,6 +52,7 @@ struct ProductFields {
name: String, name: String,
slug: String, slug: String,
description: Option<String>, description: Option<String>,
short_description: Option<String>,
currency: String, currency: String,
category_id: Option<i32>, category_id: Option<i32>,
published: bool, published: bool,
@@ -67,6 +68,7 @@ async fn parse_product_fields(
.ok_or_else(|| Error::BadRequest("product name is required".to_string()))?; .ok_or_else(|| Error::BadRequest("product name is required".to_string()))?;
let currency = form.text("currency").unwrap_or_else(|| "EUR".to_string()); let currency = form.text("currency").unwrap_or_else(|| "EUR".to_string());
let description = form.text("description"); let description = form.text("description");
let short_description = form.text("short_description");
let category_id = form.text("category_id").and_then(|s| s.parse::<i32>().ok()); let category_id = form.text("category_id").and_then(|s| s.parse::<i32>().ok());
let published = form.checked("published"); let published = form.checked("published");
@@ -91,6 +93,7 @@ async fn parse_product_fields(
name, name,
slug, slug,
description, description,
short_description,
currency, currency,
category_id, category_id,
published, published,
@@ -438,6 +441,7 @@ async fn create(
name: Set(fields.name), name: Set(fields.name),
slug: Set(fields.slug), slug: Set(fields.slug),
description: Set(fields.description), description: Set(fields.description),
short_description: Set(fields.short_description),
currency: Set(fields.currency), currency: Set(fields.currency),
view_count: Set(0), view_count: Set(0),
published: Set(fields.published), published: Set(fields.published),
@@ -552,6 +556,7 @@ async fn update(
product.name = Set(fields.name); product.name = Set(fields.name);
product.slug = Set(fields.slug); product.slug = Set(fields.slug);
product.description = Set(fields.description); product.description = Set(fields.description);
product.short_description = Set(fields.short_description);
product.currency = Set(fields.currency); product.currency = Set(fields.currency);
product.category_id = Set(fields.category_id); product.category_id = Set(fields.category_id);
product.published = Set(fields.published); product.published = Set(fields.published);

View File

@@ -15,6 +15,8 @@ pub struct Model {
pub slug: String, pub slug: String,
#[sea_orm(column_type = "Text", nullable)] #[sea_orm(column_type = "Text", nullable)]
pub description: Option<String>, pub description: Option<String>,
#[sea_orm(column_type = "Text", nullable)]
pub short_description: Option<String>,
pub currency: String, pub currency: String,
pub view_count: i32, pub view_count: i32,
pub published: bool, pub published: bool,

View File

@@ -27,6 +27,7 @@ pub fn product_card(
"name": product.name, "name": product.name,
"slug": product.slug, "slug": product.slug,
"description": product.description, "description": product.description,
"short_description": product.short_description,
"price": format_price(priced.price_cents), "price": format_price(priced.price_cents),
"on_sale": priced.is_reduced(), "on_sale": priced.is_reduced(),
"is_business": priced.is_business, "is_business": priced.is_business,
@@ -69,6 +70,7 @@ pub fn product_form(product: &products::Model, images: &[product_images::Model])
"name": product.name, "name": product.name,
"slug": product.slug, "slug": product.slug,
"description": product.description, "description": product.description,
"short_description": product.short_description,
"currency": product.currency, "currency": product.currency,
"published": product.published, "published": product.published,
"category_id": product.category_id, "category_id": product.category_id,