feat(layouts): add 'Single' layout option to GroupLayout and update related functionality

This commit is contained in:
2026-08-12 11:09:11 -03:00
parent 0fae1ca1d7
commit 36c1c185ee
7 changed files with 117 additions and 8 deletions

View File

@@ -8,6 +8,7 @@ enum GroupLayout: string
case Simple = 'simple';
case SimpleVertical = 'simple_vertical';
case Carousel = 'carousel';
case Single = 'single';
/**
* @return list<string>

View File

@@ -7,6 +7,7 @@ enum ProductLayout: string
case Row = 'row';
case ColumnWithImage = 'column_with_image';
case ColumnWithCart = 'column_with_cart';
case TicketSelector = 'ticket_selector';
/**
* @return list<string>

View File

@@ -25,7 +25,7 @@ class CatalogFeaturedItemResource extends JsonResource
return $this->columnWithImageData($catalogItem);
}
return [
$data = [
'id' => $catalogItem->id,
'type' => $catalogItem->type->value,
'nombre' => $catalogItem->nombre,
@@ -51,16 +51,17 @@ class CatalogFeaturedItemResource extends JsonResource
])
->values(),
];
if ($featuredGroup->product_layout === ProductLayout::TicketSelector) {
$data['image'] = $this->firstImageUrl($catalogItem);
}
return $data;
}
/** @return array<string, mixed> */
private function columnWithImageData(CatalogItem $catalogItem): array
{
$attachment = $catalogItem->attachments->first()
?? $catalogItem->variants
->flatMap(fn (Variant $variant) => $variant->attachments)
->first();
return [
'id' => $catalogItem->id,
'type' => $catalogItem->type->value,
@@ -69,7 +70,17 @@ class CatalogFeaturedItemResource extends JsonResource
'ticket_generation_policy' => $catalogItem->ticket_generation_policy->value,
'validity_time_id' => $catalogItem->validity_time_id,
'validity_time' => ValidityTimeResource::make($catalogItem->validityTime),
'image' => $attachment?->getTemporaryUrl(1440),
'image' => $this->firstImageUrl($catalogItem),
];
}
private function firstImageUrl(CatalogItem $catalogItem): ?string
{
$attachment = $catalogItem->attachments->first()
?? $catalogItem->variants
->flatMap(fn (Variant $variant) => $variant->attachments)
->first();
return $attachment?->getTemporaryUrl(1440);
}
}

View File

@@ -18,7 +18,13 @@ class FeaturedGroupService
public function itemsResponse(FeaturedGroup $featuredGroup, int $page): array
{
if ($featuredGroup->group_layout !== GroupLayout::Paginated) {
$items = $this->itemsQuery($featuredGroup)->get();
$query = $this->itemsQuery($featuredGroup);
if ($featuredGroup->group_layout === GroupLayout::Single) {
$query->limit(1);
}
$items = $query->get();
$this->attachGroup($items, $featuredGroup);
return CatalogFeaturedItemResource::collection($items)->resolve();

View File

@@ -0,0 +1,53 @@
<?php
use App\Domains\Catalog\Enums\GroupLayout;
use App\Domains\Catalog\Enums\ProductLayout;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
if (! in_array(DB::getDriverName(), ['mysql', 'mariadb'], true)) {
return;
}
$this->alterEnums(ProductLayout::values(), GroupLayout::values());
}
public function down(): void
{
if (! in_array(DB::getDriverName(), ['mysql', 'mariadb'], true)) {
return;
}
$this->alterEnums(
['row', 'column_with_image', 'column_with_cart'],
['paginated', 'simple', 'simple_vertical', 'carousel'],
);
}
/**
* @param list<string> $productLayouts
* @param list<string> $groupLayouts
*/
private function alterEnums(array $productLayouts, array $groupLayouts): void
{
$products = $this->enumValues($productLayouts);
$groups = $this->enumValues($groupLayouts);
DB::statement("ALTER TABLE featured_groups MODIFY product_layout ENUM({$products}) NOT NULL");
DB::statement("ALTER TABLE featured_groups MODIFY group_layout ENUM({$groups}) NOT NULL DEFAULT 'paginated'");
DB::statement("ALTER TABLE tenants MODIFY search_product_layout ENUM({$products}) NOT NULL DEFAULT 'column_with_image'");
DB::statement("ALTER TABLE tenants MODIFY search_group_layout ENUM({$groups}) NOT NULL DEFAULT 'paginated'");
}
/** @param list<string> $values */
private function enumValues(array $values): string
{
return collect($values)
->map(fn (string $value): string => DB::getPdo()->quote($value))
->implode(',');
}
};

View File

@@ -241,6 +241,32 @@ class CatalogControllerTest extends TestCase
->assertJsonPath('0.nombre', 'carousel Item 1');
}
public function test_single_group_layout_returns_only_its_first_available_item(): void
{
$tenant = $this->createTenant('catalog-single-layout');
$group = $this->createGroup(
$tenant,
ProductLayout::TicketSelector,
'Entradas',
groupLayout: GroupLayout::Single,
);
foreach (['Primera entrada', 'Segunda entrada'] as $order => $name) {
$item = $this->createItem($tenant, $name);
$group->featuredItems()->create([
'catalog_item_id' => $item->id,
'order' => $order,
]);
}
$this->getJson("/api/tenants/{$tenant->codigo}/catalog")
->assertOk()
->assertJsonPath('0.layout', ProductLayout::TicketSelector->value)
->assertJsonPath('0.group_layout', GroupLayout::Single->value)
->assertJsonCount(1, '0.items')
->assertJsonPath('0.items.0.nombre', 'Primera entrada');
}
public function test_groups_can_source_items_from_a_category_or_the_entire_catalog(): void
{
$tenant = $this->createTenant('catalog-sources');

View File

@@ -36,9 +36,20 @@ class CatalogModelsTest extends TestCase
'simple',
'simple_vertical',
'carousel',
'single',
], GroupLayout::values());
}
public function test_product_layout_has_all_supported_values(): void
{
$this->assertSame([
'row',
'column_with_image',
'column_with_cart',
'ticket_selector',
], ProductLayout::values());
}
public function test_attribute_maps_its_values_and_relations(): void
{
$attribute = new Attribute;