@lancom/shared 0.0.422 → 0.0.423

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -63,8 +63,8 @@
63
63
  :display-products-count="true"
64
64
  class="ProductColorsSelector__section" />
65
65
  <div
66
- class="ProductColorsSelector__toggle-gst"
67
- v-if="hasToggleGst">
66
+ v-if="hasToggleGst"
67
+ class="ProductColorsSelector__toggle-gst">
68
68
  <checkbox
69
69
  v-model="inclGST"
70
70
  :dark="true"
@@ -75,13 +75,14 @@
75
75
  </div>
76
76
  <div class="ProductColorsSelector__countries">
77
77
  <div>
78
- Stock by countries:
78
+ Stock by countries:
79
79
  </div>
80
80
  <div>
81
81
  <client-settings-stock-country />
82
82
  </div>
83
83
  </div>
84
- <products-size-selector-color
84
+ <component
85
+ :is="productsSizeSelector"
85
86
  :key="productDetailsKey"
86
87
  :with-gst="inclGSTFinal"
87
88
  class="ProductColorsSelector__section"
@@ -101,6 +102,7 @@ import ProductColorsSelector from '@lancom/shared/components/product/layouts/pro
101
102
  import ProductColorsSelectorOptions from '@lancom/shared/components/product/layouts/product_colors_selector/product_colors_selector_options/product-colors-selector-options';
102
103
  import ProductColorImage from '@lancom/shared/components/product/product_color_image/product-color-image';
103
104
  import ProductsSizeSelectorColor from '@lancom/shared/components/product/products_size_selector_color/products-size-selector-color';
105
+ import ProductsMultipacksSizeSelectorColor from '@lancom/shared/components/product/products_multipacks_size_selector_color/products-multipacks-size-selector-color';
104
106
  import ClientSettingsStockCountry from '@lancom/shared/components/common/client_settings_stock_country/client-settings-stock-country';
105
107
 
