@dative-gpi/foundation-core-components 1.1.10 → 1.1.12-dashboards-explorer-01
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.
|
@@ -41,7 +41,7 @@ import { defineComponent, ref, type PropType, computed } from "vue";
|
|
|
41
41
|
|
|
42
42
|
import { EntityType } from "@dative-gpi/foundation-shared-domain/enums";
|
|
43
43
|
|
|
44
|
-
import type { DashboardOrganisationFilters, DashboardOrganisationTypeFilters, DeviceOrganisationFilters, FolderFilters, GroupFilters, LocationFilters, ModelFilters, UserOrganisationFilters } from "@dative-gpi/foundation-core-domain/models";
|
|
44
|
+
import type { DashboardOrganisationFilters, DashboardOrganisationTypeFilters, DashboardShallowFilters, DeviceOrganisationFilters, FolderFilters, GroupFilters, GroupingFilters, LocationFilters, ModelFilters, SubgroupingFilters, UserOrganisationFilters } from "@dative-gpi/foundation-core-domain/models";
|
|
45
45
|
|
|
46
46
|
import FSEntityFieldUI from "@dative-gpi/foundation-shared-components/components/fields/FSEntityFieldUI.vue";
|
|
47
47
|
|
|
@@ -141,7 +141,7 @@ export default defineComponent({
|
|
|
141
141
|
dashboardOrganisationsIds: props.modelValue,
|
|
142
142
|
dashboardOrganisationTypesIds: props.modelValue,
|
|
143
143
|
dashboardShallowsIds: props.modelValue
|
|
144
|
-
} satisfies DashboardOrganisationFilters & DashboardOrganisationTypeFilters;
|
|
144
|
+
} satisfies DashboardOrganisationFilters & DashboardOrganisationTypeFilters & DashboardShallowFilters;
|
|
145
145
|
case EntityType.Group:
|
|
146
146
|
return {
|
|
147
147
|
groupsIds: props.modelValue
|
|
@@ -164,14 +164,12 @@ export default defineComponent({
|
|
|
164
164
|
} satisfies ModelFilters;
|
|
165
165
|
case EntityType.Grouping:
|
|
166
166
|
return {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
};
|
|
167
|
+
groupingsIds: props.modelValue
|
|
168
|
+
} satisfies GroupingFilters;
|
|
170
169
|
case EntityType.Subgrouping:
|
|
171
170
|
return {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
};
|
|
171
|
+
subgroupingsIds: props.modelValue
|
|
172
|
+
} satisfies SubgroupingFilters;
|
|
175
173
|
default:
|
|
176
174
|
return undefined;
|
|
177
175
|
};
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FSDataTable
|
|
3
|
+
defaultMode="iterator"
|
|
4
|
+
:loading="fetchingDashboardExplorerElements"
|
|
5
|
+
:singleSelect="$props.singleSelect"
|
|
6
|
+
:items="dashboardExplorerElements"
|
|
7
|
+
:selectable="$props.selectable"
|
|
8
|
+
:tableCode="$props.tableCode"
|
|
9
|
+
:itemTo="$props.itemTo"
|
|
10
|
+
:noSearch="$props.recursiveSearch"
|
|
11
|
+
:headersOptions="headersOptions"
|
|
12
|
+
:modelValue="$props.modelValue"
|
|
13
|
+
@update:modelValue="onUpdate"
|
|
14
|
+
v-model:search="search"
|
|
15
|
+
v-bind="$attrs"
|
|
16
|
+
>
|
|
17
|
+
<template
|
|
18
|
+
v-for="(_, name) in $slots"
|
|
19
|
+
v-slot:[name]="slotData"
|
|
20
|
+
>
|
|
21
|
+
<slot
|
|
22
|
+
:name="name"
|
|
23
|
+
v-bind="slotData"
|
|
24
|
+
/>
|
|
25
|
+
</template>
|
|
26
|
+
<template
|
|
27
|
+
#header.imageId-title
|
|
28
|
+
>
|
|
29
|
+
<FSIcon>
|
|
30
|
+
mdi-panorama-variant-outline
|
|
31
|
+
</FSIcon>
|
|
32
|
+
</template>
|
|
33
|
+
<template
|
|
34
|
+
#item.imageId="{ item }"
|
|
35
|
+
>
|
|
36
|
+
<FSImage
|
|
37
|
+
v-if="item.imageId"
|
|
38
|
+
height="32px"
|
|
39
|
+
width="32px"
|
|
40
|
+
:imageId="item.imageId"
|
|
41
|
+
:thumbnail="true"
|
|
42
|
+
/>
|
|
43
|
+
</template>
|
|
44
|
+
<template
|
|
45
|
+
#item.tags="{ item }"
|
|
46
|
+
>
|
|
47
|
+
<FSTagGroup
|
|
48
|
+
variant="slide"
|
|
49
|
+
:showRemove="false"
|
|
50
|
+
:tags="item.tags"
|
|
51
|
+
/>
|
|
52
|
+
</template>
|
|
53
|
+
<template
|
|
54
|
+
#item.type="{ item }"
|
|
55
|
+
>
|
|
56
|
+
<FSDashboardExplorerElementChip
|
|
57
|
+
:type="item.type"
|
|
58
|
+
/>
|
|
59
|
+
</template>
|
|
60
|
+
<template
|
|
61
|
+
#item.icon="{ item }"
|
|
62
|
+
>
|
|
63
|
+
<FSIcon>
|
|
64
|
+
{{ item.icon }}
|
|
65
|
+
</FSIcon>
|
|
66
|
+
</template>
|
|
67
|
+
<template
|
|
68
|
+
#item.locked="{ item }"
|
|
69
|
+
>
|
|
70
|
+
<FSIconCheck
|
|
71
|
+
v-if="item.locked != null"
|
|
72
|
+
:value="item.locked"
|
|
73
|
+
/>
|
|
74
|
+
</template>
|
|
75
|
+
<template
|
|
76
|
+
#item.tile="{ index, item, toggleSelect }"
|
|
77
|
+
>
|
|
78
|
+
<FSFolderTileUI
|
|
79
|
+
v-if="item.type === DashboardExplorerElementType.Folder"
|
|
80
|
+
:key="index"
|
|
81
|
+
:to="$props.itemTo && $props.itemTo(item)"
|
|
82
|
+
:imageId="item.imageId"
|
|
83
|
+
:icon="item.icon"
|
|
84
|
+
:label="item.label"
|
|
85
|
+
:code="item.code"
|
|
86
|
+
:bottomColor="item.colors"
|
|
87
|
+
:recursiveFoldersIds="item.recursiveFoldersIds"
|
|
88
|
+
:recursiveDashboardOrganisationsIds="item.recursiveDashboardOrganisationsIds"
|
|
89
|
+
:recursiveDashboardShallowsIds="item.recursiveDashboardShallowsIds"
|
|
90
|
+
:modelValue="isSelected(item.id)"
|
|
91
|
+
:selectable="$props.selectable"
|
|
92
|
+
@update:modelValue="toggleSelect(item)"
|
|
93
|
+
/>
|
|
94
|
+
<FSDashboardOrganisationTileUI
|
|
95
|
+
v-if="item.type === DashboardExplorerElementType.DashboardOrganisation"
|
|
96
|
+
:key="index"
|
|
97
|
+
:to="$props.itemTo && $props.itemTo(item)"
|
|
98
|
+
:bottomColor="item.colors"
|
|
99
|
+
:imageId="item.imageId"
|
|
100
|
+
:icon="item.icon"
|
|
101
|
+
:label="item.label"
|
|
102
|
+
:code="item.code"
|
|
103
|
+
:modelValue="isSelected(item.id)"
|
|
104
|
+
:selectable="$props.selectable"
|
|
105
|
+
@update:modelValue="toggleSelect(item)"
|
|
106
|
+
/>
|
|
107
|
+
<FSDashboardShallowTileUI
|
|
108
|
+
v-if="item.type === DashboardExplorerElementType.DashboardShallow"
|
|
109
|
+
:key="index"
|
|
110
|
+
:to="$props.itemTo && $props.itemTo(item)"
|
|
111
|
+
:bottomColor="item.colors"
|
|
112
|
+
:imageId="item.imageId"
|
|
113
|
+
:icon="item.icon"
|
|
114
|
+
:label="item.label"
|
|
115
|
+
:code="item.code"
|
|
116
|
+
:modelValue="isSelected(item.id)"
|
|
117
|
+
:selectable="$props.selectable"
|
|
118
|
+
@update:modelValue="toggleSelect(item)"
|
|
119
|
+
/>
|
|
120
|
+
<FSDashboardOrganisationTypeTileUI
|
|
121
|
+
v-if="item.type === DashboardExplorerElementType.DashboardOrganisationType"
|
|
122
|
+
:key="index"
|
|
123
|
+
:to="$props.itemTo && $props.itemTo(item)"
|
|
124
|
+
:bottomColor="item.colors"
|
|
125
|
+
:imageId="item.imageId"
|
|
126
|
+
:icon="item.icon"
|
|
127
|
+
:label="item.label"
|
|
128
|
+
:code="item.code"
|
|
129
|
+
:modelValue="isSelected(item.id)"
|
|
130
|
+
:selectable="$props.selectable"
|
|
131
|
+
@update:modelValue="toggleSelect(item)"
|
|
132
|
+
/>
|
|
133
|
+
</template>
|
|
134
|
+
</FSDataTable>
|
|
135
|
+
</template>
|
|
136
|
+
|
|
137
|
+
<script lang="ts">
|
|
138
|
+
import { computed, defineComponent, type PropType, ref, watch } from "vue";
|
|
139
|
+
import { type RouteLocation } from "vue-router";
|
|
140
|
+
import _ from "lodash";
|
|
141
|
+
|
|
142
|
+
import { DashboardExplorerElementType } from "@dative-gpi/foundation-shared-domain/enums";
|
|
143
|
+
import { useDashboardExplorerElements } from "@dative-gpi/foundation-core-services/composables";
|
|
144
|
+
import { type DashboardExplorerElementInfos } from "@dative-gpi/foundation-core-domain/models";
|
|
145
|
+
import { dashboardExplorerElementTypeLabel } from "@dative-gpi/foundation-core-components/utils";
|
|
146
|
+
import { useDebounce } from "@dative-gpi/foundation-shared-components/composables";
|
|
147
|
+
|
|
148
|
+
import FSDashboardOrganisationTypeTileUI from "@dative-gpi/foundation-shared-components/components/tiles/FSDashboardOrganisationTypeTileUI.vue";
|
|
149
|
+
import FSDashboardOrganisationTileUI from "@dative-gpi/foundation-shared-components/components/tiles/FSDashboardOrganisationTileUI.vue";
|
|
150
|
+
import FSDashboardShallowTileUI from "@dative-gpi/foundation-shared-components/components/tiles/FSDashboardShallowTileUI.vue";
|
|
151
|
+
import FSFolderTileUI from "@dative-gpi/foundation-shared-components/components/tiles/FSFolderTileUI.vue";
|
|
152
|
+
import FSIconCheck from "@dative-gpi/foundation-shared-components/components/FSIconCheck.vue";
|
|
153
|
+
import FSTagGroup from "@dative-gpi/foundation-shared-components/components/FSTagGroup.vue";
|
|
154
|
+
import FSImage from "@dative-gpi/foundation-shared-components/components/FSImage.vue";
|
|
155
|
+
import FSIcon from "@dative-gpi/foundation-shared-components/components/FSIcon.vue";
|
|
156
|
+
|
|
157
|
+
import FSDashboardExplorerElementChip from "./FSDashboardExplorerElementChip.vue";
|
|
158
|
+
import FSDataTable from "../lists/FSDataTable.vue";
|
|
159
|
+
|
|
160
|
+
export default defineComponent({
|
|
161
|
+
name: "FSBaseDashboardsExplorer",
|
|
162
|
+
components: {
|
|
163
|
+
FSDashboardExplorerElementChip,
|
|
164
|
+
FSDashboardOrganisationTypeTileUI,
|
|
165
|
+
FSDashboardOrganisationTileUI,
|
|
166
|
+
FSDashboardShallowTileUI,
|
|
167
|
+
FSFolderTileUI,
|
|
168
|
+
FSDataTable,
|
|
169
|
+
FSIconCheck,
|
|
170
|
+
FSTagGroup,
|
|
171
|
+
FSImage,
|
|
172
|
+
FSIcon
|
|
173
|
+
},
|
|
174
|
+
props: {
|
|
175
|
+
tableCode: {
|
|
176
|
+
type: String as PropType<string | null>,
|
|
177
|
+
required: false,
|
|
178
|
+
default: null
|
|
179
|
+
},
|
|
180
|
+
recursiveSearch: {
|
|
181
|
+
type: Boolean,
|
|
182
|
+
required: false,
|
|
183
|
+
default: true
|
|
184
|
+
},
|
|
185
|
+
parentId: {
|
|
186
|
+
type: String as PropType<string | null>,
|
|
187
|
+
required: false,
|
|
188
|
+
default: null
|
|
189
|
+
},
|
|
190
|
+
root: {
|
|
191
|
+
type: Boolean,
|
|
192
|
+
required: false,
|
|
193
|
+
default: true
|
|
194
|
+
},
|
|
195
|
+
allowedTypes: {
|
|
196
|
+
type: Array as PropType<DashboardExplorerElementType[]>,
|
|
197
|
+
required: false,
|
|
198
|
+
default: () => [
|
|
199
|
+
DashboardExplorerElementType.Folder,
|
|
200
|
+
DashboardExplorerElementType.DashboardOrganisation,
|
|
201
|
+
DashboardExplorerElementType.DashboardShallow,
|
|
202
|
+
DashboardExplorerElementType.DashboardOrganisationType
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
itemTo: {
|
|
206
|
+
type: Function as PropType<(item: DashboardExplorerElementInfos) => Partial<RouteLocation>>,
|
|
207
|
+
required: false
|
|
208
|
+
},
|
|
209
|
+
selectable: {
|
|
210
|
+
type: Boolean,
|
|
211
|
+
required: false,
|
|
212
|
+
default: true
|
|
213
|
+
},
|
|
214
|
+
singleSelect: {
|
|
215
|
+
type: Boolean,
|
|
216
|
+
required: false,
|
|
217
|
+
default: false
|
|
218
|
+
},
|
|
219
|
+
modelValue: {
|
|
220
|
+
type: Array as PropType<string[]>,
|
|
221
|
+
default: () => [],
|
|
222
|
+
required: false
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
emits: ["update:modelValue", "update:types"],
|
|
226
|
+
setup(props, { emit }) {
|
|
227
|
+
const { entities, fetching: fetchingDashboardExplorerElements, getMany: getManyDashboardExplorerElements } = useDashboardExplorerElements();
|
|
228
|
+
const { debounce } = useDebounce();
|
|
229
|
+
|
|
230
|
+
const search = ref("");
|
|
231
|
+
|
|
232
|
+
const dashboardExplorerElements = computed((): DashboardExplorerElementInfos[] => {
|
|
233
|
+
return entities.value
|
|
234
|
+
.filter(e => props.allowedTypes.includes(e.type))
|
|
235
|
+
.slice()
|
|
236
|
+
.sort((a, b) => a.type - b.type);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const headersOptions = computed(() => ({
|
|
240
|
+
type: {
|
|
241
|
+
fixedFilters: props.allowedTypes.map(t => ({
|
|
242
|
+
value: t,
|
|
243
|
+
text: dashboardExplorerElementTypeLabel(t)
|
|
244
|
+
})),
|
|
245
|
+
methodFilter: (value: DashboardExplorerElementType, type: DashboardExplorerElementType) => {
|
|
246
|
+
return value === type;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}));
|
|
250
|
+
|
|
251
|
+
const isSelected = (id: string): boolean => {
|
|
252
|
+
return props.modelValue.includes(id);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const onUpdate = (value: string[]): void => {
|
|
256
|
+
const types = value.map(id => entities.value.find(dee => dee.id === id)?.type);
|
|
257
|
+
emit("update:types", types);
|
|
258
|
+
|
|
259
|
+
emit("update:modelValue", value);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const fetch = () => {
|
|
263
|
+
if (props.recursiveSearch && search.value) {
|
|
264
|
+
getManyDashboardExplorerElements({
|
|
265
|
+
parentId: props.parentId,
|
|
266
|
+
search: search.value,
|
|
267
|
+
types: props.allowedTypes
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
getManyDashboardExplorerElements({
|
|
272
|
+
parentId: props.parentId,
|
|
273
|
+
root: props.root,
|
|
274
|
+
types: props.allowedTypes
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Delay to wait before fetching after a search change
|
|
280
|
+
const debounceFetch = (): void => debounce(fetch, 1000);
|
|
281
|
+
|
|
282
|
+
watch([() => props.parentId, () => props.root, () => props.allowedTypes], (next, previous) => {
|
|
283
|
+
if ((!next && !previous) || !_.isEqual(next, previous)) {
|
|
284
|
+
fetch();
|
|
285
|
+
}
|
|
286
|
+
}, { immediate: true });
|
|
287
|
+
|
|
288
|
+
watch(search, (next, previous) => {
|
|
289
|
+
if (props.recursiveSearch && next !== previous) {
|
|
290
|
+
debounceFetch();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
fetchingDashboardExplorerElements,
|
|
296
|
+
DashboardExplorerElementType,
|
|
297
|
+
dashboardExplorerElements,
|
|
298
|
+
headersOptions,
|
|
299
|
+
search,
|
|
300
|
+
isSelected,
|
|
301
|
+
onUpdate
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
</script>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FSChip
|
|
3
|
+
:label="chipLabel"
|
|
4
|
+
:color="ColorEnum.Light"
|
|
5
|
+
/>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import { computed, defineComponent, type PropType } from "vue";
|
|
10
|
+
|
|
11
|
+
import type { DashboardExplorerElementType } from "@dative-gpi/foundation-shared-domain/enums";
|
|
12
|
+
import { dashboardExplorerElementTypeLabel } from "@dative-gpi/foundation-core-components/utils";
|
|
13
|
+
|
|
14
|
+
import FSChip from "@dative-gpi/foundation-shared-components/components/FSChip.vue";
|
|
15
|
+
import { ColorEnum } from "@dative-gpi/foundation-shared-components/models";
|
|
16
|
+
|
|
17
|
+
export default defineComponent({
|
|
18
|
+
name: "FSDashboardExplorerElementChip",
|
|
19
|
+
components: {
|
|
20
|
+
FSChip
|
|
21
|
+
},
|
|
22
|
+
props: {
|
|
23
|
+
type: {
|
|
24
|
+
type: Number as PropType<DashboardExplorerElementType>,
|
|
25
|
+
required: true
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
setup(props) {
|
|
29
|
+
const chipLabel = computed(() => dashboardExplorerElementTypeLabel(props.type));
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
chipLabel,
|
|
33
|
+
ColorEnum
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
</script>
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"url": "https://github.com/Dative-GPI/foundation-shared-ui.git"
|
|
5
5
|
},
|
|
6
6
|
"sideEffects": false,
|
|
7
|
-
"version": "1.1.
|
|
7
|
+
"version": "1.1.12-dashboards-explorer-01",
|
|
8
8
|
"description": "",
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
"author": "",
|
|
14
14
|
"license": "ISC",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@dative-gpi/foundation-core-domain": "1.1.
|
|
17
|
-
"@dative-gpi/foundation-core-services": "1.1.
|
|
18
|
-
"@dative-gpi/foundation-shared-components": "1.1.
|
|
19
|
-
"@dative-gpi/foundation-shared-domain": "1.1.
|
|
20
|
-
"@dative-gpi/foundation-shared-services": "1.1.
|
|
16
|
+
"@dative-gpi/foundation-core-domain": "1.1.12-dashboards-explorer-01",
|
|
17
|
+
"@dative-gpi/foundation-core-services": "1.1.12-dashboards-explorer-01",
|
|
18
|
+
"@dative-gpi/foundation-shared-components": "1.1.12-dashboards-explorer-01",
|
|
19
|
+
"@dative-gpi/foundation-shared-domain": "1.1.12-dashboards-explorer-01",
|
|
20
|
+
"@dative-gpi/foundation-shared-services": "1.1.12-dashboards-explorer-01"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"@dative-gpi/bones-ui": "^1.0.0",
|
|
@@ -29,5 +29,5 @@
|
|
|
29
29
|
"sass": "1.71.1",
|
|
30
30
|
"sass-loader": "13.3.2"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "fb53e71b5c00401b3238a32e8df80f1772a01e45"
|
|
33
33
|
}
|
package/utils/dashboards.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DashboardOrganisationInfos, DashboardOrganisationTypeInfos, DashboardShallowInfos, FolderInfos } from "@dative-gpi/foundation-core-domain/models";
|
|
2
2
|
import { useTranslations as useTranslationsProvider } from "@dative-gpi/bones-ui/composables";
|
|
3
3
|
import { type ColorBase, ColorEnum } from "@dative-gpi/foundation-shared-components/models";
|
|
4
|
-
import { DashboardType } from "@dative-gpi/foundation-shared-domain/enums";
|
|
4
|
+
import { DashboardExplorerElementType, DashboardType } from "@dative-gpi/foundation-shared-domain/enums";
|
|
5
5
|
|
|
6
6
|
const { $tr } = useTranslationsProvider();
|
|
7
7
|
|
|
@@ -49,3 +49,13 @@ export enum FoldersListType {
|
|
|
49
49
|
|
|
50
50
|
export type DashboardsListItem = DashboardShallowListItem | DashboardOrganisationListItem | OrganisationTypeDashboardListItem;
|
|
51
51
|
export type FoldersListItem = DashboardShallowListItem | DashboardOrganisationListItem | FolderListItem;
|
|
52
|
+
|
|
53
|
+
export const dashboardExplorerElementTypeLabel = (type: DashboardExplorerElementType): string => {
|
|
54
|
+
switch (type) {
|
|
55
|
+
case DashboardExplorerElementType.Folder: return $tr("ui.common.folder", "Folder");
|
|
56
|
+
case DashboardExplorerElementType.DashboardOrganisation: return $tr("ui.common.custom", "Custom");
|
|
57
|
+
case DashboardExplorerElementType.DashboardShallow: return $tr("ui.dashboard-type.shallow", "Shallow copy");
|
|
58
|
+
case DashboardExplorerElementType.DashboardOrganisationType: return $tr("ui.common.shared", "Shared");
|
|
59
|
+
default: return $tr("ui.common.none", "None");
|
|
60
|
+
}
|
|
61
|
+
};
|