@policystudio/policy-studio-ui-vue 1.1.6 → 1.1.7

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.
@@ -6,116 +6,238 @@
6
6
  .psui-el-table-results {
7
7
  @apply psui-relative psui-align-top psui-w-full psui-max-w-full psui-text-p;
8
8
 
9
- tr {
10
- th, td {
11
- &:last-child {
12
- @apply psui-pr-2;
9
+ &.layout-results {
10
+ tr {
11
+ th, td {
12
+ &:last-child {
13
+ @apply psui-pr-2;
14
+ }
13
15
  }
14
16
  }
15
- }
16
-
17
- thead {
18
- @apply psui-bg-white psui-items-start psui-shadow-elevation-10 psui-sticky psui-top-0 psui-z-15;
19
-
20
- &:after {
21
- content: '';
22
- @apply psui-inline-block psui-absolute psui-top-0 psui-w-2 psui-h-full psui-bg-white;
23
- right: -8px;
24
- }
25
-
26
- tr {
27
- .title {
28
- @apply psui-text-small psui-font-bold psui-leading-4 psui-text-gray-80;
17
+
18
+ thead {
19
+ @apply psui-bg-white psui-items-start psui-shadow-elevation-10 psui-sticky psui-top-0 psui-z-15;
20
+
21
+ &:after {
22
+ content: '';
23
+ @apply psui-inline-block psui-absolute psui-top-0 psui-w-2 psui-h-full psui-bg-white;
24
+ right: -8px;
29
25
  }
30
-
31
- th {
32
- @apply psui-pl-6 psui-text-gray-50 psui-text-right psui-w-auto psui-align-top;
33
- padding-top: 13px;
34
- padding-bottom: 13px;
35
-
36
- .description {
37
- @apply psui-text-xsmall psui-font-normal;
38
- line-height: 110%;
26
+
27
+ tr {
28
+ .title {
29
+ @apply psui-text-small psui-font-bold psui-leading-4 psui-text-gray-80;
39
30
  }
40
-
41
- &:first-child {
42
- @apply psui-pl-0 psui-pr-8 psui-text-left psui-bg-white psui-sticky psui-z-10 psui-left-0;
43
- box-shadow: inset -1px 0px 0px #EBEEF0;
44
- min-width: 300px;
45
-
46
- > div {
47
- @apply psui-pl-0;
31
+
32
+ th {
33
+ @apply psui-pl-6 psui-text-gray-50 psui-text-right psui-w-auto psui-align-top;
34
+ padding-top: 13px;
35
+ padding-bottom: 13px;
36
+
37
+ .description {
38
+ @apply psui-text-xsmall psui-font-normal;
39
+ line-height: 110%;
40
+ }
41
+
42
+ &:first-child {
43
+ @apply psui-pl-0 psui-pr-8 psui-text-left psui-bg-white psui-sticky psui-z-10 psui-left-0;
44
+ box-shadow: inset -1px 0px 0px #EBEEF0;
45
+ min-width: 300px;
46
+
47
+ > div {
48
+ @apply psui-pl-0;
49
+ }
50
+ }
51
+ }
52
+
53
+ &:first-of-type {
54
+ th {
55
+ @apply psui-text-left;
56
+ padding-top: 0;
57
+ padding-bottom: 0;
58
+
59
+ > div {
60
+ @apply psui-flex psui-flex-row psui-border-b psui-border-gray-30;
61
+ padding-top: 11px;
62
+ padding-bottom: 11px;
63
+ }
64
+ }
65
+
66
+ p {
67
+ @apply psui-text-p;
68
+ line-height: 18px;
48
69
  }
49
70
  }
50
71
  }
51
-
52
- &:first-of-type {
53
- th {
54
- @apply psui-text-left;
55
- padding-top: 0;
56
- padding-bottom: 0;
57
-
58
- > div {
59
- @apply psui-flex psui-flex-row psui-border-b psui-border-gray-30;
60
- padding-top: 11px;
61
- padding-bottom: 11px;
72
+ }
73
+
74
+ tbody {
75
+ tr {
76
+ @apply psui-border-b psui-border-gray-20;
77
+
78
+ &.is-first {
79
+ .title {
80
+ @apply psui-font-bold psui-text-gray-80;
62
81
  }
63
82
  }
64
-
65
- p {
66
- @apply psui-text-p;
67
- line-height: 18px;
83
+
84
+ .title {
85
+ @apply psui-flex psui-items-center;
86
+ }
87
+
88
+ td {
89
+ @apply psui-pl-8 psui-text-gray-80 psui-h-10 psui-text-right psui-text-small;
90
+ padding-top: 11px;
91
+ padding-bottom: 11px;
92
+
93
+ > div {
94
+ @apply psui-flex psui-items-center;
95
+ }
96
+
97
+ .actions {
98
+ @apply psui-flex psui-items-center psui-h-full psui-relative;
99
+
100
+ &-button {
101
+ @apply psui-cursor-pointer psui-ml-1;
102
+ }
103
+
104
+ .is-last-deep {
105
+ @apply psui-pl-10;
106
+ }
107
+ }
108
+
109
+ &:not(:first-child) {
110
+ > div {
111
+ @apply psui-justify-end;
112
+ }
113
+ }
114
+
115
+ &:first-child {
116
+ @apply psui-pl-0 psui-pr-8 psui-text-left psui-block psui-bg-white psui-sticky psui-z-10 psui-left-0;
117
+ box-shadow: inset -1px 0px 0px #EBEEF0;
118
+ padding-top: 8px;
119
+ padding-bottom: 8px;
120
+ }
68
121
  }
69
122
  }
70
123
  }
71
124
  }
72
125
 
73
- tbody {
126
+ &.layout-comparison {
127
+ border-collapse: separate;
128
+ border-spacing: 2px 8px;
129
+
74
130
  tr {
75
- @apply psui-border-b psui-border-gray-20;
131
+ th, td {
132
+ @apply psui-relative;
76
133
 
77
- &.is-first {
78
- .title {
79
- @apply psui-font-bold psui-text-gray-80;
134
+ &:last-child {
135
+ @apply psui-pr-2;
80
136
  }
81
137
  }
138
+ }
82
139
 
83
- .title {
84
- @apply psui-flex psui-items-center;
85
- }
86
-
87
- td {
88
- @apply psui-pl-8 psui-text-gray-80 psui-h-10 psui-text-right psui-text-small;
89
- padding-top: 11px;
90
- padding-bottom: 11px;
140
+ thead {
141
+ @apply psui-bg-transparent psui-items-start;
91
142
 
92
- > div {
93
- @apply psui-flex psui-items-center;
143
+ div {
144
+ @apply psui-relative;
145
+ }
146
+
147
+ tr {
148
+ .title {
149
+ @apply psui-text-small psui-font-bold psui-leading-4 psui-text-gray-80;
150
+ }
151
+
152
+ th {
153
+ @apply psui-px-6 psui-py-4 psui-text-gray-50 psui-text-center psui-align-top;
154
+ padding-top: 13px;
155
+ padding-bottom: 13px;
156
+ min-width: 200px;
157
+
158
+ .description {
159
+ @apply psui-text-xsmall psui-font-normal;
160
+ line-height: 110%;
161
+ }
94
162
  }
95
163
 
96
- .actions {
97
- @apply psui-flex psui-items-center psui-h-full psui-relative;
164
+ &:not(:first-of-type) {
165
+ th {
98
166
 
99
- &-button {
100
- @apply psui-cursor-pointer psui-ml-1;
167
+ &:after {
168
+ @apply psui-absolute psui-top-0 psui-h-full psui-inline-block psui-bg-gray-30;
169
+ content: '';
170
+ width: 1px;
171
+ right: -1px;
172
+ }
101
173
  }
174
+ }
175
+
176
+ &:first-of-type {
177
+ th {
178
+ @apply psui-text-left psui-text-gray-50 psui-text-p;
179
+ padding-top: 11px;
180
+ padding-bottom: 11px;
102
181
 
103
- .is-last-deep {
104
- @apply psui-pl-10;
182
+ &:first-child {
183
+ min-width: 240px;
184
+ .title {
185
+ @apply psui-text-gray-80 psui-font-bold;
186
+ }
187
+ }
188
+
189
+ &:not(:first-child) {
190
+ &:before {
191
+ content: '';
192
+ display: inline-block;
193
+ background: linear-gradient(180deg, #FFFFFF 0%, #E6ECF2 100%);
194
+ border-radius: 12px 12px 0px 0px;
195
+ position: absolute;
196
+ top: 0; left: 0;
197
+ width: 100%; height: 88px;
198
+ }
199
+ }
200
+ }
201
+
202
+ p {
203
+ @apply psui-text-small;
204
+ line-height: 18px;
205
+
206
+ &.title {
207
+ @apply psui-text-gray-50 psui-font-normal;
208
+ }
105
209
  }
106
210
  }
211
+ }
212
+ }
107
213
 
108
- &:not(:first-child) {
109
- > div {
110
- @apply psui-justify-end;
214
+ tbody {
215
+ tr {
216
+ td {
217
+ @apply psui-bg-white psui-relative;
218
+ box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.03), 0px 1px 2px rgba(0, 0, 0, 0.1);
219
+
220
+ &:after {
221
+ @apply psui-top-0 psui-absolute psui-h-full psui-inline-block;
222
+ content: '';
223
+ width: 2px;
224
+ right: -2px;
225
+ background: rgb(214,221,229);
226
+ background: linear-gradient(90deg, rgba(214,221,229,1) 50%, rgba(255,255,255,1) 50%);
111
227
  }
112
- }
113
228
 
114
- &:first-child {
115
- @apply psui-pl-0 psui-pr-2 psui-text-left psui-block psui-bg-white psui-sticky psui-z-10 psui-left-0;
116
- box-shadow: inset -1px 0px 0px #EBEEF0;
117
- padding-top: 8px;
118
- padding-bottom: 8px;
229
+ &:first-child {
230
+ @apply psui-pl-3 psui-rounded-tl-md psui-rounded-bl-md;
231
+
232
+ &:after {
233
+ @apply psui-bg-gray-20;
234
+ background-image: none;
235
+ }
236
+ }
237
+
238
+ .psui-truncate {
239
+ width: 144px;
240
+ }
119
241
  }
120
242
  }
121
243
  }
@@ -23,6 +23,7 @@
23
23
  <span class="flex psui-items-center">
24
24
  <p>{{ columnGroup.title }}</p>
25
25
  <PsIcon
26
+ class="psui-el-draggable-wrapper-title-icon"
26
27
  v-if="columnGroup.hasHelper && columnGroup.hasHelper.showOnEditHideColumns"
27
28
  icon="info"
28
29
  size="16"
@@ -37,7 +38,6 @@
37
38
  class="psui-el-draggable-wrapper-list"
38
39
  :class="{'psui-opacity-50 psui-pointer-events-none':columnGroup.disabled}"
39
40
  >
40
-
41
41
  <div
42
42
  v-for="(column, indexColumn) in columnGroup.columns"
43
43
  :key="`edit-columns-column-${indexColumnGroup}-${indexColumn}`"
@@ -62,6 +62,7 @@
62
62
  @change="$emit('on-select-item', { studyKey: getColumns.key, indexColumnGroup, indexColumn, columnGroupKey: columnGroup.key })"
63
63
  />
64
64
  <PsIcon
65
+ class="psui-el-draggable-item-title-icon"
65
66
  v-if="column.hasHelper && column.hasHelper.showOnEditHideColumns"
66
67
  icon="info"
67
68
  size="16"
@@ -4,8 +4,122 @@
4
4
  :style="{ maxHeight: tableMaxHeight }"
5
5
  >
6
6
  <table
7
+ v-if="layout == 'comparison'"
7
8
  ref="table"
8
9
  class="psui-el-table-results"
10
+ :class="`layout-${layout}`"
11
+ >
12
+ <slot name="header"></slot>
13
+
14
+ <!-- <slot name="body"></slot> -->
15
+
16
+ <tbody class="psui-el-table-results-body">
17
+ <tr
18
+ v-for="(item, index) in getRows"
19
+ :key="index"
20
+ :class="[
21
+ {
22
+ 'is-disabled' : item.is_disabled,
23
+ 'is-first' : item.deep == 0,
24
+ 'psui-hidden' : checkRowIsCollapsed(item),
25
+ 'is-last' : item.is_last
26
+ },
27
+ `type-${item.type}`
28
+ ]"
29
+ @mouseenter="onRowHover(index)"
30
+ @mouseleave="onRowHover(false)"
31
+ >
32
+ <td>
33
+ <div class="psui-flex psui-items-center">
34
+ <PsIcon
35
+ icon="expand_more"
36
+ size="24"
37
+ class="actions-button psui-transition psui-transform psui-mr-1"
38
+ :icon-classes="item.is_disabled ? 'psui-text-gray-40' : getIconClasses(item)"
39
+ :style="{ display: 'flex' }"
40
+ :class="[checkRowIsVisible(item) ? 'psui-rotate-0' : 'psui--rotate-90', { 'psui-pointer-events-none' : item.is_disabled }]"
41
+ @click.native="onCollapse(item)"
42
+ />
43
+ <span class="psui-text-small psui-text-gray-80 psui-font-bold psui-truncate psui-leading-none psui-pr-3">{{ item.title }}</span>
44
+ <PsIcon
45
+ icon="trending_down"
46
+ size="24"
47
+ class="actions-button psui-transition psui-transform psui-mr-1"
48
+ :icon-classes="item.is_disabled ? 'psui-text-gray-40' : getIconClasses(item)"
49
+ :style="{ display: 'flex' }"
50
+ :class="[checkRowIsVisible(item) ? 'psui-rotate-0' : 'psui--rotate-90', { 'psui-pointer-events-none' : item.is_disabled }]"
51
+ @click.native="onCollapse(item)"
52
+ />
53
+ </div>
54
+ </td>
55
+ <template
56
+ v-for="(columnGroup, indexColumn) of columnGroups"
57
+ >
58
+ <td
59
+ v-for="column of columnGroup.columns"
60
+ :key="indexColumn + '-' + columnGroup.key + '-' + column.key"
61
+ >
62
+ <div class="psui-py-4 psui-px-6">
63
+ <PsTagScope
64
+ v-if="column.isScope"
65
+ :included="item[column.key]"
66
+ />
67
+ <PsBarChart
68
+ v-else-if="column.isChart"
69
+ :show-number="item[column.key]"
70
+ :total="item[column.key]"
71
+ :fill-width="Math.floor(Math.random() * 100)"
72
+ />
73
+ <p v-else>
74
+ {{ item[column.key] }}
75
+ </p>
76
+ <!-- <PsIcon
77
+ v-if="column.hasProjections && !item.is_disabled"
78
+ icon="bar_chart"
79
+ size="16"
80
+ class="psui-cursor-pointer"
81
+ icon-classes="psui-text-blue-60 psui-opacity-0 psui-leading-none psui-transition"
82
+ :style="{ display: 'flex' }"
83
+ @click.native="$emit('policy-selected', { item, column })"
84
+ /> -->
85
+
86
+ <!-- <p v-if="formatFunction && !item.is_disabled">
87
+ {{ formatFunction(column.key, item.data[column.key], item.data) }}
88
+ </p> -->
89
+
90
+ <!-- <p
91
+ v-else-if="item.is_disabled"
92
+ class="psui-text-gray-50"
93
+ >
94
+ --
95
+ </p> -->
96
+
97
+
98
+ <!-- <PsProgressBar
99
+ v-if="column.isChart && formatFunction"
100
+ :value="formatFunction(column.key, item.data[column.key], item.data) == '--' ? 0 : item.data[column.key]"
101
+ :force-break-even="item.is_disabled || formatFunction(column.key, item.data[column.key], item.data) === '--' ? true : false"
102
+ />
103
+
104
+ <PsProgressBar
105
+ v-else-if="column.isChart"
106
+ :value="item.data[column.key] == '--' ? 0 : item.data[column.key]"
107
+ :force-break-even="item.is_disabled || item.data[column.key] === '--' ? true : false"
108
+ /> -->
109
+ </div>
110
+ </td>
111
+ </template>
112
+ </tr>
113
+ </tbody>
114
+
115
+ <!-- <slot name="footer"></slot> -->
116
+ </table>
117
+
118
+ <table
119
+ v-else
120
+ ref="table"
121
+ class="psui-el-table-results"
122
+ :class="`layout-${layout}`"
9
123
  >
10
124
  <slot name="header"></slot>
11
125
 
@@ -210,11 +324,25 @@
210
324
  import PsRichTooltip from '../tooltip/PsRichTooltip.vue'
211
325
  import PsIcon from '../ui/PsIcon.vue'
212
326
  import PsProgressBar from '../badges-and-tags/PsProgressBar.vue'
327
+ import PsTagScope from '../badges-and-tags/PsTagScope.vue'
328
+ import PsBarChart from '../data-graphics/PsBarChart.vue'
329
+
330
+ export const tableLayout = ['results', 'comparison']
213
331
 
214
332
  export default {
215
333
  name: 'PsTableResults',
216
- components: { PsProgressBar, PsIcon, PsRichTooltip },
334
+ components: { PsProgressBar, PsIcon, PsRichTooltip, PsTagScope, PsBarChart },
217
335
  props: {
336
+ /**
337
+ * It sets the layout of the table. eg: results or comparison
338
+ */
339
+ layout: {
340
+ type: String,
341
+ default: 'results',
342
+ validator: (value) => {
343
+ return tableLayout.indexOf(value) !== -1
344
+ }
345
+ },
218
346
  /**
219
347
  * It sets the max-height to table.
220
348
  */
@@ -278,6 +406,15 @@ export default {
278
406
  /**
279
407
  * It sets the values which will be use to render the body.
280
408
  */
409
+ existingColumnGroup: {
410
+ type: Array,
411
+ default() {
412
+ return []
413
+ }
414
+ },
415
+ /**
416
+ * It sets the values which will be use to render the body.
417
+ */
281
418
  rows: {
282
419
  type: Array,
283
420
  default() {
@@ -298,6 +435,15 @@ export default {
298
435
  type: Number,
299
436
  default: 1
300
437
  },
438
+ /**
439
+ * It sets the values which will be use to render the body.
440
+ */
441
+ policies: {
442
+ type: Array,
443
+ default() {
444
+ return []
445
+ }
446
+ },
301
447
  },
302
448
  data: () => ({
303
449
  collapsedRows : [],
@@ -409,7 +555,7 @@ export default {
409
555
  executeCallback(item, action) {
410
556
  if(item?.actions?.length === 1) item.actions[0].callback(item)
411
557
  else action?.callback(item)
412
- }
558
+ },
413
559
  },
414
560
  }
415
561
  </script>
@@ -15,7 +15,6 @@
15
15
  v-for="column of columnGroup.columns"
16
16
  :key="indexColumn + '-' + columnGroup.key + '-' + column.key"
17
17
  >
18
- <!-- <pre>{{ column }}</pre> -->
19
18
  <p>{{ column.title }}</p>
20
19
  </td>
21
20
  </template>
@@ -45,7 +45,7 @@
45
45
  :icon="orderDirection == 'asc' ? 'arrow_downward' : 'arrow_upward'"
46
46
  :type="orderDirection == 'asc' ? 'arrow_upward' : 'arrow_upward'"
47
47
  size="16"
48
- class="psui-cursor-pointer"
48
+ class="psui-cursor-pointer helper"
49
49
  icon-classes="psui-text-blue-50 psui-opacity-0 psui-leading-none psui-transition"
50
50
  :style="{ display: 'flex' }"
51
51
  @click.native="$emit('click-order-column', column)"
@@ -54,7 +54,7 @@
54
54
  <PsIcon
55
55
  icon="info"
56
56
  size="16"
57
- class="psui-cursor-pointer"
57
+ class="psui-cursor-pointer helper"
58
58
  icon-classes="psui-text-blue-50 psui-opacity-0 psui-leading-none psui-transition"
59
59
  :style="{ display: 'flex' }"
60
60
  @click.native="$emit('click-column-helper', column, $event)"
@@ -0,0 +1,122 @@
1
+ <template>
2
+ <thead>
3
+ <tr>
4
+ <th rowspan="2">
5
+ <div v-if="firstColumnTitle">
6
+ <p class="title">{{ firstColumnTitle }}</p>
7
+ </div>
8
+ <div>
9
+ <p v-if="firstColumnDescription" class="description">{{ firstColumnDescription }}</p>
10
+ </div>
11
+ </th>
12
+
13
+ <th
14
+ v-for="columnGroup of header"
15
+ :key="columnGroup.key"
16
+ :colspan="columnGroup.columns.reduce( (prev, cur) => cur.isActive ? prev + 1 : prev, 0 )"
17
+ >
18
+ <div class="psui-flex psui-space-x-1 psui-items-center psui-show-childrens-on-hover">
19
+ <p class="title">{{ columnGroup.title }}</p>
20
+ <PsIcon
21
+ icon="info"
22
+ size="18"
23
+ class="psui-cursor-pointer"
24
+ icon-classes="psui-text-blue-50 psui-opacity-0 psui-leading-none psui-transition"
25
+ :style="{ display: 'flex' }"
26
+ @click.native="$emit('click-column-group-helper', columnGroup, $event)"
27
+ />
28
+ </div>
29
+ </th>
30
+ </tr>
31
+
32
+ <tr>
33
+ <template v-for="columnGroup of header">
34
+ <th
35
+ v-for="column of columnGroup.columns"
36
+ :key="`${columnGroup.key}-${column.key}`"
37
+ :style="`--dataCount: ${columnGroup.columns.reduce( (prev, cur) => cur.isActive ? prev + 1 : prev, 0 )};`"
38
+ >
39
+ <div class="psui-flex psui-flex-col psui-justify-center psui-items-center psui-text-center">
40
+ <div class="psui-show-childrens-on-hover absolute-childrens psui-mb-px">
41
+ <p class="title" v-if="column.title">{{ column.title }}</p>
42
+
43
+ <PsIcon
44
+ icon="info"
45
+ size="16"
46
+ class="psui-cursor-pointer helper"
47
+ icon-classes="psui-text-blue-50 psui-opacity-0 psui-leading-none psui-transition"
48
+ :style="{ display: 'flex' }"
49
+ @click.native="$emit('click-column-helper', column, $event)"
50
+ />
51
+
52
+ <PsIcon
53
+ v-if="showOrder"
54
+ :icon="orderDirection == 'asc' ? 'arrow_downward' : 'arrow_upward'"
55
+ :type="orderDirection == 'asc' ? 'arrow_upward' : 'arrow_upward'"
56
+ size="16"
57
+ class="psui-cursor-pointer helper"
58
+ icon-classes="psui-text-blue-50 psui-opacity-0 psui-leading-none psui-transition"
59
+ :style="{ display: 'flex' }"
60
+ @click.native="$emit('click-order-column', column)"
61
+ />
62
+ </div>
63
+ <p class="description" v-if="column.description">{{ column.description }}</p>
64
+ </div>
65
+ </th>
66
+ </template>
67
+ </tr>
68
+ </thead>
69
+ </template>
70
+
71
+ <script>
72
+ import PsIcon from '../ui/PsIcon.vue'
73
+ export default {
74
+ name: 'PsTableResultsHead',
75
+ components: { PsIcon },
76
+ props: {
77
+ /**
78
+ * It sets the title for the first column.
79
+ */
80
+ firstColumnTitle: {
81
+ type: String,
82
+ default: ''
83
+ },
84
+ /**
85
+ * It sets the description for the first column.
86
+ */
87
+ firstColumnDescription: {
88
+ type: String,
89
+ default: ''
90
+ },
91
+ /**
92
+ * It sets the values which will be use to render the header.
93
+ */
94
+ header: {
95
+ type: Array,
96
+ required: true,
97
+ default: () => {
98
+ return []
99
+ },
100
+ },
101
+ /**
102
+ * this sets whether sorting will be displayed.
103
+ */
104
+ showOrder: {
105
+ type: Boolean,
106
+ default: false
107
+ }
108
+ },
109
+ computed: {
110
+ columnsSelectedForStudy() {
111
+ return this.$parent.columnsSelectedForStudy
112
+ },
113
+ orderColumn() {
114
+ return this.$parent.orderColumn
115
+ },
116
+ orderDirection() {
117
+ return this.$parent.orderDirection
118
+ },
119
+ }
120
+ }
121
+ </script>
122
+ <style> /* Please, use the file src/assets/scss/components/PsTableResults.scss */ </style>
@@ -15,7 +15,6 @@
15
15
  v-for="column of columnGroup.columns"
16
16
  :key="indexColumn + '-' + columnGroup.key + '-' + column.key"
17
17
  >
18
- <!-- <pre>{{ column }}</pre> -->
19
18
  <p>{{ row.data[column.key] }}</p>
20
19
  </td>
21
20
  </template>