106
108
  export default {
@@ -113,14 +115,6 @@ export default {
113
115
  Multiselect,
114
116
  ClientSettingsStockCountry
115
117
  },
116
- data() {
117
- return {
118
- inclGSTFinal: false,
119
- inclGSTSpinner: false,
120
- inclGSTSpinnerTimer: null,
121
- opened: true
122
- };
123
- },
124
118
  props: {
125
119
  hasAnotherPrintBtn: {
126
120
  type: Boolean,
@@ -131,9 +125,17 @@ export default {
131
125
  default: true
132
126
  }
133
127
  },
128
+ data() {
129
+ return {
130
+ inclGSTFinal: false,
131
+ inclGSTSpinner: false,
132
+ inclGSTSpinnerTimer: null,
133
+ opened: true
134
+ };
135
+ },
134
136
  computed: {
135
137
  ...mapGetters(['stockCountry', 'taxName']),
136
- ...mapGetters('product', ['productDetailsKey', 'editableColor', 'availableColors', 'visibleSteps', 'hasLayers', 'isPrintPricing', 'priceIncludeGST']),
138
+ ...mapGetters('product', ['product', 'productDetailsKey', 'editableColor', 'availableColors', 'visibleSteps', 'hasLayers', 'isPrintPricing', 'priceIncludeGST']),
137
139
  selectedColor: {
138
140
  get() {
139
141
  return this.editableColor;
@@ -149,6 +151,13 @@ export default {
149
151
  set(value) {
150
152
  this.setPriceIncludeGST(value);
151
153
  }
154
+ },
155
+ productsSizeSelector() {
156
+ return this.availableMultipacksColorsSelector ? ProductsMultipacksSizeSelectorColor : ProductsSizeSelectorColor;
157
+ },
158
+ availableMultipacksColorsSelector() {
159
+ const multipacks = (this.product.multipacks || []);
160
+ return multipacks.some(m => !m.SKU);
152
161
  }
153
162
  },
154
163
  watch: {
@@ -63,6 +63,9 @@ export default {
63
63
  type: Object,
64
64
  required: true
65
65
  },
66
+ multipack: {
67
+ type: Object
68
+ },
66
69
  sideControls: {
67
70
  type: Boolean,
68
71
  default: false
@@ -88,7 +91,7 @@ export default {
88
91
  },
89
92
  computed: {
90
93
  ...mapGetters(['currency']),
91
- ...mapGetters('product', ['usedSimpleProducts', 'product']),
94
+ ...mapGetters('product', ['commonProductsMultipacks', 'usedSimpleProducts', 'product']),
92
95
  disabled() {
93
96
  const sizeWithoutColor = this.color.sizes && !this.color.sizes.find(({ alias }) => alias === this.size.alias);
94
97
  return sizeWithoutColor || this.outStock;
@@ -97,17 +100,34 @@ export default {
97
100
  return this.usedSimpleProducts
98
101
  .find(({ color, size }) => color._id === this.color._id && size._id === this.size._id);
99
102
  },
103
+ simpleProductAmount() {
104
+ return (this.simpleProduct || {}).amount || this.defaultValue || 0;
105
+ },
100
106
  model: {
101
107
  get() {
102
- return (this.simpleProduct || {}).amount || this.defaultValue;
108
+ const amount = this.simpleProductAmount;
109
+ if (this.multipack) {
110
+ const biggerMultipacks = [...this.commonProductsMultipacks]
111
+ .sort((a, b) => b.qty - a.qty)
112
+ .filter(p => p.qty > this.multipack.qty);
113
+ const biggerMultipacksAmount = amount - biggerMultipacks.reduce((leftAmount, p) => leftAmount % p.qty, amount);
114
+ const multipackAmount = amount - biggerMultipacksAmount;
115
+ const result = Math.floor(multipackAmount / this.multipack.qty);
116
+ return result;
117
+ }
118
+ return amount;
103
119
  },
104
120
  set(value) {
105
121
  const outOfStockQuantity = this.simpleProduct?.quantityStock;
122
+ if (this.multipack) {
123
+ const leftAmount = this.simpleProductAmount - (this.model * this.multipack.qty);
124
+ value = leftAmount + Math.max(0, value) * this.multipack.qty;
125
+ }
106
126
  if (outOfStockQuantity && outOfStockQuantity < value && this.showMaxModal) {
107
127
  const message = `Maximum value: ${outOfStockQuantity}`;
108
128
  this.showConfirmationModal(message);
109
129
  }
110
- const amount = inRange(value, 0, outOfStockQuantity || 999);
130
+ const amount = inRange(value, 0, outOfStockQuantity || 1000);
111
131
  this.setSimpleProductAmount({
112
132
  colorId: this.color._id,
113
133
  sizeId: this.size._id,
@@ -0,0 +1,61 @@
1
+ @import '@/assets/scss/variables';
2
+
3
+ .ProductMultipacksSizeSelectorColor {
4
+ &__wrapper td {
5
+ font-weight: 800;
6
+ font-size: 18px;
7
+ line-height: 25px;
8
+ text-transform: uppercase;
9
+ color: $black;
10
+ &.disabled {
11
+ color: $gray_main;
12
+ pointer-events: none !important;
13
+ }
14
+ }
15
+ &__color-name {
16
+ width: 70px;
17
+ span {
18
+ writing-mode: vertical-lr;
19
+ transform: rotate(180deg);
20
+ width: 100%;
21
+ display: block;
22
+ margin-left: -20px;
23
+ }
24
+ @media (max-width: $bp-extra-small-max) {
25
+ width: 40px;
26
+ span {
27
+ margin-left: -6px;
28
+ }
29
+ }
30
+ }
31
+ &__size {
32
+ width: 70px;
33
+ }
34
+ &__in-stock,
35
+ &__out-stock {
36
+ font-weight: 600;
37
+ text-transform: none;
38
+ }
39
+ &__out-stock {
40
+ color: $gray_main;
41
+ }
42
+ &__stock {
43
+ font-size: 11px;
44
+ letter-spacing: -1px;
45
+ }
46
+ }
47
+
48
+ ::v-deep .ProductSizeSelectorColorCell {
49
+ &__field {
50
+ line-height: 20px;
51
+ input {
52
+ height: 30px;
53
+ padding: 5px !important;
54
+ background-color: $gray;
55
+ }
56
+ }
57
+ &__side-control {
58
+ background: $green;
59
+ width: 26px;
60
+ }
61
+ }
@@ -0,0 +1,102 @@
1
+ <template>
2
+ <tr class="ProductMultipacksSizeSelectorColor__wrapper">
3
+ <td
4
+ v-if="rowspan"
5
+ :rowspan="rowspan"
6
+ class="ProductMultipacksSizeSelectorColor__color-name">
7
+ <span>
8
+ {{ product.color.name }}
9
+ </span>
10
+ </td>
11
+ <td
12
+ class="ProductMultipacksSizeSelectorColor__size"
13
+ :class="{ disabled }">
14
+ <div>
15
+ {{ product.size.shortName }}
16
+ </div>
17
+ <div class="ProductMultipacksSizeSelectorColor__stock">
18
+ <div
19
+ v-if="product.quantityStock"
20
+ class="ProductMultipacksSizeSelectorColor__in-stock">
21
+ {{ currentQuantityStockLabel }} in stock
22
+ </div>
23
+ <div
24
+ v-else
25
+ class="ProductMultipacksSizeSelectorColor__out-stock">
26
+ out of stock
27
+ </div>
28
+ </div>
29
+ </td>
30
+ <td
31
+ v-for="multipack in commonProductsMultipacks"
32
+ :key="multipack._id"
33
+ class="ProductMultipacksSizeSelectorColor__cell">
34
+ <product-size-selector-color-cell
35
+ :side-controls="true"
36
+ :show-max-modal="false"
37
+ :out-stock="disabled"
38
+ :color="product.color"
39
+ :size="product.size"
40
+ :multipack="multipack" />
41
+ </td>
42
+ </tr>
43
+ </template>
44
+
45
+ <script>
46
+ import { mapGetters } from 'vuex';
47
+ import { price, tax } from '@lancom/shared/assets/js/utils/filters';
48
+ import ProductSizeSelectorColorCell from '@lancom/shared/components/product/product_size_selector/product_size_selector_color/product_size_selector_color_cell/product-size-selector-color-cell';
49
+
50
+ export default {
51
+ name: 'ProductMultipacksSizeSelectorColor',
52
+ filters: {
53
+ price
54
+ },
55
+ components: {
56
+ ProductSizeSelectorColorCell
57
+ },
58
+ props: {
59
+ product: {
60
+ type: Object,
61
+ required: true
62
+ },
63
+ rowspan: {
64
+ type: Number,
65
+ default: 0
66
+ },
67
+ withGst: {
68
+ type: Boolean,
69
+ default: false
70
+ }
71
+ },
72
+ computed: {
73
+ ...mapGetters('product', ['commonProductsMultipacks', 'isPrintPricing', 'usedSimpleProductsQuantity', 'printsPrice']),
74
+ ...mapGetters(['gstTax', 'currency']),
75
+ currentQuantityStockLabel() {
76
+ return this.currentQuantityStock > 100 ? '100+' : this.currentQuantityStock;
77
+ },
78
+ pricing() {
79
+ return (this.isPrintPricing ? this.product.pricing : this.product.unprintedPricing) || [];
80
+ },
81
+ productPrice() {
82
+ const amount = this.usedSimpleProductsQuantity;
83
+ const pricing = this.pricing;
84
+ const price = (pricing.find(({ min, max }) => (!min || min <= amount) && (!max || max >= amount)) || pricing[0] || {}).price || 0;
85
+ return this.withGst ? tax(price, this.gstTax) : price;
86
+ },
87
+ productPrintsPrice() {
88
+ return this.withGst ? tax(this.printsPrice, this.gstTax) : this.printsPrice;
89
+ },
90
+ disabled() {
91
+ return !this.product.quantityStock || !this.productPrice;
92
+ },
93
+ currentQuantityStock() {
94
+ return (this.product.quantityStock || 0) - (this.product.amount || 0);
95
+ }
96
+ }
97
+ };
98
+ </script>
99
+
100
+ <style lang="scss" scoped>
101
+ @import 'product-multipacks-size-selector-color.scss';
102
+ </style>
@@ -0,0 +1,15 @@
1
+ @import '@/assets/scss/variables';
2
+
3
+ .ProductsMultipacksSizeSelectorColor {
4
+ &__table {
5
+ border: 1px solid $gray;
6
+ font-weight: 800;
7
+ font-size: 18px;
8
+ line-height: 25px;
9
+ text-transform: uppercase;
10
+ color: $black;
11
+ }
12
+ &__cost-column {
13
+ width: 150px;
14
+ }
15
+ }
@@ -0,0 +1,95 @@
1
+ <template>
2
+ <div>
3
+ <table class="ProductsMultipacksSizeSelectorColor__table lc_table bordered">
4
+ <thead class="centered">
5
+ <tr>
6
+ <th
7
+ colspan="2"
8
+ class="ProductsMultipacksSizeSelectorColor__cost-column">
9
+ </th>
10
+ <th
11
+ v-for="multipack in commonProductsMultipacks"
12
+ :key="multipack._id">
13
+ {{ multipack.name }} ({{ multipack.qty }})
14
+ </th>
15
+ </tr>
16
+ <tr>
17
+ <th
18
+ colspan="2"
19
+ class="ProductsMultipacksSizeSelectorColor__cost-column">
20
+ COST
21
+ </th>
22
+ <th
23
+ v-for="multipack in commonProductsMultipacks"
24
+ :key="multipack._id">
25
+ {{ priceFromRange(multipack.qty, multipack.qty) | price(currency) }}
26
+ </th>
27
+ </tr>
28
+ <tr>
29
+ <th
30
+ colspan="2"
31
+ style="width: 140px;">
32
+ UNiTCOST
33
+ </th>
34
+ <th
35
+ v-for="multipack in commonProductsMultipacks"
36
+ :key="multipack._id">
37
+ {{ priceFromRange(multipack.qty, 1) | price(currency) }}
38
+ </th>
39
+ </tr>
40
+ </thead>
41
+ <tbody class="centered">
42
+ <product-multipacks-size-selector-color
43
+ v-for="(product, index) in products"
44
+ :key="`${editableColor._id}_${product._id}`"
45
+ :rowspan="index === 0 ? products.length : 0"
46
+ :with-gst="withGst"
47
+ :product="product" />
48
+ </tbody>
49
+ </table>
50
+ </div>
51
+ </template>
52
+
53
+ <script>
54
+ import { mapGetters } from 'vuex';
55
+ import { sortBySize } from '@lancom/shared/assets/js/utils/sizes';
56
+ import { priceFromRange } from '@lancom/shared/assets/js/utils/pricing';
57
+ import { price, tax } from '@lancom/shared/assets/js/utils/filters';
58
+ import ProductMultipacksSizeSelectorColor from './product_multipacks_size_selector_color/product-multipacks-size-selector-color';
59
+
60
+ export default {
61
+ name: 'ProductsMultipacksSizeSelectorColor',
62
+ filters: {
63
+ price
64
+ },
65
+ components: {
66
+ ProductMultipacksSizeSelectorColor
67
+ },
68
+ props: {
69
+ withGst: {
70
+ type: Boolean,
71
+ default: false
72
+ }
73
+ },
74
+ computed: {
75
+ ...mapGetters(['gstTax']),
76
+ ...mapGetters('product', ['defaultSimpleProduct', 'commonProductsMultipacks', 'editableColor', 'editableColorSimpleProducts']),
77
+ products() {
78
+ return sortBySize(this.editableColorSimpleProducts);
79
+ },
80
+ pricing() {
81
+ return this.defaultSimpleProduct.pricing;
82
+ }
83
+ },
84
+ methods: {
85
+ priceFromRange(min, qty) {
86
+ const price = priceFromRange(min, this.pricing) * qty;
87
+ return this.withGst ? tax(price, this.gstTax) : price;
88
+ }
89
+ }
90
+ };
91
+ </script>
92
+
93
+ <style lang="scss" scoped>
94
+ @import 'products-multipacks-size-selector-color.scss';
95
+ </style>
@@ -10,7 +10,7 @@
10
10
  v-show="visibleWizard" />
11
11
  </div>
12
12
  <div
13
- v-if="editableColor"
13
+ v-if="editableColor && availableMultipacks"
14
14
  class="Editor__section">
15
15
  <product-multipacks-carousel
16
16
  :key="editableColor._id"
@@ -77,6 +77,10 @@ export default {
77
77
  'isPrintPricing',
78
78
  'priceIncludeGST'
79
79
  ]),
80
+ availableMultipacks() {
81
+ const multipacks = (this.product.multipacks || []);
82
+ return multipacks.some(m => !!m.SKU);
83
+ },
80
84
  visibleWizard() {
81
85
  return (this.product.printTypes || []).length > 0;
82
86
  },
@@ -12,7 +12,7 @@
12
12
  }
13
13
 
14
14
  .CategoryCard {
15
- width: 270px;
15
+ width: 170px;
16
16
  margin: 0 0 15px 15px;
17
17
  position: relative;
18
18
  transition: transform 0.3s ease, box-shadow 0.3s ease;
@@ -34,7 +34,7 @@
34
34
 
35
35
  &__image-wrapper {
36
36
  width: 100%;
37
- height: 300px;
37
+ height: 200px;
38
38
  overflow: hidden;
39
39
  position: relative;
40
40
  }
@@ -62,4 +62,4 @@
62
62
  @media (max-width: $bp-extra-small-max) {
63
63
  margin: 0 auto 25px auto;
64
64
  }
65
- }
65
+ }
@@ -20,16 +20,12 @@
20
20
  <h1 class="Products__name">
21
21
  {{ routeName }}
22
22
  </h1>
23
- <div
24
- v-if="!visiblChildrenCategories"
25
- class="Products__filters">
23
+ <div class="Products__filters">
26
24
  <products-filters @open="openAside" />
27
25
  </div>
28
26
  </div>
29
27
  <div class="Products__content">
30
- <div
31
- v-if="!visiblChildrenCategories"
32
- class="Products__aside">
28
+ <div class="Products__aside">
33
29
  <breakpoint
34
30
  name="md"
35
31
  mode="up">
@@ -37,10 +33,8 @@
37
33
  </breakpoint>
38
34
  </div>
39
35
  <div class="Products__list">
40
- <children-categories v-if="visiblChildrenCategories" />
41
- <products-catalog
42
- v-else
43
- class="Products__catalog" />
36
+ <children-categories v-if="hasChildrenCategories" />
37
+ <products-catalog class="Products__catalog" />
44
38
  </div>
45
39
  </div>
46
40
  <div
@@ -103,7 +97,7 @@
103
97
  async fetch() {
104
98
  await this.loadProducts();
105
99
 
106
- if (this.page === 1 && this.products.length === 0) {
100
+ if (this.page === 1) {
107
101
  await this.fetchChildrenCategories(this.currentCategory);
108
102
  }
109
103
 
@@ -115,8 +109,8 @@
115
109
  ...mapGetters('page', ['routeInfo']),
116
110
  ...mapGetters(['notificationBar', 'shop', 'country', 'currency']),
117
111
  ...mapGetters('products', ['category', 'childrenCategories', 'categories', 'brands', 'types', 'tags', 'loadError', 'count', 'products', 'minPrice', 'maxPrice', 'page']),
118
- visiblChildrenCategories() {
119
- return false; // this.page === 1 && this.products.length === 0 && this.childrenCategories.length > 0;
112
+ hasChildrenCategories() {
113
+ return this.childrenCategories.length > 0;
120
114
  },
121
115
  pageItemCanonical() {
122
116
  const page = +this.$route.query.page;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lancom/shared",
3
- "version": "0.0.422",
3
+ "version": "0.0.423",
4
4
  "description": "lancom common scripts",
5
5
  "author": "e.tokovenko <e.tokovenko@gmail.com>",
6
6
  "repository": {
package/store/product.js CHANGED
@@ -56,6 +56,7 @@ export const getters = {
56
56
  productDetailsKey: ({ productDetailsKey }) => productDetailsKey,
57
57
  loadingProductDetails: ({ loadingProductDetails }) => loadingProductDetails,
58
58
  multipack: ({ multipack }) => multipack,
59
+ commonProductsMultipacks: ({ product }) => product?.multipacks?.filter(p => !p.SKU) || [],
59
60
  calculatingPrice: ({ calculatingPrice }) => calculatingPrice,
60
61
  product: ({ product }) => product,
61
62
  preSetPrints: ({ preSetPrints }) => preSetPrints,