@saooti/octopus-sdk 41.1.14 → 41.2.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/index.ts +24 -1
  3. package/package.json +1 -1
  4. package/src/api/groupsApi.ts +214 -0
  5. package/src/api/podcastApi.ts +47 -9
  6. package/src/components/buttons/ActionButton.vue +99 -0
  7. package/src/components/buttons/index.ts +5 -0
  8. package/src/components/composable/route/types.ts +11 -3
  9. package/src/components/composable/route/useAdvancedParamInit.ts +38 -13
  10. package/src/components/composable/useErrorHandler.ts +3 -2
  11. package/src/components/composable/useNotifications.ts +50 -0
  12. package/src/components/display/emission/EmissionGroupChooser.vue +56 -0
  13. package/src/components/display/emission/EmissionItem.vue +23 -3
  14. package/src/components/display/emission/EmissionList.vue +8 -2
  15. package/src/components/display/filter/AdvancedSearch.vue +82 -23
  16. package/src/components/display/list/ListPaginate.vue +4 -1
  17. package/src/components/display/podcasts/PodcastList.vue +12 -5
  18. package/src/components/display/podcasts/PodcastPlayButton.vue +2 -2
  19. package/src/components/display/podcasts/TagList.vue +4 -1
  20. package/src/components/form/ClassicMultiselect.vue +43 -37
  21. package/src/components/icons.ts +13 -0
  22. package/src/components/misc/ClassicAlert.vue +8 -1
  23. package/src/components/misc/ClassicBigChip.vue +84 -0
  24. package/src/components/misc/ClassicDataTable.vue +98 -0
  25. package/src/components/misc/ClassicDataTable_Internal.vue +132 -0
  26. package/src/components/misc/ClassicHelpButton.vue +3 -3
  27. package/src/components/misc/ClassicNotifications.vue +23 -0
  28. package/src/components/misc/ClassicPopover.vue +1 -0
  29. package/src/components/pages/EmissionPage.vue +2 -2
  30. package/src/components/pages/EmissionsPage.vue +10 -15
  31. package/src/components/pages/PodcastPage.vue +1 -1
  32. package/src/components/pages/PodcastsPage.vue +8 -20
  33. package/src/helper/fetchHelper.ts +1 -0
  34. package/src/locale/de.ts +2 -0
  35. package/src/locale/en.ts +2 -0
  36. package/src/locale/es.ts +2 -0
  37. package/src/locale/fr.ts +5 -3
  38. package/src/locale/it.ts +2 -0
  39. package/src/locale/sl.ts +2 -0
  40. package/src/router/router.ts +38 -53
  41. package/src/stores/class/general/emission.ts +8 -2
  42. package/src/style/_variables.scss +3 -0
  43. package/src/style/bootstrap.scss +5 -0
