16 Commits

Author SHA1 Message Date
Valentin Lab
43993f587c chg: responsive fixes for homepage text, scroll overflow and menu font on small screens (< 430px) 2026-03-27 14:14:25 +01:00
Valentin Lab
de13dee166 fix: add ellipsis on long article labels to prevent overflow on neighbor thumbnails 2026-03-27 13:56:25 +01:00
Valentin Lab
64af20e10a chg: reduce spacing between article thumbnails on small screens (< 430px) 2026-03-27 13:55:37 +01:00
Valentin Lab
2184f1e83c chg: reduce article label font size under thumbnails on small screens (< 430px) 2026-03-27 13:54:58 +01:00
Valentin Lab
8d94c038ad chg: reduce discover button size and hide suffix on small screens (< 430px) 2026-03-27 13:36:36 +01:00
Valentin Lab
f27a7ef8e6 chg: reduce shelve title font size on small screens (< 430px) 2026-03-27 13:12:14 +01:00
Valentin Lab
6e46485d53 fix: show article description on merchandise product pages 2026-03-27 13:12:04 +01:00
Valentin Lab
31815cd618 fix: replace hardcoded nature keys with dynamic foreach in article add-to-basket 2026-03-27 13:11:56 +01:00
Valentin Lab
5947ee256a fix: allow merchandise articles to appear in category pages 2026-03-27 13:11:41 +01:00
Valentin Lab
93f027f815 new: add filtered articles link from category edit form 2026-03-27 13:11:32 +01:00
Valentin Lab
3bfbd629bf fix: exclude invisible articles from category menu visibility 2026-03-27 13:11:22 +01:00
Valentin Lab
493743307a new: add visibility badge for articles in offer form select 2026-03-16 16:44:48 +01:00
Valentin Lab
d8f95c667c new: add warning icon for tariffs without price list in offer form select 2026-03-16 16:41:17 +01:00
Valentin Lab
2563398df2 new: add colored status badges for tariffs in list and offer form select 2026-03-16 16:35:24 +01:00
Valentin Lab
39572c9ea2 new: show active/inactive status toggle on offer edit form 2026-03-16 16:24:44 +01:00
Valentin Lab
55051334ef new: add quick edit links for article, package and tariff on offer form 2026-03-16 16:23:43 +01:00
13 changed files with 255 additions and 83 deletions

View File

@@ -22,7 +22,7 @@ class TariffsDataTable extends DataTable
{
$datatables
->editColumn('status', function (Tariff $tariff) {
return Tariffs::getStatus($tariff['status_id']);
return Tariffs::getStatusBadge($tariff['status_id']);
})
->editColumn('sale_channels2', function (Tariff $tariff) {
$html = '';
@@ -32,7 +32,7 @@ class TariffsDataTable extends DataTable
return $html;
})
->rawColumns(['sale_channels2', 'action']);
->rawColumns(['status', 'sale_channels2', 'action']);
return parent::modifier($datatables);
}

View File

@@ -26,6 +26,7 @@ class ArticleController extends Controller
'article_natures' => ArticleNatures::getOptions(),
'categories' => Categories::getOptions(),
'tags' => Tags::getOptionsFullName(),
'filters' => request()->only(['article_nature_id', 'category_id', 'tag_id']),
];
return $dataTable->render('Admin.Shop.Articles.list', $data);

View File

