отображение ненужных цен за кг
This commit is contained in:
@@ -49,9 +49,51 @@ final class CategoryRepository extends BaseRepository
|
|||||||
$childrenByParent[$parentKey][] = $item;
|
$childrenByParent[$parentKey][] = $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$childrenByParent[0] = $this->sortRootCategories($childrenByParent[0] ?? []);
|
||||||
|
|
||||||
return $this->buildTree($childrenByParent, 0);
|
return $this->buildTree($childrenByParent, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array<string, mixed>> $categories
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
private function sortRootCategories(array $categories): array
|
||||||
|
{
|
||||||
|
usort($categories, function (array $left, array $right): int {
|
||||||
|
return [
|
||||||
|
$this->rootCategoryWeight((string) ($left['name'] ?? '')),
|
||||||
|
(int) ($left['sort_order'] ?? 100),
|
||||||
|
(string) ($left['name'] ?? ''),
|
||||||
|
] <=> [
|
||||||
|
$this->rootCategoryWeight((string) ($right['name'] ?? '')),
|
||||||
|
(int) ($right['sort_order'] ?? 100),
|
||||||
|
(string) ($right['name'] ?? ''),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return $categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rootCategoryWeight(string $name): int
|
||||||
|
{
|
||||||
|
$lowerName = trim(mb_strtolower($name));
|
||||||
|
|
||||||
|
return match (true) {
|
||||||
|
str_contains($lowerName, 'рыб') => 10,
|
||||||
|
str_contains($lowerName, 'морепроду') => 20,
|
||||||
|
str_contains($lowerName, 'икра') => 30,
|
||||||
|
$lowerName === 'мясо' => 40,
|
||||||
|
str_contains($lowerName, 'сыр') => 50,
|
||||||
|
str_contains($lowerName, 'полуфаб') => 60,
|
||||||
|
str_contains($lowerName, 'фрукт') || str_contains($lowerName, 'овощ') || str_contains($lowerName, 'ягод') || str_contains($lowerName, 'гриб') => 70,
|
||||||
|
str_contains($lowerName, 'бакале') => 80,
|
||||||
|
str_contains($lowerName, 'мясная гастрономия') => 90,
|
||||||
|
str_contains($lowerName, 'молоч') => 100,
|
||||||
|
default => 1000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int, array<int, array<string, mixed>>> $childrenByParent
|
* @param array<int, array<int, array<string, mixed>>> $childrenByParent
|
||||||
* @return array<int, array<string, mixed>>
|
* @return array<int, array<string, mixed>>
|
||||||
|
|||||||
@@ -44,6 +44,61 @@ function unit_label(?string $unit): string
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function text_lower(string $value): string
|
||||||
|
{
|
||||||
|
if (function_exists('mb_strtolower')) {
|
||||||
|
return mb_strtolower($value, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtolower(strtr($value, [
|
||||||
|
'А' => 'а', 'Б' => 'б', 'В' => 'в', 'Г' => 'г', 'Д' => 'д',
|
||||||
|
'Е' => 'е', 'Ё' => 'ё', 'Ж' => 'ж', 'З' => 'з', 'И' => 'и',
|
||||||
|
'Й' => 'й', 'К' => 'к', 'Л' => 'л', 'М' => 'м', 'Н' => 'н',
|
||||||
|
'О' => 'о', 'П' => 'п', 'Р' => 'р', 'С' => 'с', 'Т' => 'т',
|
||||||
|
'У' => 'у', 'Ф' => 'ф', 'Х' => 'х', 'Ц' => 'ц', 'Ч' => 'ч',
|
||||||
|
'Ш' => 'ш', 'Щ' => 'щ', 'Ъ' => 'ъ', 'Ы' => 'ы', 'Ь' => 'ь',
|
||||||
|
'Э' => 'э', 'Ю' => 'ю', 'Я' => 'я',
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Price per kg helps with boxes and weight products, but confuses customers on
|
||||||
|
* caviar, cans, sauces, spices and small consumer packs. In those cases the
|
||||||
|
* storefront should sell by package and avoid derived kg prices.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $product
|
||||||
|
* @param array<string, mixed> $variant
|
||||||
|
*/
|
||||||
|
function product_should_show_unit_price(array $product, array $variant): bool
|
||||||
|
{
|
||||||
|
$unit = (string) ($variant['unit'] ?? $product['base_unit'] ?? '');
|
||||||
|
$unitLabel = unit_label($unit);
|
||||||
|
|
||||||
|
if (!in_array($unit, ['kg', 'кг'], true) && $unitLabel !== 'кг') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($variant['price_per_unit'] ?? null) === null || (float) $variant['price_per_unit'] <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$packageQuantity = (float) ($variant['package_quantity'] ?? 0);
|
||||||
|
if ($packageQuantity <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($packageQuantity < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$variantName = text_lower((string) ($variant['name'] ?? ''));
|
||||||
|
if (preg_match('/(^|[\s,.;])\d+(?:[.,]\d+)?\s*(мл|шт)\.?($|[\s,.;])/u', $variantName) === 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function money_label(mixed $value): string
|
function money_label(mixed $value): string
|
||||||
{
|
{
|
||||||
if ($value === null || $value === '') {
|
if ($value === null || $value === '') {
|
||||||
|
|||||||
+557
-42
@@ -260,12 +260,13 @@ body {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
gap: 7px;
|
||||||
width: auto;
|
width: auto;
|
||||||
min-width: 96px;
|
min-width: 104px;
|
||||||
max-width: 190px;
|
max-width: 220px;
|
||||||
min-height: 38px;
|
min-height: 42px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 8px 13px;
|
padding: 8px 14px 8px 10px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
font-weight: 850;
|
font-weight: 850;
|
||||||
@@ -274,6 +275,10 @@ body {
|
|||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mega-root > span:last-child {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.mega-root:hover,
|
.mega-root:hover,
|
||||||
.mega-item:focus-within .mega-root,
|
.mega-item:focus-within .mega-root,
|
||||||
.mega-item:hover .mega-root {
|
.mega-item:hover .mega-root {
|
||||||
@@ -434,21 +439,23 @@ body {
|
|||||||
|
|
||||||
.price-mode-bar {
|
.price-mode-bar {
|
||||||
border-bottom: 1px solid var(--line);
|
border-bottom: 1px solid var(--line);
|
||||||
background: rgba(244, 246, 239, 0.9);
|
background:
|
||||||
|
linear-gradient(90deg, rgba(25, 91, 63, 0.08), rgba(207, 56, 45, 0.05)),
|
||||||
|
rgba(244, 246, 239, 0.96);
|
||||||
}
|
}
|
||||||
|
|
||||||
.price-mode-inner {
|
.price-mode-inner {
|
||||||
width: min(var(--page-max), calc(100% - (var(--page-gap) * 2)));
|
width: min(980px, calc(100% - (var(--page-gap) * 2)));
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-wrap: wrap;
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
gap: 10px 14px;
|
gap: 8px 16px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 10px 0;
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.price-mode-inner > span {
|
.price-mode-inner > span {
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
color: var(--brand-green);
|
color: var(--brand-green);
|
||||||
}
|
}
|
||||||
@@ -456,15 +463,20 @@ body {
|
|||||||
.price-mode-options {
|
.price-mode-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 6px;
|
gap: 8px;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.price-mode-button {
|
.price-mode-button {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
min-height: 32px;
|
display: inline-flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 36px;
|
||||||
border: 1px solid #cfd8c9;
|
border: 1px solid #cfd8c9;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 7px 12px;
|
padding: 8px 14px;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 850;
|
font-weight: 850;
|
||||||
@@ -473,6 +485,24 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.price-mode-button b {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 22px;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1;
|
||||||
|
color: #ffffff;
|
||||||
|
background: var(--brand-red);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-mode-button-cash {
|
||||||
|
border-color: rgba(25, 91, 63, 0.35);
|
||||||
|
box-shadow: 0 8px 26px rgba(25, 91, 63, 0.10);
|
||||||
|
}
|
||||||
|
|
||||||
.price-mode-button:hover,
|
.price-mode-button:hover,
|
||||||
.price-mode-button.is-active {
|
.price-mode-button.is-active {
|
||||||
border-color: var(--brand-green);
|
border-color: var(--brand-green);
|
||||||
@@ -480,8 +510,9 @@ body {
|
|||||||
background: var(--brand-green);
|
background: var(--brand-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.price-mode-inner small {
|
.price-mode-button.is-active b {
|
||||||
color: var(--muted);
|
color: var(--brand-red);
|
||||||
|
background: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
@@ -949,7 +980,7 @@ a:hover {
|
|||||||
|
|
||||||
.category-list {
|
.category-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 7px;
|
gap: 2px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@@ -957,56 +988,189 @@ a:hover {
|
|||||||
|
|
||||||
.category-list .category-list {
|
.category-list .category-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 6px;
|
gap: 1px;
|
||||||
margin: 8px 0 0;
|
margin: 3px 0 6px;
|
||||||
padding: 0 0 0 14px;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-list li {
|
.category-list li {
|
||||||
font-size: 15px;
|
min-width: 0;
|
||||||
font-weight: 700;
|
font-size: 14px;
|
||||||
|
font-weight: 760;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list a,
|
||||||
|
.category-details summary {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) 18px;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 36px;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-list a {
|
.category-list a {
|
||||||
overflow-wrap: anywhere;
|
overflow: hidden;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-list li.is-current > a,
|
.category-list.depth-0 > li > a,
|
||||||
.category-list a.is-current-link {
|
.category-list.depth-0 > li > .category-details > summary {
|
||||||
display: inline-block;
|
grid-template-columns: 30px minmax(0, 1fr) 18px;
|
||||||
border-radius: 6px;
|
padding-left: 8px;
|
||||||
padding: 3px 6px;
|
}
|
||||||
color: #ffffff;
|
|
||||||
background: var(--brand-green);
|
.category-list.depth-0 > li > a .category-icon,
|
||||||
|
.category-list.depth-0 > li > .category-details > summary .category-icon {
|
||||||
|
grid-column: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list.depth-0 > li > a span:last-child,
|
||||||
|
.category-list.depth-0 > li > .category-details > summary a {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list a span:last-child,
|
||||||
|
.category-details summary a {
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: 0;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list.depth-0 > li {
|
||||||
|
border-bottom: 1px solid #edf1e9;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list.depth-0 > li:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-list.depth-1 li {
|
.category-list.depth-1 li {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 680;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-list.depth-2 {
|
.category-list.depth-1 > li > a,
|
||||||
padding-left: 8px;
|
.category-list.depth-1 > li > .category-details > summary {
|
||||||
|
padding-left: 46px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-list.depth-2 li {
|
.category-list.depth-2 li {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 560;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list.depth-2 > li > a,
|
||||||
|
.category-list.depth-2 > li > .category-details > summary {
|
||||||
|
padding-left: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list.depth-3 > li > a,
|
||||||
|
.category-list.depth-3 > li > .category-details > summary {
|
||||||
|
padding-left: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list li.is-current > a,
|
||||||
|
.category-list li.is-current > .category-details > summary {
|
||||||
|
color: #ffffff;
|
||||||
|
background: var(--brand-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list a.is-current-link {
|
||||||
|
color: inherit;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list li.is-current > a .category-icon,
|
||||||
|
.category-list li.is-current > .category-details > summary .category-icon {
|
||||||
|
color: #ffffff;
|
||||||
|
background: rgba(255, 255, 255, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list a:hover,
|
||||||
|
.category-details summary:hover {
|
||||||
|
color: var(--brand-green);
|
||||||
|
background: #eef4ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list li.is-current > a:hover,
|
||||||
|
.category-list li.is-current > .category-details > summary:hover {
|
||||||
|
color: #ffffff;
|
||||||
|
background: var(--brand-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-details summary {
|
.category-details summary {
|
||||||
display: flex;
|
position: relative;
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 28px;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding: 2px 4px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-details summary:hover {
|
.category-details summary::-webkit-details-marker {
|
||||||
background: #eef4ea;
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-details summary::after {
|
||||||
|
grid-column: 2;
|
||||||
|
justify-self: center;
|
||||||
|
color: #8a9587;
|
||||||
|
content: ">";
|
||||||
|
transition: transform 0.16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list.depth-0 > li > .category-details > summary::after {
|
||||||
|
grid-column: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-details[open] > summary::after {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-icon {
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 8px;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--brand-green);
|
||||||
|
background: #edf4ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-icon svg {
|
||||||
|
width: 19px;
|
||||||
|
height: 19px;
|
||||||
|
fill: none;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-width: 1.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-icon.is-caviar svg circle,
|
||||||
|
.category-icon.is-meat svg circle,
|
||||||
|
.category-icon.is-cheese svg circle {
|
||||||
|
fill: currentColor;
|
||||||
|
stroke-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-category-icon {
|
||||||
|
flex: 0 0 26px;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-category-icon svg {
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-details summary a {
|
.category-details summary a {
|
||||||
@@ -1190,6 +1354,12 @@ a:hover {
|
|||||||
color: #2d372f;
|
color: #2d372f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.price-line.is-price-placeholder,
|
||||||
|
.package-price-note.is-price-placeholder {
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.price-unit-label {
|
.price-unit-label {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -1239,6 +1409,7 @@ a:hover {
|
|||||||
|
|
||||||
.package-price-note {
|
.package-price-note {
|
||||||
display: block;
|
display: block;
|
||||||
|
min-height: 15px;
|
||||||
margin: 3px 0 8px;
|
margin: 3px 0 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
@@ -1606,10 +1777,18 @@ body[data-payment-mode="cash"] .price-tier-popover:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.variant-row b {
|
.variant-row b {
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
color: var(--brand-green);
|
color: var(--brand-green);
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.variant-row b small {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
.variant-row .add-to-cart-button {
|
.variant-row .add-to-cart-button {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
@@ -1825,6 +2004,102 @@ body[data-payment-mode="cash"] .price-tier-popover:hover {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.catalog-sidebar {
|
||||||
|
position: sticky;
|
||||||
|
top: 96px;
|
||||||
|
align-self: start;
|
||||||
|
max-height: calc(100vh - 108px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-side-stack {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
max-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-menu-panel {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-menu-panel > .category-list {
|
||||||
|
max-height: calc(100vh - 290px);
|
||||||
|
padding-right: 4px;
|
||||||
|
overflow: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-side-heading {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-side-heading span {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 900;
|
||||||
|
color: var(--brand-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-side-heading small {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-mini-cart {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-mini-cart div {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-mini-cart span {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-mini-cart strong {
|
||||||
|
color: var(--brand-green);
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-mini-cart p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-mini-cart a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 36px;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #ffffff;
|
||||||
|
background: var(--brand-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-mini-cart a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background: #102f21;
|
||||||
|
}
|
||||||
|
|
||||||
.sticky-panel {
|
.sticky-panel {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 98px;
|
top: 98px;
|
||||||
@@ -1842,7 +2117,216 @@ body[data-payment-mode="cash"] .price-tier-popover:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.catalog-heading h1 {
|
.catalog-heading h1 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-benefit-banner {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(260px, 0.38fr);
|
||||||
|
gap: 22px;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(25, 91, 63, 0.16);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
padding: 24px;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 94% 16%, rgba(207, 56, 45, 0.12), transparent 26%),
|
||||||
|
linear-gradient(135deg, #fffdf8 0%, #eef5ea 100%);
|
||||||
|
box-shadow: var(--shadow-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-benefit-banner,
|
||||||
|
.invoice-benefit-banner {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-payment-mode="qr"] .cash-benefit-banner,
|
||||||
|
body[data-payment-mode="invoice"] .cash-benefit-banner {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-payment-mode="qr"] .qr-benefit-banner,
|
||||||
|
body[data-payment-mode="invoice"] .invoice-benefit-banner {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-benefit-banner {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 92% 18%, rgba(25, 91, 63, 0.14), transparent 28%),
|
||||||
|
linear-gradient(135deg, #fffdf8 0%, #eef6f4 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-benefit-banner {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 92% 18%, rgba(23, 61, 44, 0.12), transparent 28%),
|
||||||
|
linear-gradient(135deg, #fffdf8 0%, #f4f1ea 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-benefit-copy h2 {
|
||||||
|
max-width: 720px;
|
||||||
|
margin: 0 0 10px;
|
||||||
|
color: var(--brand-green);
|
||||||
|
font-size: clamp(24px, 3vw, 38px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-benefit-copy p:not(.eyebrow) {
|
||||||
|
max-width: 660px;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-receipt {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
border: 1px solid #e2dfd2;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 18px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 18px 44px rgba(22, 60, 43, 0.13);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-receipt::before,
|
||||||
|
.cash-receipt::after {
|
||||||
|
position: absolute;
|
||||||
|
right: 18px;
|
||||||
|
left: 18px;
|
||||||
|
height: 1px;
|
||||||
|
background-image: linear-gradient(90deg, #d7ded2 50%, transparent 0);
|
||||||
|
background-size: 10px 1px;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-receipt::before {
|
||||||
|
top: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-receipt::after {
|
||||||
|
bottom: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-receipt-top {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-receipt-top span,
|
||||||
|
.cash-receipt small {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-receipt-top strong {
|
||||||
|
color: var(--brand-green);
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-receipt dl {
|
||||||
|
display: grid;
|
||||||
|
gap: 9px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-receipt dl div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-receipt dt {
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 850;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-receipt dd {
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 950;
|
||||||
|
background: var(--brand-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cash-receipt small {
|
||||||
|
justify-self: center;
|
||||||
|
padding-top: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-benefit-card {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 190px;
|
||||||
|
border: 1px solid #e2dfd2;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 18px 44px rgba(22, 60, 43, 0.13);
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-benefit-card span,
|
||||||
|
.payment-benefit-card small {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 850;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-benefit-card strong {
|
||||||
|
color: var(--brand-green);
|
||||||
|
font-size: clamp(46px, 6vw, 72px);
|
||||||
|
line-height: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-benefit-banner .payment-benefit-card strong {
|
||||||
|
max-width: 220px;
|
||||||
|
color: var(--brand-green);
|
||||||
|
font-size: clamp(32px, 4vw, 48px);
|
||||||
|
line-height: 1.02;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-benefit-card p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-seo-panel {
|
||||||
|
margin-top: 26px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px 22px;
|
||||||
|
color: #536055;
|
||||||
|
background: rgba(255, 254, 250, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-seo-panel h2 {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
color: var(--brand-green);
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-seo-content {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-seo-content p,
|
||||||
|
.catalog-seo-content ul,
|
||||||
|
.catalog-seo-content ol {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.catalog-product-grid {
|
.catalog-product-grid {
|
||||||
@@ -2518,6 +3002,19 @@ body[data-payment-mode="cash"] .price-tier-popover:hover {
|
|||||||
max-height: none;
|
max-height: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.catalog-sidebar {
|
||||||
|
position: static;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-side-stack {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-menu-panel > .category-list {
|
||||||
|
max-height: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
.info-visual {
|
.info-visual {
|
||||||
min-height: 170px;
|
min-height: 170px;
|
||||||
}
|
}
|
||||||
@@ -2526,6 +3023,10 @@ body[data-payment-mode="cash"] .price-tier-popover:hover {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.payment-benefit-banner {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.hero-media {
|
.hero-media {
|
||||||
min-height: 240px;
|
min-height: 240px;
|
||||||
}
|
}
|
||||||
@@ -2556,6 +3057,20 @@ body[data-payment-mode="cash"] .price-tier-popover:hover {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.price-mode-inner {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
justify-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-mode-inner > span {
|
||||||
|
justify-self: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-mode-options {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.brand-link {
|
.brand-link {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|||||||
+89
-13
@@ -8,6 +8,7 @@ declare(strict_types=1);
|
|||||||
/** @var array<string, mixed> $pagination */
|
/** @var array<string, mixed> $pagination */
|
||||||
|
|
||||||
$heading = $currentCategory['h1'] ?? $currentCategory['name'] ?? 'Каталог продукции';
|
$heading = $currentCategory['h1'] ?? $currentCategory['name'] ?? 'Каталог продукции';
|
||||||
|
$categoryDescription = trim((string) ($currentCategory['description'] ?? ''));
|
||||||
$pagination = $pagination ?? [
|
$pagination = $pagination ?? [
|
||||||
'base_path' => '/catalog',
|
'base_path' => '/catalog',
|
||||||
'total' => count($products),
|
'total' => count($products),
|
||||||
@@ -21,14 +22,28 @@ $pagination = $pagination ?? [
|
|||||||
?>
|
?>
|
||||||
<section class="catalog-layout">
|
<section class="catalog-layout">
|
||||||
<aside class="catalog-sidebar">
|
<aside class="catalog-sidebar">
|
||||||
<div class="panel sticky-panel">
|
<div class="catalog-side-stack">
|
||||||
<h2>Категории</h2>
|
<div class="panel catalog-menu-panel">
|
||||||
<?php
|
<div class="catalog-side-heading">
|
||||||
$items = $categories;
|
<span>Каталог</span>
|
||||||
$depth = 0;
|
<small><?= e((string) $pagination['total']) ?> позиций</small>
|
||||||
$currentSlug = $currentCategory['slug'] ?? '';
|
</div>
|
||||||
require base_path('views/partials/category-tree.php');
|
<?php
|
||||||
?>
|
$items = $categories;
|
||||||
|
$depth = 0;
|
||||||
|
$currentSlug = $currentCategory['slug'] ?? '';
|
||||||
|
require base_path('views/partials/category-tree.php');
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel catalog-mini-cart" data-mini-cart>
|
||||||
|
<div>
|
||||||
|
<span>Корзина</span>
|
||||||
|
<strong data-mini-cart-total>0 руб.</strong>
|
||||||
|
</div>
|
||||||
|
<p><span data-mini-cart-count>0</span> позиций</p>
|
||||||
|
<a href="/cart">Перейти</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@@ -36,13 +51,65 @@ $pagination = $pagination ?? [
|
|||||||
<div class="catalog-heading">
|
<div class="catalog-heading">
|
||||||
<p class="eyebrow"><?= $currentCategory === null ? 'Полный каталог' : 'Категория' ?></p>
|
<p class="eyebrow"><?= $currentCategory === null ? 'Полный каталог' : 'Категория' ?></p>
|
||||||
<h1><?= e($heading) ?></h1>
|
<h1><?= e($heading) ?></h1>
|
||||||
<?php if (!empty($currentCategory['description'])): ?>
|
|
||||||
<p><?= e($currentCategory['description']) ?></p>
|
|
||||||
<?php else: ?>
|
|
||||||
<p class="muted">Выберите категорию слева или смотрите товары сразу в каталоге.</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<section class="payment-benefit-banner cash-benefit-banner" aria-label="Выгода при оплате наличными">
|
||||||
|
<div class="cash-benefit-copy">
|
||||||
|
<p class="eyebrow">Выгода считается автоматически</p>
|
||||||
|
<h2>Оплачивайте наличными при получении и получайте лучшие условия</h2>
|
||||||
|
<p>Добавляйте товары в корзину, а сайт сам пересчитает итог от суммы заказа. Ничего считать вручную не нужно.</p>
|
||||||
|
</div>
|
||||||
|
<div class="cash-receipt" aria-hidden="true">
|
||||||
|
<div class="cash-receipt-top">
|
||||||
|
<span>Чек выгоды</span>
|
||||||
|
<strong>Рыбсток</strong>
|
||||||
|
</div>
|
||||||
|
<dl>
|
||||||
|
<div>
|
||||||
|
<dt>от 7 000р.</dt>
|
||||||
|
<dd>-300р.</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>от 20 000р.</dt>
|
||||||
|
<dd>-5%</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>от 50 000р.</dt>
|
||||||
|
<dd>-7%</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
<small>Пересчет появится в корзине сразу</small>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="payment-benefit-banner qr-benefit-banner" aria-label="Преимущество оплаты QR-кодом">
|
||||||
|
<div class="cash-benefit-copy">
|
||||||
|
<p class="eyebrow">Удобная оплата при получении</p>
|
||||||
|
<h2>Принимаем оплату QR-кодом без комиссии даже с кредитной карты</h2>
|
||||||
|
<p>После подтверждения заказа менеджер согласует получение, а при передаче товара можно оплатить QR-кодом без лишних комиссий.</p>
|
||||||
|
</div>
|
||||||
|
<div class="payment-benefit-card" aria-hidden="true">
|
||||||
|
<span>QR-оплата</span>
|
||||||
|
<strong>0%</strong>
|
||||||
|
<p>комиссия для покупателя</p>
|
||||||
|
<small>Наличными пользоваться необязательно</small>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="payment-benefit-banner invoice-benefit-banner" aria-label="Оплата по расчетному счету">
|
||||||
|
<div class="cash-benefit-copy">
|
||||||
|
<p class="eyebrow">Для организаций и крупных заказов</p>
|
||||||
|
<h2>Счет на оплату выставляем день в день</h2>
|
||||||
|
<p>После подтверждения заказа менеджер подготовит счет и согласует документы. Безналичный расчет доступен для заказов от 30 000р.</p>
|
||||||
|
</div>
|
||||||
|
<div class="payment-benefit-card" aria-hidden="true">
|
||||||
|
<span>Расчетный счет</span>
|
||||||
|
<strong>день в день</strong>
|
||||||
|
<p>после подтверждения заказа</p>
|
||||||
|
<small>Документы согласуем с менеджером</small>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<?php if ($products === []): ?>
|
<?php if ($products === []): ?>
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<p class="muted">В этой категории товары появятся после переноса каталога со старого сайта.</p>
|
<p class="muted">В этой категории товары появятся после переноса каталога со старого сайта.</p>
|
||||||
@@ -74,5 +141,14 @@ $pagination = $pagination ?? [
|
|||||||
</nav>
|
</nav>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($categoryDescription !== ''): ?>
|
||||||
|
<section class="catalog-seo-panel">
|
||||||
|
<h2><?= e($heading) ?></h2>
|
||||||
|
<div class="catalog-seo-content">
|
||||||
|
<?= (string) $categoryDescription ?>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<?php endif; ?>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -7,6 +7,31 @@ declare(strict_types=1);
|
|||||||
/** @var array<int, array<string, mixed>> $images */
|
/** @var array<int, array<string, mixed>> $images */
|
||||||
|
|
||||||
$mainImage = $product['main_image_path'] ?? '';
|
$mainImage = $product['main_image_path'] ?? '';
|
||||||
|
$showUnitPriceForVariant = static function (array $variant) use ($product): bool {
|
||||||
|
if (function_exists('product_should_show_unit_price')) {
|
||||||
|
return product_should_show_unit_price($product, $variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
$unit = (string) ($variant['unit'] ?? $product['base_unit'] ?? '');
|
||||||
|
if (!in_array($unit, ['kg', 'кг'], true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($variant['price_per_unit'] ?? null) === null || (float) $variant['price_per_unit'] <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$packageQuantity = (float) ($variant['package_quantity'] ?? 0);
|
||||||
|
if ($packageQuantity < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lower = static function (string $value): string {
|
||||||
|
return function_exists('mb_strtolower') ? mb_strtolower($value, 'UTF-8') : strtolower($value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return preg_match('/(^|[\s,.;])\d+(?:[.,]\d+)?\s*(мл|шт)\.?($|[\s,.;])/u', $lower((string) ($variant['name'] ?? ''))) !== 1;
|
||||||
|
};
|
||||||
?>
|
?>
|
||||||
<section
|
<section
|
||||||
class="product-detail price-aware"
|
class="product-detail price-aware"
|
||||||
@@ -43,6 +68,7 @@ $mainImage = $product['main_image_path'] ?? '';
|
|||||||
<section class="variant-list" aria-label="Фасовки товара">
|
<section class="variant-list" aria-label="Фасовки товара">
|
||||||
<h2>Фасовки</h2>
|
<h2>Фасовки</h2>
|
||||||
<?php foreach ($variants as $variant): ?>
|
<?php foreach ($variants as $variant): ?>
|
||||||
|
<?php $showUnitPrice = $showUnitPriceForVariant($variant); ?>
|
||||||
<div
|
<div
|
||||||
class="variant-row"
|
class="variant-row"
|
||||||
data-variant-id="<?= e((string) ($variant['id'] ?? '')) ?>"
|
data-variant-id="<?= e((string) ($variant['id'] ?? '')) ?>"
|
||||||
@@ -52,11 +78,12 @@ $mainImage = $product['main_image_path'] ?? '';
|
|||||||
>
|
>
|
||||||
<strong><?= e((string) $variant['name']) ?></strong>
|
<strong><?= e((string) $variant['name']) ?></strong>
|
||||||
<span>
|
<span>
|
||||||
<?php if ($variant['price_per_unit'] !== null): ?>
|
<?php if ($showUnitPrice): ?>
|
||||||
<span data-dynamic-unit-price data-base-unit-price="<?= e((string) $variant['price_per_unit']) ?>"><?= e(money_label($variant['price_per_unit'])) ?></span> / <?= e(unit_label((string) $variant['unit'])) ?>
|
<span data-dynamic-unit-price data-base-unit-price="<?= e((string) $variant['price_per_unit']) ?>"><?= e(money_label($variant['price_per_unit'])) ?></span> / <?= e(unit_label((string) $variant['unit'])) ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</span>
|
</span>
|
||||||
<b>
|
<b>
|
||||||
|
<small>За фасовку <?= e((string) $variant['name']) ?></small>
|
||||||
<?php if (!empty($variant['old_package_price'])): ?>
|
<?php if (!empty($variant['old_package_price'])): ?>
|
||||||
<span class="old-price"><?= e(money_label($variant['old_package_price'])) ?></span>
|
<span class="old-price"><?= e(money_label($variant['old_package_price'])) ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
+101
-21
@@ -71,15 +71,17 @@ try {
|
|||||||
|
|
||||||
<?php require base_path('views/partials/mega-category-menu.php'); ?>
|
<?php require base_path('views/partials/mega-category-menu.php'); ?>
|
||||||
|
|
||||||
<section class="price-mode-bar" aria-label="Режим отображения цен">
|
<section class="price-mode-bar" aria-label="Система оплаты при получении">
|
||||||
<div class="price-mode-inner">
|
<div class="price-mode-inner">
|
||||||
<span>Показывать цены:</span>
|
<span>Выберите систему оплаты при получении:</span>
|
||||||
<div class="price-mode-options" role="group" aria-label="Способ оплаты">
|
<div class="price-mode-options" role="group" aria-label="Способ оплаты">
|
||||||
<button type="button" class="price-mode-button is-active" data-payment-mode="cash">Наличными</button>
|
<button type="button" class="price-mode-button price-mode-button-cash is-active" data-payment-mode="cash">
|
||||||
|
<span>Наличными</span>
|
||||||
|
<b>Выгодно от 7000р.</b>
|
||||||
|
</button>
|
||||||
<button type="button" class="price-mode-button" data-payment-mode="qr">QR-код</button>
|
<button type="button" class="price-mode-button" data-payment-mode="qr">QR-код</button>
|
||||||
<button type="button" class="price-mode-button" data-payment-mode="invoice">Расчетный счет</button>
|
<button type="button" class="price-mode-button" data-payment-mode="invoice">Расчетный счет</button>
|
||||||
</div>
|
</div>
|
||||||
<small data-price-mode-note>Скидка наличными применится автоматически от суммы корзины.</small>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -245,22 +247,33 @@ try {
|
|||||||
const total = getCartTotal();
|
const total = getCartTotal();
|
||||||
|
|
||||||
if (payment === 'invoice') {
|
if (payment === 'invoice') {
|
||||||
return { multiplier: 1.08, label: 'Оплата по расчетному счету: +8%' };
|
return { multiplier: 1.08, fixedDiscount: 0, label: 'Оплата по расчетному счету: +8%' };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payment === 'qr') {
|
if (payment === 'qr') {
|
||||||
return { multiplier: 1, label: 'QR-код: базовая цена' };
|
return { multiplier: 1, fixedDiscount: 0, label: 'QR-код: базовая цена' };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total >= 50000) {
|
if (total >= 50000) {
|
||||||
return { multiplier: 0.93, label: 'Наличные: цена от 50 000р.' };
|
return { multiplier: 0.93, fixedDiscount: 0, label: 'Наличные: цена от 50 000р.' };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total >= 20000) {
|
if (total >= 20000) {
|
||||||
return { multiplier: 0.95, label: 'Наличные: цена от 20 000р.' };
|
return { multiplier: 0.95, fixedDiscount: 0, label: 'Наличные: цена от 20 000р.' };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { multiplier: 1, label: 'Наличные: базовая цена до 20 000р.' };
|
if (total >= 7000) {
|
||||||
|
return { multiplier: 1, fixedDiscount: 300, label: 'Наличные: -300р. от 7000р.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { multiplier: 1, fixedDiscount: 0, label: 'Наличные: базовая цена до 7000р.' };
|
||||||
|
};
|
||||||
|
|
||||||
|
const cartCurrentTotal = (items = getCartItems()) => {
|
||||||
|
const mode = priceMode();
|
||||||
|
const subtotal = Math.floor(cartBaseTotal(items) * mode.multiplier);
|
||||||
|
|
||||||
|
return Math.max(0, subtotal - Number(mode.fixedDiscount || 0));
|
||||||
};
|
};
|
||||||
|
|
||||||
const tierValues = (base) => ({
|
const tierValues = (base) => ({
|
||||||
@@ -293,7 +306,9 @@ try {
|
|||||||
if (unitNode || packageNode) {
|
if (unitNode || packageNode) {
|
||||||
const unitBase = Number(unitNode?.dataset.baseUnitPrice || 0);
|
const unitBase = Number(unitNode?.dataset.baseUnitPrice || 0);
|
||||||
const packageBase = Number(packageNode?.dataset.basePackagePrice || 0);
|
const packageBase = Number(packageNode?.dataset.basePackagePrice || 0);
|
||||||
const base = unitBase > 0 ? unitBase : packageBase;
|
const unitPriceRow = card.querySelector('[data-unit-price-row]');
|
||||||
|
const showUnitPrice = card.dataset.showUnitPrice === '1' && !unitPriceRow?.classList.contains('is-price-placeholder');
|
||||||
|
const base = showUnitPrice && unitBase > 0 ? unitBase : packageBase;
|
||||||
|
|
||||||
const tiers = tierValues(base);
|
const tiers = tierValues(base);
|
||||||
const tierBase = card.querySelector('[data-tier-base]');
|
const tierBase = card.querySelector('[data-tier-base]');
|
||||||
@@ -304,7 +319,9 @@ try {
|
|||||||
if (tierBase) tierBase.textContent = formatRub(tiers.base);
|
if (tierBase) tierBase.textContent = formatRub(tiers.base);
|
||||||
if (tierCash20) tierCash20.textContent = formatRub(tiers.cash20);
|
if (tierCash20) tierCash20.textContent = formatRub(tiers.cash20);
|
||||||
if (tierCash50) tierCash50.textContent = formatRub(tiers.cash50);
|
if (tierCash50) tierCash50.textContent = formatRub(tiers.cash50);
|
||||||
if (tierUnitLabel) tierUnitLabel.textContent = card.querySelector('[data-unit-label]')?.textContent || 'шт.';
|
if (tierUnitLabel) tierUnitLabel.textContent = showUnitPrice
|
||||||
|
? (card.querySelector('[data-unit-label]')?.textContent || 'кг')
|
||||||
|
: 'фасовку';
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -318,6 +335,20 @@ try {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateMiniCart = () => {
|
||||||
|
const items = getCartItems();
|
||||||
|
const count = items.reduce((sum, item) => sum + Number(item.quantity || 0), 0);
|
||||||
|
const total = cartCurrentTotal(items);
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-mini-cart-count]').forEach((node) => {
|
||||||
|
node.textContent = String(count);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-mini-cart-total]').forEach((node) => {
|
||||||
|
node.textContent = formatRub(total);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const itemCurrentPrice = (item) => Math.floor(Number(item.basePackagePrice || 0) * priceMode().multiplier);
|
const itemCurrentPrice = (item) => Math.floor(Number(item.basePackagePrice || 0) * priceMode().multiplier);
|
||||||
|
|
||||||
const renderCartPage = () => {
|
const renderCartPage = () => {
|
||||||
@@ -336,7 +367,7 @@ try {
|
|||||||
const savingNode = cartPage.querySelector('[data-cart-saving-total]');
|
const savingNode = cartPage.querySelector('[data-cart-saving-total]');
|
||||||
const items = getCartItems();
|
const items = getCartItems();
|
||||||
const baseTotal = cartBaseTotal(items);
|
const baseTotal = cartBaseTotal(items);
|
||||||
const currentTotal = Math.floor(baseTotal * priceMode().multiplier);
|
const currentTotal = cartCurrentTotal(items);
|
||||||
const savingTotal = Math.max(0, baseTotal - currentTotal);
|
const savingTotal = Math.max(0, baseTotal - currentTotal);
|
||||||
|
|
||||||
if (emptyNode) {
|
if (emptyNode) {
|
||||||
@@ -397,20 +428,12 @@ try {
|
|||||||
button.classList.toggle('is-active', button.dataset.paymentMode === payment);
|
button.classList.toggle('is-active', button.dataset.paymentMode === payment);
|
||||||
});
|
});
|
||||||
|
|
||||||
const note = document.querySelector('[data-price-mode-note]');
|
|
||||||
if (note) {
|
|
||||||
const total = getCartTotal();
|
|
||||||
const mode = priceMode();
|
|
||||||
const suffix = payment === 'cash' && total < 50000
|
|
||||||
? (total < 20000 ? ' До цены от 20 000р. осталось ' + formatRub(20000 - total) + '.' : ' До цены от 50 000р. осталось ' + formatRub(50000 - total) + '.')
|
|
||||||
: '';
|
|
||||||
note.textContent = mode.label + '. Сумма корзины: ' + formatRub(total) + '.' + suffix;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateAllPrices = () => {
|
const updateAllPrices = () => {
|
||||||
updateModeButtons();
|
updateModeButtons();
|
||||||
updateCartCount();
|
updateCartCount();
|
||||||
|
updateMiniCart();
|
||||||
updateDynamicPrices(document);
|
updateDynamicPrices(document);
|
||||||
document.querySelectorAll('.product-card.price-aware').forEach(updateCardPrices);
|
document.querySelectorAll('.product-card.price-aware').forEach(updateCardPrices);
|
||||||
renderCartPage();
|
renderCartPage();
|
||||||
@@ -427,9 +450,46 @@ try {
|
|||||||
},
|
},
|
||||||
currentMode: priceMode,
|
currentMode: priceMode,
|
||||||
cartBaseTotal,
|
cartBaseTotal,
|
||||||
|
cartCurrentTotal,
|
||||||
update: updateAllPrices
|
update: updateAllPrices
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const catalogScrollKey = 'rybstock.catalogScrollY';
|
||||||
|
const catalogMenuScrollKey = 'rybstock.catalogMenuScrollY';
|
||||||
|
|
||||||
|
if (window.location.pathname.startsWith('/catalog')) {
|
||||||
|
const savedCatalogScroll = sessionStorage.getItem(catalogScrollKey);
|
||||||
|
const savedCatalogMenuScroll = sessionStorage.getItem(catalogMenuScrollKey);
|
||||||
|
const catalogMenu = document.querySelector('.catalog-menu-panel > .category-list');
|
||||||
|
|
||||||
|
if (savedCatalogScroll !== null) {
|
||||||
|
sessionStorage.removeItem(catalogScrollKey);
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: Math.max(0, parseInt(savedCatalogScroll, 10) || 0),
|
||||||
|
left: 0,
|
||||||
|
behavior: 'auto'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedCatalogMenuScroll !== null && catalogMenu) {
|
||||||
|
sessionStorage.removeItem(catalogMenuScrollKey);
|
||||||
|
catalogMenu.scrollTop = Math.max(0, parseInt(savedCatalogMenuScroll, 10) || 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
const categoryLink = event.target.closest('.catalog-menu-panel .category-list a[href^="/catalog/"]');
|
||||||
|
|
||||||
|
if (!categoryLink || event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStorage.setItem(catalogScrollKey, String(window.scrollY));
|
||||||
|
sessionStorage.setItem(catalogMenuScrollKey, String(categoryLink.closest('.catalog-menu-panel > .category-list')?.scrollTop || 0));
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener('click', (event) => {
|
document.addEventListener('click', (event) => {
|
||||||
const modeButton = event.target.closest('[data-payment-mode]');
|
const modeButton = event.target.closest('[data-payment-mode]');
|
||||||
|
|
||||||
@@ -594,6 +654,26 @@ try {
|
|||||||
const unitLabelNoteNode = card.querySelector('[data-unit-label-note]');
|
const unitLabelNoteNode = card.querySelector('[data-unit-label-note]');
|
||||||
const oldPriceNode = card.querySelector('[data-old-package-price-display]');
|
const oldPriceNode = card.querySelector('[data-old-package-price-display]');
|
||||||
const oldUnitPriceNode = card.querySelector('[data-old-unit-price-display]');
|
const oldUnitPriceNode = card.querySelector('[data-old-unit-price-display]');
|
||||||
|
const unitPriceRow = card.querySelector('[data-unit-price-row]');
|
||||||
|
const packageTierTrigger = card.querySelector('[data-package-tier-trigger]');
|
||||||
|
const packagePriceNote = card.querySelector('[data-package-price-note]');
|
||||||
|
const showUnitPrice = button.dataset.showUnitPrice === '1';
|
||||||
|
|
||||||
|
card.dataset.showUnitPrice = showUnitPrice ? '1' : '0';
|
||||||
|
|
||||||
|
if (unitPriceRow) {
|
||||||
|
unitPriceRow.classList.toggle('is-price-placeholder', !showUnitPrice);
|
||||||
|
unitPriceRow.setAttribute('aria-hidden', showUnitPrice ? 'false' : 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packageTierTrigger) {
|
||||||
|
packageTierTrigger.hidden = showUnitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packagePriceNote) {
|
||||||
|
packagePriceNote.classList.toggle('is-price-placeholder', !showUnitPrice);
|
||||||
|
packagePriceNote.setAttribute('aria-hidden', showUnitPrice ? 'false' : 'true');
|
||||||
|
}
|
||||||
|
|
||||||
if (unitNode) {
|
if (unitNode) {
|
||||||
unitNode.textContent = button.dataset.unit || 'ед.';
|
unitNode.textContent = button.dataset.unit || 'ед.';
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
if (!function_exists('category_tree_icon')) {
|
||||||
|
function category_tree_icon(string $name): string
|
||||||
|
{
|
||||||
|
$lowerName = mb_strtolower($name);
|
||||||
|
|
||||||
|
return match (true) {
|
||||||
|
str_contains($lowerName, 'рыб') => 'fish',
|
||||||
|
str_contains($lowerName, 'морепроду') => 'shrimp',
|
||||||
|
str_contains($lowerName, 'икра') => 'caviar',
|
||||||
|
str_contains($lowerName, 'мяс') => 'meat',
|
||||||
|
str_contains($lowerName, 'сыр') => 'cheese',
|
||||||
|
str_contains($lowerName, 'полуфаб') => 'box',
|
||||||
|
str_contains($lowerName, 'овощ') || str_contains($lowerName, 'фрукт') || str_contains($lowerName, 'ягод') || str_contains($lowerName, 'гриб') => 'leaf',
|
||||||
|
str_contains($lowerName, 'бакале') => 'jar',
|
||||||
|
str_contains($lowerName, 'молоч') => 'milk',
|
||||||
|
default => 'grid',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('category_tree_icon_svg')) {
|
||||||
|
function category_tree_icon_svg(string $icon): string
|
||||||
|
{
|
||||||
|
return match ($icon) {
|
||||||
|
'fish' => '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 12c3.2-4.2 7.1-6.2 11.6-6 2.3.1 4.2 1.1 5.8 3l1.6-1.5v9L20.4 15c-1.6 1.9-3.6 2.9-5.8 3-4.5.2-8.4-1.8-11.6-6Z"/><path d="M7.5 12h.1"/><path d="M14 7.5c-1.4 1.2-2.1 2.7-2.1 4.5s.7 3.3 2.1 4.5"/></svg>',
|
||||||
|
'shrimp' => '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18.5 7.5c-2.8-2.3-7.5-1.6-10.2.9-2.7 2.4-2.8 6-.3 8.1 2.3 2 6 1.9 8.2-.2 1.6-1.5 1.8-3.8.4-5.2-1.2-1.2-3.3-1.3-4.7-.2"/><path d="M19 8l2-2"/><path d="M18.8 8.2l2.4.8"/><path d="M8.8 16.8 7 19"/><path d="M11.4 17.7 10.5 20"/><path d="M14 17.2l.3 2.4"/></svg>',
|
||||||
|
'leaf' => '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 4c-7.8.1-12.8 3-15 8.6C3.7 16 6 20 9.8 20c5.7 0 9.2-5.6 10.2-16Z"/><path d="M5 19c3.2-4.4 7.2-7.5 12-9.2"/></svg>',
|
||||||
|
'caviar' => '<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="8" cy="8" r="2.4"/><circle cx="15.5" cy="7.5" r="2"/><circle cx="12" cy="14" r="2.5"/><circle cx="6.8" cy="16.2" r="1.8"/><circle cx="17.5" cy="16.5" r="2.1"/></svg>',
|
||||||
|
'jar' => '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 3h8"/><path d="M9 6h6"/><path d="M7 9c0-1.7 1.4-3 3-3h4c1.7 0 3 1.3 3 3v9c0 1.7-1.3 3-3 3h-4c-1.6 0-3-1.3-3-3V9Z"/><path d="M7 13h10"/></svg>',
|
||||||
|
'box' => '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m4 8 8-4 8 4-8 4-8-4Z"/><path d="M4 8v8l8 4 8-4V8"/><path d="M12 12v8"/></svg>',
|
||||||
|
'meat' => '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5.5 15.5c-2.8-3.9.5-9.1 5.6-9.6 4.1-.4 7.9 1.9 8.7 5.2.8 3.5-2.1 6.8-6.2 7.2-3.4.4-6.3-.7-8.1-2.8Z"/><circle cx="11" cy="12" r="2.4"/></svg>',
|
||||||
|
'cheese' => '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 16 19 7v10H4v-1Z"/><path d="M4 16v3h15v-2"/><circle cx="14.5" cy="13" r="1"/><circle cx="17" cy="10.5" r=".8"/><circle cx="9.5" cy="15" r=".8"/></svg>',
|
||||||
|
'milk' => '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 3h6v4l2 3v10H7V10l2-3V3Z"/><path d="M9 7h6"/><path d="M7 13h10"/></svg>',
|
||||||
|
default => '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 5h6v6H5z"/><path d="M13 5h6v6h-6z"/><path d="M5 13h6v6H5z"/><path d="M13 13h6v6h-6z"/></svg>',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ declare(strict_types=1);
|
|||||||
/** @var int $depth */
|
/** @var int $depth */
|
||||||
/** @var string $currentSlug */
|
/** @var string $currentSlug */
|
||||||
|
|
||||||
|
require_once __DIR__ . '/category-icons.php';
|
||||||
|
|
||||||
if (!function_exists('category_tree_has_current')) {
|
if (!function_exists('category_tree_has_current')) {
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $category
|
* @param array<string, mixed> $category
|
||||||
@@ -35,10 +37,14 @@ if (!function_exists('category_tree_has_current')) {
|
|||||||
<?php $isCurrent = ($currentSlug ?? '') === $category['slug']; ?>
|
<?php $isCurrent = ($currentSlug ?? '') === $category['slug']; ?>
|
||||||
<?php $hasChildren = !empty($category['children']); ?>
|
<?php $hasChildren = !empty($category['children']); ?>
|
||||||
<?php $shouldOpen = category_tree_has_current($category, $currentSlug ?? ''); ?>
|
<?php $shouldOpen = category_tree_has_current($category, $currentSlug ?? ''); ?>
|
||||||
<li class="<?= $isCurrent ? 'is-current' : '' ?>">
|
<?php $iconName = $depth === 0 ? category_tree_icon((string) $category['name']) : ''; ?>
|
||||||
|
<li class="<?= $isCurrent ? 'is-current' : '' ?><?= $hasChildren ? ' has-children' : '' ?>">
|
||||||
<?php if ($hasChildren): ?>
|
<?php if ($hasChildren): ?>
|
||||||
<details class="category-details" <?= $shouldOpen ? 'open' : '' ?>>
|
<details class="category-details" <?= $shouldOpen ? 'open' : '' ?>>
|
||||||
<summary>
|
<summary>
|
||||||
|
<?php if ($depth === 0): ?>
|
||||||
|
<span class="category-icon is-<?= e($iconName) ?>" aria-hidden="true"><?= category_tree_icon_svg($iconName) ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
<a class="<?= $isCurrent ? 'is-current-link' : '' ?>" href="/catalog/<?= e($category['slug']) ?>"><?= e($category['name']) ?></a>
|
<a class="<?= $isCurrent ? 'is-current-link' : '' ?>" href="/catalog/<?= e($category['slug']) ?>"><?= e($category['name']) ?></a>
|
||||||
</summary>
|
</summary>
|
||||||
<?php
|
<?php
|
||||||
@@ -51,7 +57,12 @@ if (!function_exists('category_tree_has_current')) {
|
|||||||
?>
|
?>
|
||||||
</details>
|
</details>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<a href="/catalog/<?= e($category['slug']) ?>"><?= e($category['name']) ?></a>
|
<a href="/catalog/<?= e($category['slug']) ?>">
|
||||||
|
<?php if ($depth === 0): ?>
|
||||||
|
<span class="category-icon is-<?= e($iconName) ?>" aria-hidden="true"><?= category_tree_icon_svg($iconName) ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<span><?= e($category['name']) ?></span>
|
||||||
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</li>
|
</li>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
/** @var array<int, array<string, mixed>> $megaCategories */
|
/** @var array<int, array<string, mixed>> $megaCategories */
|
||||||
|
|
||||||
|
require_once __DIR__ . '/category-icons.php';
|
||||||
|
|
||||||
if (!function_exists('render_mega_category_children')) {
|
if (!function_exists('render_mega_category_children')) {
|
||||||
/**
|
/**
|
||||||
* @param array<int, array<string, mixed>> $children
|
* @param array<int, array<string, mixed>> $children
|
||||||
@@ -30,8 +32,12 @@ if (!function_exists('render_mega_category_children')) {
|
|||||||
<nav class="category-mega-bar" aria-label="Категории товаров">
|
<nav class="category-mega-bar" aria-label="Категории товаров">
|
||||||
<div class="category-mega-inner">
|
<div class="category-mega-inner">
|
||||||
<?php foreach ($megaCategories as $category): ?>
|
<?php foreach ($megaCategories as $category): ?>
|
||||||
|
<?php $iconName = category_tree_icon((string) $category['name']); ?>
|
||||||
<div class="mega-item">
|
<div class="mega-item">
|
||||||
<a class="mega-root" href="/catalog/<?= e((string) $category['slug']) ?>"><?= e((string) $category['name']) ?></a>
|
<a class="mega-root" href="/catalog/<?= e((string) $category['slug']) ?>">
|
||||||
|
<span class="mega-category-icon category-icon is-<?= e($iconName) ?>" aria-hidden="true"><?= category_tree_icon_svg($iconName) ?></span>
|
||||||
|
<span><?= e((string) $category['name']) ?></span>
|
||||||
|
</a>
|
||||||
<?php render_mega_category_children($category['children'] ?? []); ?>
|
<?php render_mega_category_children($category['children'] ?? []); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|||||||
@@ -12,6 +12,31 @@ $activeVariant = $variants[0] ?? [
|
|||||||
'package_quantity' => $product['package_quantity'] ?? 0,
|
'package_quantity' => $product['package_quantity'] ?? 0,
|
||||||
'old_package_price' => $product['old_package_price'] ?? null,
|
'old_package_price' => $product['old_package_price'] ?? null,
|
||||||
];
|
];
|
||||||
|
$showUnitPriceForVariant = static function (array $variant) use ($product): bool {
|
||||||
|
if (function_exists('product_should_show_unit_price')) {
|
||||||
|
return product_should_show_unit_price($product, $variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
$unit = (string) ($variant['unit'] ?? $product['base_unit'] ?? '');
|
||||||
|
if (!in_array($unit, ['kg', 'кг'], true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($variant['price_per_unit'] ?? null) === null || (float) $variant['price_per_unit'] <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$packageQuantity = (float) ($variant['package_quantity'] ?? 0);
|
||||||
|
if ($packageQuantity < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lower = static function (string $value): string {
|
||||||
|
return function_exists('mb_strtolower') ? mb_strtolower($value, 'UTF-8') : strtolower($value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return preg_match('/(^|[\s,.;])\d+(?:[.,]\d+)?\s*(мл|шт)\.?($|[\s,.;])/u', $lower((string) ($variant['name'] ?? ''))) !== 1;
|
||||||
|
};
|
||||||
$cardId = 'product-card-' . (int) ($product['id'] ?? 0);
|
$cardId = 'product-card-' . (int) ($product['id'] ?? 0);
|
||||||
$activePackagePrice = (float) ($activeVariant['package_price'] ?? 0);
|
$activePackagePrice = (float) ($activeVariant['package_price'] ?? 0);
|
||||||
$activeUnitPrice = $activeVariant['price_per_unit'] === null ? null : (float) $activeVariant['price_per_unit'];
|
$activeUnitPrice = $activeVariant['price_per_unit'] === null ? null : (float) $activeVariant['price_per_unit'];
|
||||||
@@ -29,8 +54,9 @@ $activeOldUnitPrice = ($activeOldPackagePrice !== null && $activePackageQuantity
|
|||||||
if ($activeOldUnitPrice !== null && $activeOldUnitPrice <= $activeUnitPrice) {
|
if ($activeOldUnitPrice !== null && $activeOldUnitPrice <= $activeUnitPrice) {
|
||||||
$activeOldUnitPrice = null;
|
$activeOldUnitPrice = null;
|
||||||
}
|
}
|
||||||
$activeTierBase = $activeUnitPrice ?? $activePackagePrice;
|
$activeShowsUnitPrice = $showUnitPriceForVariant($activeVariant);
|
||||||
$activeTierUnit = unit_label((string) ($activeVariant['unit'] ?? '')) ?: 'шт.';
|
$activeTierBase = $activeShowsUnitPrice ? ($activeUnitPrice ?? $activePackagePrice) : $activePackagePrice;
|
||||||
|
$activeTierUnit = $activeShowsUnitPrice ? unit_label((string) ($activeVariant['unit'] ?? '')) : 'фасовку';
|
||||||
$isPublished = (int) ($product['is_published'] ?? 1) === 1;
|
$isPublished = (int) ($product['is_published'] ?? 1) === 1;
|
||||||
$isAvailable = (int) ($product['is_available'] ?? 1) === 1;
|
$isAvailable = (int) ($product['is_available'] ?? 1) === 1;
|
||||||
$isPurchasable = $isPublished && $isAvailable && $activePackagePrice > 0;
|
$isPurchasable = $isPublished && $isAvailable && $activePackagePrice > 0;
|
||||||
@@ -42,6 +68,7 @@ $isPurchasable = $isPublished && $isAvailable && $activePackagePrice > 0;
|
|||||||
data-product-name="<?= e((string) ($product['name'] ?? '')) ?>"
|
data-product-name="<?= e((string) ($product['name'] ?? '')) ?>"
|
||||||
data-product-slug="<?= e((string) ($product['slug'] ?? '')) ?>"
|
data-product-slug="<?= e((string) ($product['slug'] ?? '')) ?>"
|
||||||
data-product-image="<?= e((string) ($product['main_image_path'] ?? '')) ?>"
|
data-product-image="<?= e((string) ($product['main_image_path'] ?? '')) ?>"
|
||||||
|
data-show-unit-price="<?= $activeShowsUnitPrice ? '1' : '0' ?>"
|
||||||
>
|
>
|
||||||
<?php if (!$isPurchasable): ?>
|
<?php if (!$isPurchasable): ?>
|
||||||
<span class="product-unavailable-badge"><?= !$isPublished ? 'Ожидается поступление' : 'Нет в наличии' ?></span>
|
<span class="product-unavailable-badge"><?= !$isPublished ? 'Ожидается поступление' : 'Нет в наличии' ?></span>
|
||||||
@@ -95,6 +122,7 @@ $isPurchasable = $isPublished && $isAvailable && $activePackagePrice > 0;
|
|||||||
if ($variantOldUnitPrice !== null && $variantOldUnitPrice <= $variantUnitPrice) {
|
if ($variantOldUnitPrice !== null && $variantOldUnitPrice <= $variantUnitPrice) {
|
||||||
$variantOldUnitPrice = null;
|
$variantOldUnitPrice = null;
|
||||||
}
|
}
|
||||||
|
$variantShowsUnitPrice = $showUnitPriceForVariant($variant);
|
||||||
?>
|
?>
|
||||||
<button
|
<button
|
||||||
class="variant-choice-button<?= $index === 0 ? ' is-active' : '' ?>"
|
class="variant-choice-button<?= $index === 0 ? ' is-active' : '' ?>"
|
||||||
@@ -110,6 +138,7 @@ $isPurchasable = $isPublished && $isAvailable && $activePackagePrice > 0;
|
|||||||
data-variant-unit-price-label="<?= e(money_label($variant['price_per_unit'])) ?>"
|
data-variant-unit-price-label="<?= e(money_label($variant['price_per_unit'])) ?>"
|
||||||
data-variant-package-price-label="<?= e(money_label($variant['package_price'])) ?>"
|
data-variant-package-price-label="<?= e(money_label($variant['package_price'])) ?>"
|
||||||
data-old-price="<?= e($variantOldPackagePrice === null ? '' : money_label($variantOldPackagePrice)) ?>"
|
data-old-price="<?= e($variantOldPackagePrice === null ? '' : money_label($variantOldPackagePrice)) ?>"
|
||||||
|
data-show-unit-price="<?= $variantShowsUnitPrice ? '1' : '0' ?>"
|
||||||
>
|
>
|
||||||
<?= e((string) $variant['name']) ?>
|
<?= e((string) $variant['name']) ?>
|
||||||
</button>
|
</button>
|
||||||
@@ -118,8 +147,8 @@ $isPurchasable = $isPublished && $isAvailable && $activePackagePrice > 0;
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($isPurchasable && $activeVariant['price_per_unit'] !== null): ?>
|
<?php if ($isPurchasable): ?>
|
||||||
<span class="price-line">
|
<span class="price-line<?= $activeShowsUnitPrice ? '' : ' is-price-placeholder' ?>" data-unit-price-row aria-hidden="<?= $activeShowsUnitPrice ? 'false' : 'true' ?>">
|
||||||
<span class="price-unit-label">Цена за <span data-unit-label><?= e(unit_label((string) $activeVariant['unit'])) ?></span>:</span>
|
<span class="price-unit-label">Цена за <span data-unit-label><?= e(unit_label((string) $activeVariant['unit'])) ?></span>:</span>
|
||||||
<span class="price-unit-values">
|
<span class="price-unit-values">
|
||||||
<span class="old-price" data-old-unit-price-display<?= $activeOldUnitPrice === null ? ' hidden' : '' ?>><?= e(money_label($activeOldUnitPrice)) ?></span>
|
<span class="old-price" data-old-unit-price-display<?= $activeOldUnitPrice === null ? ' hidden' : '' ?>><?= e(money_label($activeOldUnitPrice)) ?></span>
|
||||||
@@ -137,13 +166,11 @@ $isPurchasable = $isPublished && $isAvailable && $activePackagePrice > 0;
|
|||||||
<span class="package-price-values">
|
<span class="package-price-values">
|
||||||
<span class="old-price" data-old-package-price-display<?= $activeOldPackagePrice === null ? ' hidden' : '' ?>><?= e(money_label($activeOldPackagePrice)) ?></span>
|
<span class="old-price" data-old-package-price-display<?= $activeOldPackagePrice === null ? ' hidden' : '' ?>><?= e(money_label($activeOldPackagePrice)) ?></span>
|
||||||
<span data-package-price data-dynamic-package-price data-base-package-price="<?= e((string) $activePackagePrice) ?>"><?= e(money_label($activeVariant['package_price'])) ?></span>
|
<span data-package-price data-dynamic-package-price data-base-package-price="<?= e((string) $activePackagePrice) ?>"><?= e(money_label($activeVariant['package_price'])) ?></span>
|
||||||
<?php if ($activeVariant['price_per_unit'] === null): ?>
|
<button class="price-tier-trigger" data-package-tier-trigger type="button" aria-label="Показать градацию цены"<?= $activeShowsUnitPrice ? ' hidden' : '' ?>>?</button>
|
||||||
<button class="price-tier-trigger" type="button" aria-label="Показать градацию цены">?</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</span>
|
</span>
|
||||||
</strong>
|
</strong>
|
||||||
<?php if ($activeVariant['price_per_unit'] !== null): ?>
|
<?php if ($isPurchasable): ?>
|
||||||
<span class="package-price-note">Цена за <span data-unit-label-note><?= e(unit_label((string) $activeVariant['unit'])) ?></span> × <span data-package-label-note><?= e((string) $activeVariant['name']) ?></span></span>
|
<span class="package-price-note<?= $activeShowsUnitPrice ? '' : ' is-price-placeholder' ?>" data-package-price-note aria-hidden="<?= $activeShowsUnitPrice ? 'false' : 'true' ?>">Цена за <span data-unit-label-note><?= e(unit_label((string) $activeVariant['unit'])) ?></span> × <span data-package-label-note><?= e((string) $activeVariant['name']) ?></span></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php elseif (!$isPurchasable): ?>
|
<?php elseif (!$isPurchasable): ?>
|
||||||
<div class="product-card-unavailable-note">Цену уточним после поступления товара.</div>
|
<div class="product-card-unavailable-note">Цену уточним после поступления товара.</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user