@@ -0,0 +1,50 @@
1
+ import { ref } from "vue";
2
+
3
+ type NotificationType = 'success'|'info'|'warning'|'error';
4
+
5
+ /**
6
+ * Type for notifications
7
+ */
8
+ interface Notification {
9
+ /** Optional title of the notification */
10
+ title?: string;
11
+ /** Message displayed by the notification */
12
+ message: string;
13
+ /** Type of notification, may affect display */
14
+ type: NotificationType;
15
+ /** When set to false, disallow manual closing of modal notification (default: true) */
16
+ closeable?: boolean;
17
+ }
18
+
19
+ /** The notification currently displayed */
20
+ const notification = ref<Notification|null>(null);
21
+
22
+ /**
23
+ * Composable used to manage notifications
24
+ */
25
+ export const useNotifications = () => {
26
+ /**
27
+ * Add & display a notification
28
+ * @param newNotif The data of the new notification
29
+ */
30
+ function addNotification(newNotif: Notification): void {
31
+ notification.value = { ...newNotif };
32
+ }
33
+
34
+ /**
35
+ * Remove all notifications
36
+ */
37
+ function clearNotifications(): void {
38
+ notification.value = null;
39
+ }
40
+
41
+ return {
42
+ /** The currently active notification */
43
+ notification,
44
+
45
+ /** Add a new notification to be displayed */
46
+ addNotification,
47
+ /** Remove all active notifications */
48
+ clearNotifications
49
+ }
50
+ };
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <ClassicMultiselect
3
+ id="group-chooser"
4
+ ref="selectGroup"
5
+ option-label="name"
6
+ :placeholder="$t('Search - Emission groups placeholder')"
7
+ :max-element="maxElement"
8
+ width="400px"
9
+ in-modal
10
+ :option-chosen="groups"
11
+ multiple
12
+ @on-search="onSearch"
13
+ @selected="emitSelected"
14
+ />
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { useTemplateRef } from "vue";
19
+
20
+ import ClassicMultiselect from "../../form/ClassicMultiselect.vue";
21
+ import { groupsApi, EmissionGroup } from "../../../api/groupsApi";
22
+
23
+ //Props
24
+ const props = defineProps<{
25
+ /** Filter by organisation */
26
+ organisationId?: Array<string>;
27
+ /** Currently selected groups */
28
+ groups: Array<EmissionGroup>;
29
+ }>();
30
+
31
+ //Emits
32
+ const emit = defineEmits(["update:groups"]);
33
+
34
+ //Data
35
+ const maxElement = 50;
36
+ const selectGroupRef = useTemplateRef('selectGroup');
37
+
38
+ //Methods
39
+ async function onSearch(query?: string): Promise<void> {
40
+ const response = await groupsApi.search({
41
+ first: 0,
42
+ size: maxElement,
43
+ search: query,
44
+ organisationIds: props.organisationId,
45
+ });
46
+
47
+ selectGroupRef.value!.afterSearch(
48
+ response.result.filter(g => g.emissionIds?.length ?? 0 > 0),
49
+ response.count
50
+ );
51
+ }
52
+
53
+ function emitSelected(option: Array<EmissionGroup>) {
54
+ emit("update:groups", option);
55
+ }
56
+ </script>
@@ -38,7 +38,7 @@
38
38
  <!-- eslint-disable vue/no-v-html -->
39
39
  <div
40
40
  ref="descriptionEmission"
41
- v-html="urlify(emission.description || '')"
41
+ v-html="description"
42
42
  />
43
43
  <!-- eslint-enable -->
44
44
  </div>
@@ -71,6 +71,7 @@ import { ListClassicReturn } from "@/stores/class/general/listReturn";
71
71
  import ClassicImageBanner from '../../misc/ClassicImageBanner.vue';
72
72
 
73
73
  import { useI18n } from "vue-i18n";
74
+ import { useResizePhone } from "../../composable/useResizePhone";
74
75
 
75
76
  //Props
76
77
  const props = defineProps<{
@@ -111,6 +112,25 @@ onMounted(()=>{
111
112
  }
112
113
  })
113
114
 
115
+ const { isPhone } = useResizePhone();
116
+ const description = computed((): string => {
117
+ let str = props.emission.description;
118
+ if (!str) {
119
+ return '';
120
+ }
121
+
122
+ // Truncate description on phones
123
+ if (isPhone.value === true) {
124
+ const pattern = /^(.+?<\/p>)/;
125
+ const matches = str.match(pattern);
126
+ if (matches && matches.length === 2) {
127
+ str = matches[1];
128
+ }
129
+ }
130
+
131
+ return urlify(str);
132
+ });
133
+
114
134
  //Methods