@@ -218,7 +218,7 @@ class Article extends Model implements HasMedia
public function scopeWithAvailableOffers($query, $saleChannelId = false)
{
return $query->whereHas('offers', function ($query) use ($saleChannelId) {
return $query->visible()->whereHas('offers', function ($query) use ($saleChannelId) {
$query->active()->byStockAvailable();
if ($saleChannelId) {

View File

@@ -67,6 +67,11 @@ class Articles
return $data;
}
public static function getVisibilityMap()
{
return Article::pluck('visible', 'id')->toArray();
}
public static function getAll()
{
return Article::orderBy('name', 'asc')->get();
@@ -312,8 +317,6 @@ class Articles
case 'merchandise':
$model = $model->merchandise();
break;
default:
$model = $model->botanic();
}
return $model;

View File

@@ -16,7 +16,11 @@ class Offers
{
return [
'articles' => Articles::getOptionsWithNature(),
'article_visibilities' => Articles::getVisibilityMap(),
'tariffs' => Tariffs::getOptions(),
'tariff_statuses' => Tariffs::getStatusMap(),
'tariff_status_labels' => Tariffs::getStatuses(),
'tariff_pricelist_counts' => Tariffs::getPriceListCountMap(),
'variations' => Variations::getOptions(),
];
}

View File

@@ -50,11 +50,30 @@ class Tariffs
return self::getStatuses()[$status_id];
}
public static function getStatusBadge($status_id)
{
$colors = ['success', 'warning', 'secondary', 'danger'];
$label = self::getStatus($status_id);
$color = $colors[$status_id] ?? 'secondary';
return '<span class="badge badge-'.$color.'">'.$label.'</span>';
}
public static function getStatuses()
{
return ['Actif', 'Suspendu', 'Invisible', 'Obsolete'];
}
public static function getStatusMap()
{
return Tariff::pluck('status_id', 'id')->toArray();
}
public static function getPriceListCountMap()
{
return Tariff::withCount('price_lists')->pluck('price_lists_count', 'id')->toArray();
}
public static function getModel()
{
return Tariff::query();

View File

@@ -436,3 +436,63 @@ div.megamenu ul.megamenu li.megamenu.level1
max-width: 100%;
}
}
/* -- Titres des rayons -- */
.shelve-title {
font-size: 2em;
}
.shelve-article-label {
height: 48px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
/* -- Responsive: très petites résolutions (< 430px) -- */
@media (max-width: 429.98px) {
.shelve-title {
font-size: 1.4em;
}
.shelve-btn {
font-size: 0.8em;
}
.shelve-btn-suffix {
display: none;
}
.shelve-article-label {
font-size: 0.8em;
height: 36px;
}
.shelve-slide {
padding-left: 0.15rem !important;
padding-right: 0.15rem !important;
}
body {
overflow-x: hidden;
}
.homepage-text {
font-size: 1em !important;
}
.homepage-text h1 {
font-size: 1.3em !important;
}
.homepage-text h5 {
font-size: 0.85em !important;
}
.homepage-text p {
font-size: 0.85em;
}
.homepage-text .home-nav .auto {
min-width: 100% !important;
max-width: 100% !important;
}
#navbarContentMobile .nav-link,
#navbarContentMobile .dropdown-menu a {
font-size: 0.85em;
}
}

View File

@@ -1,3 +1,12 @@
@if ($category['id'] ?? false)
<div class="d-flex justify-content-end mb-3">
<a href="{{ route('Admin.Shop.Articles.index', ['category_id' => $category['id']]) }}" class="btn btn-outline-primary">
Voir les articles de ce rayon
<i class="fa fa-external-link"></i>
</a>
</div>
@endif
<div class="row">
<div class="col-9">
<div class="row mb-3">

View File

@@ -14,45 +14,66 @@
<div class="col-12">
<div class="row mb-3">
<div class="col-12">
@include('components.form.select', [
'name' => 'article_id',
'id_name' => 'article_id',
'list' => $articles ?? null,
'value' => $offer['article_id'] ?? null,
'with_empty' => '',
'class' => 'select2 select_article',
'label' => 'Article',
'required' => true,
])
<div class="d-flex align-items-end">
<div class="flex-grow-1">
@include('components.form.select', [
'name' => 'article_id',
'id_name' => 'article_id',
'list' => $articles ?? null,
'value' => $offer['article_id'] ?? null,
'with_empty' => '',
'class' => 'select2 select_article',
'label' => 'Article',
'required' => true,
])
</div>
<a id="edit-article-link" href="#" class="btn btn-sm btn-outline-secondary ml-2 mb-1" title="Modifier l'article" style="display:none;">
<i class="fa fa-external-link"></i>
</a>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-4">
@include('components.form.select', [
'name' => 'variation_id',
'id_name' => 'variation_id',
'list' => $variations ?? null,
'value' => $offer['variation_id'] ?? null,
'with_empty' => '',
'class' => 'select2 select_variation',
'label' => __('shop.packages.name'),
'required' => true,
])
<div class="d-flex align-items-end">
<div class="flex-grow-1">
@include('components.form.select', [
'name' => 'variation_id',
'id_name' => 'variation_id',
'list' => $variations ?? null,
'value' => $offer['variation_id'] ?? null,
'with_empty' => '',
'class' => 'select2 select_variation',
'label' => __('shop.packages.name'),
'required' => true,
])
</div>
<a id="edit-variation-link" href="#" class="btn btn-sm btn-outline-secondary ml-2 mb-1" title="Modifier la déclinaison" style="display:none;">
<i class="fa fa-external-link"></i>
</a>
</div>
</div>
<div class="col-4">
@include('components.form.select', [
'name' => 'tariff_id',
'id_name' => 'tariff_id',
'list' => $tariffs ?? null,
'value' => $offer['tariff_id'] ?? null,
'with_empty' => '',
'class' => 'select2 select_tariffs',
'label' => 'Tarif',
'required' => true,
])
<div class="d-flex align-items-end">
<div class="flex-grow-1">
@include('components.form.select', [
'name' => 'tariff_id',
'id_name' => 'tariff_id',
'list' => $tariffs ?? null,
'value' => $offer['tariff_id'] ?? null,
'with_empty' => '',
'class' => 'select2 select_tariffs',
'label' => 'Tarif',
'required' => true,
])
</div>
<a id="edit-tariff-link" href="#" class="btn btn-sm btn-outline-secondary ml-2 mb-1" title="Modifier le tarif" style="display:none;">
<i class="fa fa-external-link"></i>
</a>
</div>
</div>
<div class="col-4">
<div class="col-2">
@include('components.form.input', [
'name' => 'weight',
'value' => $offer['weight'] ?? null,
@@ -60,6 +81,15 @@
'required' => true,
])
</div>
<div class="col-2">
<input type="hidden" name="status_id" value="0">
@include('components.form.toggle', [
'name' => 'status_id',
'value' => $offer['status_id'] ?? 0,
'label' => 'Actif',
'size' => 'md',
])
</div>
</div>
@component('components.card', ['title' => 'Disponibilité', 'class' => 'mt-5'])
@@ -122,5 +152,80 @@
initChevron();
initSaveForm('#offer-form');
initSelect2();
$('#status_id').bootstrapToggle();
// Article visibility badges in select2
var articleVisibilities = {!! json_encode($article_visibilities ?? (object)[]) !!};
function formatArticle(item) {
if (!item.id) return item.text;
var visible = articleVisibilities[item.id];
var badge = (visible == 1)
? '<span class="badge badge-success" style="font-size:0.75em;margin-left:4px;">Visible</span>'
: '<span class="badge badge-warning" style="font-size:0.75em;margin-left:4px;">Invisible</span>';
return $('<span style="display:flex;justify-content:space-between;align-items:center;width:100%;">' + item.text + badge + '</span>');
}
$('#article_id').select2('destroy').select2({
placeholder: "{{ __('select_a_value') }}",
allowClear: false,
width: { value: '100%' },
templateResult: formatArticle,
templateSelection: formatArticle
});
// Tariff status badges in select2
var tariffStatuses = {!! json_encode($tariff_statuses ?? (object)[]) !!};
var tariffStatusLabels = {!! json_encode($tariff_status_labels ?? []) !!};
var tariffStatusColors = {0: '#28a745', 1: '#ffc107', 2: '#6c757d', 3: '#dc3545'};
var tariffPLCounts = {!! json_encode($tariff_pricelist_counts ?? (object)[]) !!};
function formatTariff(item) {
if (!item.id) return item.text;
var statusId = tariffStatuses[item.id];
var color = tariffStatusColors[statusId] || '#6c757d';
var label = tariffStatusLabels[statusId] || '';
var plCount = tariffPLCounts[item.id] || 0;
var warning = plCount == 0 ? '<span style="margin-left:4px;cursor:help;" title="Aucune liste de prix"><i class="fas fa-exclamation-triangle text-danger" style="font-size:1.4em;vertical-align:-2px;"></i></span>' : '';
return $('<span style="display:flex;justify-content:space-between;align-items:center;width:100%;">' + item.text + '<span>' + warning + '<span class="badge" style="background:' + color + ';color:#fff;font-size:0.75em;margin-left:4px;">' + label + '</span></span></span>');
}
$('#tariff_id').select2('destroy').select2({
placeholder: "{{ __('select_a_value') }}",
allowClear: false,
width: { value: '100%' },
templateResult: formatTariff,
templateSelection: formatTariff
});
function updateEditLink(selectId, linkId, routeTemplate) {
var val = $('#' + selectId).val();
var $link = $('#' + linkId);
if (val) {
$link.attr('href', routeTemplate.replace('__ID__', val)).show();
} else {
$link.hide();
}
}
var articleRoute = '{{ route('Admin.Shop.Articles.edit', ['id' => '__ID__']) }}';
var variationRoute = '{{ route('Admin.Shop.Variations.edit', ['id' => '__ID__']) }}';
var tariffRoute = '{{ route('Admin.Shop.Tariffs.edit', ['id' => '__ID__']) }}';
// Init on page load
updateEditLink('article_id', 'edit-article-link', articleRoute);
updateEditLink('variation_id', 'edit-variation-link', variationRoute);
updateEditLink('tariff_id', 'edit-tariff-link', tariffRoute);
// Update on change
$('#article_id').on('change', function() {
updateEditLink('article_id', 'edit-article-link', articleRoute);
});
$('#variation_id').on('change', function() {
updateEditLink('variation_id', 'edit-variation-link', variationRoute);
});
$('#tariff_id').on('change', function() {
updateEditLink('tariff_id', 'edit-tariff-link', tariffRoute);
});
</script>
@endpush

View File

@@ -1,9 +1,6 @@
@php
// Check if article is not visible OR has no offers at all
$hasNoOffers = empty($article['offers']['semences'] ?? false)
&& empty($article['offers']['plants'] ?? false)
&& empty($article['offers']['legumes'] ?? false)
&& empty($article['offers']['marchandise'] ?? false);
$hasNoOffers = empty($article['offers'] ?? false) || !array_filter($article['offers']);
$shouldShowComingSoon = !($article['visible'] ?? true) || $hasNoOffers;
@endphp
@@ -19,41 +16,16 @@
</div>
@else
{{-- Display normal offers for visible articles with available offers --}}
@if ($article['offers']['semences'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['semences'],
'title' => 'Semences',
'model' => 'semences',
'bgClass' => 'bg-green-light',
])
@endif
@if ($article['offers']['plants'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['plants'],
'title' => 'Plants',
'model' => 'plants',
'bgClass' => 'bg-green-light',
])
@endif
@if ($article['offers']['legumes'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['legumes'],
'title' => 'Légumes',
'model' => 'legumes',
'bgClass' => 'bg-green-light',
])
@endif
@if ($article['offers']['marchandise'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['marchandise'],
'title' => 'Marchandises',
'model' => 'marchandise',
'bgClass' => 'bg-green-light',
])
@endif
@foreach ($article['offers'] as $natureKey => $natureOffers)
@if (!empty($natureOffers))
@include('Shop.Articles.partials.addBasket', [
'data' => $natureOffers,
'title' => ucfirst($natureKey),
'model' => $natureKey,
'bgClass' => 'bg-green-light',
])
@endif
@endforeach
@endif
@include('load.basket')

View File

@@ -19,9 +19,8 @@
</div>
<div class="col-lg-5 col-xs-12 text-justify">
{!! $article['description']['variety'] ?? null !!}
{!! $article['description']['semences'] ?? null !!}
{!! $article['description']['plants'] ?? null !!}
{!! $article['description']['merchandise'] ?? null !!}
{!! $article['description']['description'] ?? null !!}
@if ($article['description']['plus'] ?? false)
<h3>Spécificités</h3>

View File

@@ -2,12 +2,12 @@
<div class="mb-5 bg-green-light shadow2">
<div class="row">
<div class="col-6">
<h1 class="p-2 green" style="font-size: 2em;">{{ $shelve['name'] }}</h1>
<h1 class="p-2 green shelve-title">{{ $shelve['name'] }}</h1>
</div>
<div class="col-6 text-right">
<a href="{{ route('Shop.Categories.show', ['id' => $shelve['id']]) }}"
class="mt-2 mr-2 btn btn-green-dark">
Découvrir la sélection
class="mt-2 mr-2 btn btn-green-dark shelve-btn">
Découvrir<span class="shelve-btn-suffix"> la sélection</span>
</a>
<!--
<a class="mt-2 green-dark btn" href="{{ route('Shop.Categories.show', ['id' => $shelve['id']]) }}">Tout
@@ -18,11 +18,11 @@
<div class="row">
<div class="col-11 mx-auto shelve_slider_{{ $shelve['id'] }} slider">
@foreach ($shelve['articles'] as $name => $article)
<div class="text-center pr-2 pl-2">
<div class="text-center pr-2 pl-2 shelve-slide">
<a class="green" href="{{ route('Shop.Articles.show', ['id' => $article['id']]) }}">
<img data-lazy="{{ App\Repositories\Shop\Articles::getPreviewSrc($article['image'] ?? false) }}"
class="d-block w-100 rounded" alt="{{ $name }}" />
<div style="height: 48px;">
<div class="shelve-article-label">
{{ $name }}
</div>
</a>

View File

@@ -7,7 +7,7 @@
@if (!empty($text))
<div class="row m-0 mb-3">
<div class="col-12 p-3 green-dark" style="font-size: 1.2em;">{!! $text !!}</div>
<div class="col-12 p-3 green-dark homepage-text" style="font-size: 1.2em;">{!! $text !!}</div>
</div>
@endif