feature/category-tree #55

Merged
krzysiej merged 5 commits from feature/category-tree into master 2026-01-26 08:59:58 +01:00
4 changed files with 63 additions and 8 deletions

View File

@@ -11,10 +11,11 @@ final class CategoryController extends BaseController
#[Route('/category/{category}', name: 'app_category')] #[Route('/category/{category}', name: 'app_category')]
public function __invoke(string $category): Response public function __invoke(string $category): Response
{ {
if($this->cache->getItem('list_category_'.$category)->isHit()) { // if($this->cache->getItem('list_category_'.$category)->isHit()) {
return $this->render('productList.html.twig', ['listType' => 'category_'.$category]); // return $this->render('productList.html.twig', ['listType' => 'category_'.$category]);
} // }
/** @var Product[] $products */
$products = Product::with(['price', 'lowestPrice']) $products = Product::with(['price', 'lowestPrice'])
->selectRaw('products.*') ->selectRaw('products.*')
->distinct('products.id') ->distinct('products.id')
@@ -24,6 +25,26 @@ final class CategoryController extends BaseController
->orderByDesc('created_by') ->orderByDesc('created_by')
->get(); ->get();
return $this->render('productList.html.twig', ['products' => $products, 'listType' => 'category_'.$category]); $categoriesTree = [];
foreach ($products as $product) {
$categoriesTree = $this->addToTree($product->categories, $categoriesTree);
}
return $this->render('productList.html.twig', ['products' => $products, 'listType' => 'category_' . $category, 'category' => $category, 'categoryTree' => $categoriesTree]);
}
private function addToTree(array $categories, mixed $categoriesTree)
{
$tmp = &$categoriesTree;
foreach ($categories as $category) {
if (empty($tmp[$category])) {
$tmp[$category] = ['count' => 0];
}
$tmp = &$tmp[$category];
$tmp['count']++;
}
return $categoriesTree;
} }
} }

View File

@@ -2,11 +2,11 @@
namespace Krzysiej\RyobiCrawler\Twig; namespace Krzysiej\RyobiCrawler\Twig;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Krzysiej\RyobiCrawler\Models\Price; use Krzysiej\RyobiCrawler\Models\Price;
use Krzysiej\RyobiCrawler\Models\Product; use Krzysiej\RyobiCrawler\Models\Product;
use Krzysiej\RyobiCrawler\Models\Stock; use Krzysiej\RyobiCrawler\Models\Stock;
use Symfony\Component\Routing\RouterInterface;
use Twig\Extension\AbstractExtension; use Twig\Extension\AbstractExtension;
use Twig\TwigFilter; use Twig\TwigFilter;
use Twig\TwigFunction; use Twig\TwigFunction;
@@ -15,6 +15,10 @@ use function Symfony\Component\Clock\now;
class AppExtension extends AbstractExtension class AppExtension extends AbstractExtension
{ {
public function __construct(public RouterInterface $route)
{
}
public function getFunctions(): array public function getFunctions(): array
{ {
return [ return [
@@ -23,6 +27,7 @@ class AppExtension extends AbstractExtension
new TwigFunction('newCount', [$this, 'newCount']), new TwigFunction('newCount', [$this, 'newCount']),
new TwigFunction('discontinuedCount', [$this, 'discontinuedCount']), new TwigFunction('discontinuedCount', [$this, 'discontinuedCount']),
new TwigFunction('lowestPriceCount', [$this, 'lowestPriceCount']), new TwigFunction('lowestPriceCount', [$this, 'lowestPriceCount']),
new TwigFunction('renderCategoryTree', [$this, 'renderCategoryTree']),
]; ];
} }
@@ -56,7 +61,7 @@ class AppExtension extends AbstractExtension
public function lowestPriceCount(): int public function lowestPriceCount(): int
{ {
return Product::whereRaw('priceCurrent = priceLowest') return Product::whereRaw('priceCurrent = priceLowest')
->whereRaw('lastSeen = "'.now()->format('Y-m-d').'"') ->whereRaw('lastSeen = "' . now()->format('Y-m-d') . '"')
->whereRaw('priceCurrent < productStandardPrice') ->whereRaw('priceCurrent < productStandardPrice')
->count(); ->count();
} }
@@ -65,4 +70,28 @@ class AppExtension extends AbstractExtension
{ {
return $items->first(fn($item) => str_starts_with($item->created_at, $date)); return $items->first(fn($item) => str_starts_with($item->created_at, $date));
} }
public function renderCategoryTree($categories, $current, $level = 0): string
{
$tree = '';
if ($level == 0) {
$tree .= '<ul class="list-group list-group-flush">';
}
foreach ($categories as $categoryName => $category) {
$currentClass = $categoryName == $current ? 'list-group-item-primary' : '';
$tree .= '<a class="list-group-item list-group-item-action '.$currentClass.' text-decoration-none ms-' . ($level * 2) . '" href="' . $this->route->generate('app_category', ['category' => $categoryName]) . '">' . $categoryName . ' <span class="badge bg-primary rounded-pill">' . $category['count'] . '</span></a>';
unset($category['count']);
if (is_array($category) && count($category) >= 1) {
foreach ($category as $subcategoryName => $subCategory) {
$tree .= $this->renderCategoryTree([$subcategoryName => $subCategory], $current, $level + 1);
}
}
}
if ($level == 0) {
$tree .= '</ul>';
}
return $tree;
}
} }

View File

@@ -2,6 +2,11 @@
{% block content %} {% block content %}
{% cache 'list_' ~ listType %} {% cache 'list_' ~ listType %}
{% if listType starts with 'category_' %}
{{ renderCategoryTree(categoryTree, category) | raw }}
{% endif %}
<div class="table-responsive"> <div class="table-responsive">
<table class='table table-hover'> <table class='table table-hover'>
<thead> <thead>

View File

@@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en" data-bs-theme="light">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">