115
135
  function urlify(text:string|undefined){
116
136
  return displayHelper.urlify(text);
@@ -142,8 +162,8 @@ article {
142
162
  max-height: 500px;
143
163
 
144
164
  a {
145
- flex-direction: column;
146
- flex-wrap: nowrap;
165
+ flex-direction: column !important;
166
+ flex-wrap: nowrap !important;
147
167
  }
148
168
 
149
169
  .element-name {
@@ -71,6 +71,7 @@ import { Rubriquage } from "@/stores/class/rubrique/rubriquage";
71
71
  import { useFilterStore } from "../../../stores/FilterStore";
72
72
  import { ListClassicReturn } from "@/stores/class/general/listReturn";
73
73
  import { useI18n } from "vue-i18n";
74
+ import { EmissionGroup } from "../../../api/groupsApi";
74
75
  const EmissionItem = defineAsyncComponent(() => import("./EmissionItem.vue"));
75
76
  const EmissionPlayerItem = defineAsyncComponent(
76
77
  () => import("./EmissionPlayerItem.vue"),
@@ -94,7 +95,9 @@ const props = defineProps({
94
95
  noRubriquageId: { default: () => [], type: Array as () => Array<number> },
95
96
  nbPodcasts: { default: undefined, type: Number },
96
97
  /** The beneficiaries to filter on */
97
- beneficiaries: { default: null, type: Array as () => Array<string> }
98
+ beneficiaries: { default: null, type: Array as () => Array<string> },
99
+ /** The emission groups to filter on */
100
+ emissionGroups: { default: null, type: Array as () => Array<EmissionGroup> }
98
101
  })
99
102
 
100
103
  //Data
@@ -128,7 +131,8 @@ const changePaginate = computed(() => `${props.first}|${props.size}`);
128
131
  const changed = computed(() => {
129
132
  return `${props.organisationId}|${props.query}|${props.monetisable}|${props.includeHidden}|\
130
133
  ${props.iabId}|${props.rubriqueId}|${props.rubriquageId}|${props.before}|\
131
- ${props.after}|${props.sort}|${props.noRubriquageId}|${props.beneficiaries}`;
134
+ ${props.after}|${props.sort}|${props.noRubriquageId}|${props.beneficiaries}|\
135
+ ${props.emissionGroups}`;
132
136
  });
133
137
  const sortText = computed(() => {
134
138
  let textSort = "";
@@ -204,6 +208,8 @@ async function fetchContent(reset: boolean): Promise<void> {
204
208
  param.visible = 'VISIBLE';
205
209
  }
206
210
 
211
+ // TODO use emissionGroups
212
+
207
213
  try {
208
214
  const data = await classicApi.fetchData<ListClassicReturn<Emission>>({
209
215
  api: 0,
@@ -32,6 +32,23 @@
32
32
  @update:rubrique-filter="updateRubriquageFilter"
33
33
  />
34
34
 
35
+ <!-- Group filters -->
36
+ <div v-if="!isEmission && showEmissionGroups" class="mt-3 d-flex">
37
+ <ClassicCheckbox
38
+ v-model:text-init="emissionGroupCheckbox"
39
+ class="flex-shrink-0"
40
+ id-checkbox="search-emission-groups-checkbox"
41
+ :label="t('Filters - Emission groups')"
42
+ />
43
+
44
+ <EmissionGroupChooser
45
+ v-if="emissionGroupCheckbox"
46
+ class="ms-4 flex-grow-1"
47
+ :groups="emissionGroups"
48
+ @update:groups="updateEmissionGroupFilter"
49
+ />
50
+ </div>
51
+
35
52
  <!-- Date -->
36
53
  <DateFilter
37
54
  :is-emission="isEmission"
@@ -117,7 +134,7 @@ import { useAuthStore } from "../../../stores/AuthStore";
117
134
  import { useFilterStore } from "../../../stores/FilterStore";
118
135
  import { useRubriquesFilterParam } from "../../composable/route/useRubriquesFilterParam";
119
136
  import { RubriquageFilter } from "@/stores/class/rubrique/rubriquageFilter";
120
- import { defineAsyncComponent, ref, computed, watch } from "vue";
137
+ import { defineAsyncComponent, ref, computed, watch, onMounted } from "vue";
121
138
  import { useGeneralStore } from "../../../stores/GeneralStore";
122
139
  import { useI18n } from "vue-i18n";
123
140
  const MonetizableFilter = defineAsyncComponent(
@@ -141,27 +158,34 @@ const ClassicCheckbox = defineAsyncComponent(
141
158
  const DateFilter = defineAsyncComponent(() => import("./DateFilter.vue"));
142
159
  const SearchOrder = defineAsyncComponent(() => import("./SearchOrder.vue"));
143
160
  import { ROUTE_PARAMS } from '../../composable/route/types';
161
+ import { EmissionGroup, groupsApi } from "../../../api/groupsApi";
162
+ import EmissionGroupChooser from "../emission/EmissionGroupChooser.vue";
144
163
 
145
164
  //Props
146
- const props = defineProps({
147
- organisationId: { default: undefined, type: String },
148
- isEmission: { default: false, type: Boolean },
149
- includeHidden: { default: false, type: Boolean },
150
- sort: { default: "DATE", type: String },
151
- onlyVideo: { default: false, type: Boolean },
152
- monetisable: { default: "UNDEFINED", type: String },
153
- iabId: { default: undefined, type: Number },
154
- searchPattern: { default: "", type: String },
155
- fromDate: { default: undefined, type: String },
156
- toDate: { default: undefined, type: String },
157
- validity: { default: 'true', type: String },
165
+ const props = withDefaults(defineProps<{
166
+ organisationId?: string;
167
+ /** Indicates that the filters apply to emissions */
168
+ isEmission?: boolean;
169
+ includeHidden?: boolean;
170
+ sort?: string;
171
+ onlyVideo?: boolean;
172
+ monetisable?: string;
173
+ iabId?: number;
174
+ searchPattern?: string;
175
+ fromDate?: string;
176
+ toDate?: string;
177
+ validity?: string;
158
178
  /** The filter on beneficiaries */
159
- beneficiaries: { default: null, type: Array as () => Array<string> },
160
- rubriqueFilter: {
161
- default: () => [],
162
- type: Array as () => Array<RubriquageFilter>,
163
- },
164
- })
179
+ beneficiaries?: Array<string>;
180
+ rubriqueFilter?: Array<RubriquageFilter>;
181
+ /** The filter on groups */
182
+ emissionGroups?: Array<EmissionGroup>;
183
+ }>(), {
184
+ sort: "DATE",
185
+ monetisable: "UNDEFINED",
186
+ searchPattern: "",
187
+ validity: "true"
188
+ });
165
189
 
166
190
  //Emits
167
191
  const emit = defineEmits([
@@ -174,13 +198,14 @@ const emit = defineEmits([
174
198
  "update:validity",
175
199
  "update:rubriqueFilter",
176
200
  "update:onlyVideo",
177
- "update:beneficiaries"
201
+ "update:beneficiaries",
202
+ "update:emission-groups"
178
203
  ]);
179
204
 
180
205
  //Data
181
206
  const showFilters = ref(false);
182
207
  const firstLoaded = ref(false);
183
-
208
+ const showEmissionGroups = ref(false);
184
209
 
185
210
  //Composables
186
211
  const { t } = useI18n();
@@ -190,6 +215,13 @@ const generalStore = useGeneralStore();
190
215
  const filterStore = useFilterStore();
191
216
  const authStore = useAuthStore();
192
217
 
218
+ onMounted(async() => {
219
+ // Only show emission groups if there are some
220
+ const nbGroups = await groupsApi.count({
221
+ organisationIds: [props.organisationId]
222
+ });
223
+ showEmissionGroups.value = nbGroups > 0;
224
+ });
193
225
 
194
226
  //Computed
195
227
  const organisationRight = computed(() => isEditRights(props.organisationId));
@@ -205,6 +237,7 @@ const isSelectValidity = computed(() => {
205
237
  props.includeHidden
206
238
  );
207
239
  });
240
+
208
241
  /** The beneficiaries filter is only displayed if beneficiaries are enabled */
209
242
  const beneficiariesEnabled = computed(() => {
210
243
  return authStore.authOrganisation.attributes['beneficiaries.enabled'] === 'true';
@@ -288,11 +321,37 @@ function updateRubriquageFilter(value: Array<RubriquageFilter>) {
288
321
  filterRubriques = { rubriquesId: undefined };
289
322
  }
290
323
  updateRouteParamAdvanced({
291
- ...{ r: valueString.length ? valueString : undefined },
324
+ r: valueString.length ? valueString : undefined,
292
325
  ...filterRubriques,
293
326
  });
294
327
  }
295
328
 
329
+ /** Manage the checkbox enabling filtering on groups */
330
+ const emissionGroupCheckbox = computed({
331
+ get(): boolean {
332
+ return props.emissionGroups !== undefined && props.emissionGroups !== null;
333
+ },
334
+ set(value: boolean): void {
335
+ if (value) {
336
+ updateEmissionGroupFilter([]);
337
+ } else {
338
+ updateEmissionGroupFilter(undefined);
339
+ }
340
+ }
341
+ });
342
+
343
+ /** Update selected groups */
344
+ function updateEmissionGroupFilter(groups: Array<EmissionGroup>|undefined): void {
345
+ if (groups !== undefined && groups.length === 0) {
346
+ emit('update:emission-groups', []);
347
+ } else {
348
+ updateRouteParamAdvanced({
349
+ [ROUTE_PARAMS.EmissionGroups]: groups?.map(g => g.groupId)
350
+ });
351
+ emit('update:emission-groups', groups);
352
+ }
353
+ }
354
+
296
355
  /** Update the beneficiaries filter */
297
356
  function updateBeneficiaries(value: string[]|undefined): void {
298
357
  emit('update:beneficiaries', value);
@@ -350,4 +409,4 @@ function clickShowFilters(): void {
350
409
  overflow: hidden;
351
410
  }
352
411
  }
353
- </style>
412
+ </style>
@@ -1,6 +1,9 @@
1
1
  <template>
2
2
  <div :id="id" class="d-flex flex-column align-items-center">
3
- <ClassicLoading :loading-text="loadingText" :error-text="errorText" />
3
+ <ClassicLoading
4
+ v-if="loading || errorText"
5
+ :loading-text="loadingText" :error-text="errorText"
6
+ />
4
7
  <template v-if="!loading">
5
8
  <div
6
9
  v-if="!justSizeChosen"
@@ -56,17 +56,15 @@
56
56
  <script setup lang="ts">
57
57
  import ListPaginate from "../list/ListPaginate.vue";
58
58
  import {useErrorHandler} from "../../composable/useErrorHandler";
59
- import classicApi from "../../../api/classicApi";
60
59
  import PodcastItem from "./PodcastItem.vue";
61
60
  import ClassicLazy from "../../misc/ClassicLazy.vue";
62
61
  import { useFilterStore } from "../../../stores/FilterStore";
63
62
  import { Podcast, PodcastProcessingStatus, emptyPodcastData } from "../../../stores/class/general/podcast";
64
63
  import { computed, onBeforeMount, Ref, ref, watch } from "vue";
65
- import { FetchParam } from "@/stores/class/general/fetchParam";
66
64
  import { AxiosError } from "axios";
67
- import { ListClassicReturn } from "../../../stores/class/general/listReturn";
68
65
  import { useI18n } from "vue-i18n";
69
66
  import { podcastApi, PodcastMonetisation, PodcastSearchOptions, PodcastSort } from "../../../api/podcastApi";
67
+ import { EmissionGroup } from "@/api/groupsApi";
70
68
 
71
69
  //Props
72
70
  const props = withDefaults(defineProps<{
@@ -96,6 +94,8 @@ const props = withDefaults(defineProps<{
96
94
  forceUpdateParameters?: boolean;
97
95
  /** The beneficiaries to filter on */
98
96
  beneficiaries?: Array<string>;
97
+ /** The emission groups to filter on */
98
+ emissionGroups?: Array<EmissionGroup>;
99
99
  }>(), {
100
100
  first: 0,
101
101
  size: 30,
@@ -106,6 +106,7 @@ const props = withDefaults(defineProps<{
106
106
  displaySortText: true,
107
107
  validity: true,
108
108
  justSizeChosen: false,
109
+ withVideo: undefined,
109
110
  forceUpdateParameters: false
110
111
  });
111
112
 
@@ -140,7 +141,7 @@ const changed = computed(() => {
140
141
  return `${organisation.value}|${props.emissionId}|${props.sortCriteria}|${sort.value}
141
142
  ${props.iabId}|${props.participantId}|${props.query}|${props.monetisable}|${props.popularSort}|
142
143
  ${props.rubriqueId}|${props.rubriquageId}|${props.before}|${props.after}|${props.includeHidden}|${props.noRubriquageId}|${props.validity}|
143
- ${props.withVideo}|${props.includeTag}|${props.beneficiaries}`;
144
+ ${props.withVideo}|${props.includeTag}|${props.beneficiaries}|${props.emissionGroups}`;
144
145
  });
145
146
  const organisation = computed(() => {
146
147
  if (props.organisationId) {
@@ -200,6 +201,7 @@ async function fetchContent(reset: boolean): Promise<void> {
200
201
  pageSize: dsize.value,
201
202
  organisationId: organisation.value,
202
203
  emissionId: props.emissionId,
204
+ emissionGroups: props.emissionGroups,
203
205
  iabId: props.iabId,
204
206
  participantId: props.participantId,
205
207
  query: props.query,
@@ -223,11 +225,16 @@ async function fetchContent(reset: boolean): Promise<void> {
223
225
  tags: props.includeTag?.length ? props.includeTag : undefined,
224
226
  beneficiaries: props.beneficiaries ?? undefined
225
227
  };
228
+
226
229
  try {
227
230
  const data = await podcastApi.searchFull(param, true);
228
231
  afterFetching(reset, data);
229
232
  } catch (error) {
230
- handle403(error as AxiosError);
233
+ if (error instanceof AxiosError) {
234
+ handle403(error as AxiosError);
235
+ } else {
236
+ console.error('error', error);
237
+ }
231
238
  }
232
239
  }
233
240
  function afterFetching(
@@ -47,11 +47,11 @@
47
47
  @mouseenter="hoverType = 'video'"
48
48
  @mouseleave="hoverType = ''"
49
49
  >
50
+ <PodcastIsPlaying v-if="playingPodcast && playerStore.playerVideo" />
50
51
  <PlayVideoIcon
51
- v-if="!playerStore.playerVideo"
52
+ v-else
52
53
  :size="'video' === hoverType ? 50 : 40"
53
54
  />
54
- <PodcastIsPlaying v-if="playingPodcast && playerStore.playerVideo" />
55
55
  <time
56
56
  class="ms-2 me-2"
57
57
  :datetime="durationIso"
@@ -74,9 +74,12 @@ const filterStore = useFilterStore();
74
74
 
75
75
  //Computed
76
76
  const tagListFiltered = computed(() => {
77
- return props.tagList.filter((tag: string) => {
77
+ const tags = props.tagList.filter((tag: string) => {
78
78
  return !tag.match(/^\[\[.*\]\]$/);
79
79
  });
80
+
81
+ // Each tag is only displayed once
82
+ return tags.filter((tag, index) => tags.indexOf(tag) === index);
80
83
  });
81
84
  const organisationQuery = computed(() => {
82
85
  if(filterStore.filterOrgaId){
@@ -96,7 +96,7 @@
96
96
  </div>
97
97
  </template>
98
98
 
99
- <script setup lang="ts">
99
+ <script setup lang="ts" generic="T">
100
100
  import { computed, defineAsyncComponent, ref, Ref, watch } from "vue";
101
101
  import { useI18n } from "vue-i18n";
102
102
  import AsteriskIcon from "vue-material-design-icons/Asterisk.vue";
@@ -108,37 +108,43 @@ const ClassicPopover = defineAsyncComponent(
108
108
  );
109
109
 
110
110
  //Props
111
- const props = defineProps({
112
- id: { default: "", type: String },
113
- label: { default: "", type: String },
114
- placeholder: { default: "", type: String },
115
- optionLabel: { default: "", type: String },
116
- inModal: { default: false, type: Boolean },
117
- multiple: { default: false, type: Boolean },
118
- isDisabled: { default: false, type: Boolean },
119
- width: { default: "100%", type: String },
120
- height: { default: undefined, type: String },
121
- maxElement: { default: 50, type: Number },
122
- minSearchLength: { default: 3, type: Number },
123
- optionChosen: { default: undefined, type: Object as () => unknown },
124
- noDeselect: { default: true, type: Boolean },
125
- optionCustomTemplating: { default: "", type: String },
126
- optionSelectedCustomTemplating: { default: "", type: String },
127
- displayLabel: { default: false, type: Boolean },
128
- maxOptions: { default: null, type: Number },
129
- allowEmpty: { default: true, type: Boolean },
130
- textDanger :{ default: undefined, type: String },
131
- displayRequired: { default: false, type: Boolean },
132
- popover: { default: undefined, type: String },
133
- popoverRelativeClass: { default: undefined, type: String },
134
- })
111
+ const {
112
+ inModal = false, multiple = false, isDisabled = false, width = "100%",
113
+ maxElement = 50, minSearchLength = 3, noDeselect = true, displayLabel = false,
114
+ allowEmpty = true, optionChosen, maxOptions = null,
115
+ optionCustomTemplating = '', optionSelectedCustomTemplating = ''
116
+ } = defineProps<{
117
+ id?: string;
118
+ label?: string;
119
+ placeholder?: string;
120
+ optionLabel?: string;
121
+ inModal?: boolean;
122
+ multiple?: boolean;
123
+ isDisabled?: boolean;
124
+ width?: string;
125
+ height?: string;
126
+ maxElement?: number;
127
+ minSearchLength?: number;
128
+ /** Currently chosen option */
129
+ optionChosen?: T|Array<T>;
130
+ noDeselect?: boolean;
131
+ optionCustomTemplating?: string;
132
+ optionSelectedCustomTemplating?: string;
133
+ displayLabel?: boolean;
134
+ maxOptions?: number;
135
+ allowEmpty?: boolean;
136
+ textDanger ?:string;
137
+ displayRequired?: boolean;
138
+ popover?: string;
139
+ popoverRelativeClass?: string;
140
+ }>();
135
141
 
136
142
  //Emits
137
143
  const emit = defineEmits(["onSearch", "selected", "onClose"]);
138
144
 
139
145
  //Data
140
- const optionSelected : Ref<unknown>= ref(undefined);
141
- const options : Ref<Array<unknown>>= ref([]);
146
+ const optionSelected : Ref<T|T[]>= ref(undefined);
147
+ const options : Ref<Array<T>>= ref([]);
142
148
  const remainingElements = ref(0);
143
149
  const isLoading = ref(false);
144
150
  const searchInput = ref("");
@@ -149,20 +155,20 @@ const { t } = useI18n();
149
155
 
150
156
  //Computed
151
157
  const maxOptionsSelected = computed(() => {
152
- if (props.maxOptions !== null && props.multiple) {
158
+ if (maxOptions !== null && multiple) {
153
159
  return (
154
- (optionSelected.value as Array<unknown>).length >= props.maxOptions
160
+ (optionSelected.value as Array<T>).length >= maxOptions
155
161
  );
156
162
  }
157
163
  return false;
158
164
  });
159
165
 
160
166
  //Watch
161
- watch(()=>props.optionChosen, () => {
162
- optionSelected.value = props.optionChosen;
167
+ watch(()=>optionChosen, () => {
168
+ optionSelected.value = optionChosen;
163
169
  }, {deep: true, immediate: true});
164
170
  watch(optionSelected, () => {
165
- if (props.noDeselect || null !== optionSelected.value) {
171
+ if (noDeselect || null !== optionSelected.value) {
166
172
  return;
167
173
  }
168
174
  emit("selected", undefined);
@@ -173,7 +179,7 @@ function fakeSearch(): Array<unknown> {
173
179
  return options.value;
174
180
  }
175
181
  function onSearch(search?: string): void {
176
- if (search && search.length < props.minSearchLength) {
182
+ if (search && search.length < minSearchLength) {
177
183
  return;
178
184
  } else if (search) {
179
185
  searchInput.value = search;
@@ -185,20 +191,20 @@ function onClose() {
185
191
  emit("onClose", searchInput.value);
186
192
  searchInput.value = "";
187
193
  }
188
- function afterSearch(optionsFetched: Array<unknown>, count: number): void {
194
+ function afterSearch(optionsFetched: Array<T>, count: number): void {
189
195
  options.value = optionsFetched;
190
- remainingElements.value = Math.max(0, count - props.maxElement);
196
+ remainingElements.value = Math.max(0, count - maxElement);
191
197
  isLoading.value = false;
192
198
  }
193
199
  function onOptionSelected(optionSelected: unknown): void {
194
200
  emit("selected", optionSelected);
195
201
  }
196
202
  function onOptionDeselect(event: unknown): void {
197
- if (!props.multiple) {
203
+ if (!multiple) {
198
204
  return;
199
205
  }
200
206
  if (
201
- !props.allowEmpty &&
207
+ !allowEmpty &&
202
208
  0 === (optionSelected.value as Array<unknown>).length
203
209
  ) {
204
210
  (optionSelected.value as Array<unknown>).push(event);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Library of icons to use in the application
3
+ * Icons should be defined here, then used with their alias.
4
+ * This will allow for better consistency, and easily changing icons.
5
+ */
6
+
7
+ import SquareEditOutline from 'vue-material-design-icons/SquareEditOutline.vue';
8
+ import TrashCan from 'vue-material-design-icons/TrashCan.vue';
9
+
10
+ export default {
11
+ Delete: TrashCan,
12
+ Edit: SquareEditOutline
13
+ }
@@ -82,6 +82,13 @@ const iconComponent = computed(() => {
82
82
  display: flex;
83
83
  flex-direction: column;
84
84
  align-self: center;
85
+
86
+ &:deep(p) {
87
+ margin: 0 !important;
88
+ &:not(:first-child) {
89
+ margin-top: 8px !important;
90
+ }
91
+ }
85
92
  }
86
93
 
87
94
  $types: (
@@ -103,4 +110,4 @@ const iconComponent = computed(() => {
103
110
  }
104
111
  }
105
112
  }
106
- </style>
113
+ </style>