@saooti/octopus-sdk 41.7.2 → 41.8.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 (36) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/CHANGELOG.md +28 -0
  3. package/index.ts +6 -2
  4. package/package.json +1 -1
  5. package/src/api/emissionApi.ts +44 -1
  6. package/src/api/podcastApi.ts +27 -5
  7. package/src/components/composable/route/useAdvancedParamInit.ts +4 -3
  8. package/src/components/composable/useRights.ts +196 -0
  9. package/src/components/composable/useSeasonsManagement.ts +43 -0
  10. package/src/components/display/live/RadioPlanning.vue +6 -4
  11. package/src/components/display/podcasts/PodcastFilterList.vue +100 -18
  12. package/src/components/display/podcasts/PodcastInlineListTemplate.vue +4 -1
  13. package/src/components/display/podcasts/PodcastList.vue +5 -1
  14. package/src/components/display/podcasts/PodcastModuleBox.vue +21 -4
  15. package/src/components/display/podcasts/PodcastSwiperList.vue +9 -3
  16. package/src/components/form/ClassicRadio.vue +13 -14
  17. package/src/components/misc/TopBar.vue +7 -1
  18. package/src/components/pages/EmissionPage.vue +22 -10
  19. package/src/locale/de.json +7 -1
  20. package/src/locale/en.json +7 -1
  21. package/src/locale/es.json +7 -1
  22. package/src/locale/fr.json +7 -1
  23. package/src/locale/it.json +7 -1
  24. package/src/locale/sl.json +7 -1
  25. package/src/stores/class/general/emission.ts +20 -0
  26. package/src/stores/class/general/podcast.ts +17 -0
  27. package/src/style/general.scss +7 -0
  28. package/tests/api/podcastApi.spec.ts +43 -0
  29. package/tests/components/composable/useAdvancedParamInit.spec.ts +90 -0
  30. package/tests/components/composable/useRights.spec.ts +265 -0
  31. package/tests/components/composable/useSeasonsManagement.spec.ts +35 -0
  32. package/tests/components/display/podcasts/PodcastFilterList.spec.ts +33 -0
  33. package/tests/components/display/podcasts/PodcastInlineListTemplate.spec.ts +23 -0
  34. package/tests/components/display/podcasts/PodcastModuleBox.spec.ts +49 -22
  35. package/tests/components/pages/EmissionPage.spec.ts +86 -0
  36. package/tests/utils.ts +12 -1
@@ -8,7 +8,8 @@
8
8
  "Bash(npm audit:*)",
9
9
  "Bash(python3:*)",
10
10
  "Bash(node --version:*)",
11
- "Bash(npm:*)"
11
+ "Bash(npm:*)",
12
+ "Bash(npx vitest:*)"
12
13
  ]
13
14
  }
14
15
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 41.8.0 (18/03/2026)
4
+
5
+ **Features**
6
+
7
+ - **14083** - Ajout des fonctionnalités de saisons et types d'épisodes
8
+ - Ajout des propriétés relatives aux saisons sur les émissions et les épisodes
9
+ - `PodcastFilterList` permet de regrouper les épisodes en saisons
10
+ - `EmissionPage` affiche ses épisodes en saisons si définies
11
+ - `PodcastModuleBox` affiche le numéro de saison et d'épisode du podcast si
12
+ définis et que le `seasonMode` permet leur affichage
13
+ - Ajout d'une classe `required` pour afficher une asterisque sur les champs
14
+ requis
15
+
16
+ **Fix**
17
+
18
+ - **14291** - Activation du bouton de génération de la transcription suite à
19
+ correction des droits
20
+
21
+ ## 41.7.3 (11/03/2026)
22
+
23
+ **Features**
24
+
25
+ - Intégration du composable de vérification des droits
26
+
27
+ **Fix**
28
+
29
+ - Correction affichage épisodes à valider pour `PODCAST_VALIDATION`.
30
+
3
31
  ## 41.7.2 (10/03/2026)
4
32
 
5
33
  **Fix**
package/index.ts CHANGED
@@ -118,6 +118,7 @@ export const getClassicTagInput = () => import("./src/components/form/ClassicTag
118
118
  export const getClassicWysiwyg = () => import("./src/components/form/ClassicWysiwyg.vue");
119
119
 
120
120
  //Composable
121
+ import { useRights, EditRight } from "./src/components/composable/useRights.ts";
121
122
  import {useResizePhone} from "./src/components/composable/useResizePhone";
122
123
  import {useTagOf} from "./src/components/composable/useTagOf.ts";
123
124
  import {useSelenium} from "./src/components/composable/useSelenium.ts";
@@ -133,6 +134,7 @@ export { useSharePlatforms, SharePlatformName, type SharePlatform } from "./src/
133
134
  export { useSharePath } from "./src/components/composable/share/useSharePath.ts";
134
135
  export { useOrgaComputed } from "./src/components/composable/useOrgaComputed.ts";
135
136
  export { useSeoTitleUrl } from "./src/components/composable/route/useSeoTitleUrl.ts";
137
+ export { useSeasonsManagement } from "./src/components/composable/useSeasonsManagement.ts";
136
138
 
137
139
  //helper
138
140
  import domHelper from "./src/helper/domHelper.ts";
@@ -165,8 +167,8 @@ export { playlistApi } from "./src/api/playlistApi.ts";
165
167
  export { podcastApi, PodcastSort, type PodcastSearchOptions } from "./src/api/podcastApi.ts";
166
168
 
167
169
  // Types
168
- export { type Emission, emptyEmissionData } from "./src/stores/class/general/emission.ts";
169
- export { type Podcast, type PodcastAvailability } from "./src/stores/class/general/podcast.ts";
170
+ export { type Emission, SeasonMode, emptyEmissionData } from "./src/stores/class/general/emission.ts";
171
+ export { type Podcast, type PodcastAvailability, PodcastType } from "./src/stores/class/general/podcast.ts";
170
172
  export { type Playlist, type PlaylistRule } from "./src/stores/class/general/playlist.ts";
171
173
  export { type Annotations } from "./src/stores/class/general";
172
174
 
@@ -202,6 +204,8 @@ import { ROUTE_PARAMS } from "./src/components/composable/route/types";
202
204
  import { defineAsyncComponent } from "vue";
203
205
 
204
206
  export {
207
+ useRights,
208
+ EditRight,
205
209
  useResizePhone,
206
210
  useTagOf,
207
211
  useSelenium,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saooti/octopus-sdk",
3
- "version": "41.7.2",
3
+ "version": "41.8.0",
4
4
  "private": false,
5
5
  "description": "Javascript SDK for using octopus",
6
6
  "author": "Saooti",
@@ -2,6 +2,7 @@ import classicApi from "./classicApi";
2
2
  import { ModuleApi } from "./apiConnection";
3
3
  import { mapFromGetAll } from "./apiUtils";
4
4
  import { Emission } from "@/stores/class/general/emission";
5
+ import { ListClassicReturn } from "@/stores/class/general/listReturn";
5
6
 
6
7
  /**
7
8
  * Retrieve an emission by ID
@@ -25,7 +26,49 @@ async function getAllById(emissionIds: Array<number>): Promise<Record<string, Em
25
26
  return mapFromGetAll(emissionIds, get, 'emissionId');
26
27
  }
27
28
 
29
+
30
+ /**
31
+ * Search for emissions
32
+ * @param query A filter on the name of the emission
33
+ * @param options Optional options to filter the results
34
+ */
35
+ async function search(query?: string, options?: {
36
+ /** The index of the first element to retrieve */
37
+ first?: number;
38
+ /** The number of elements to retrieve */
39
+ size?: number;
40
+ specialTreatment?: boolean;
41
+ distributedBy?: string;
42
+ /** Filter by organisation */
43
+ organisationId?: string|string[];
44
+ }): Promise<ListClassicReturn<Emission>> {
45
+ return classicApi.fetchData<ListClassicReturn<Emission>>({
46
+ api: ModuleApi.DEFAULT,
47
+ path: "emission/search",
48
+ parameters: {
49
+ query,
50
+ first: options?.first ?? 0,
51
+ size: options?.size ?? 10,
52
+ distributedBy: options?.distributedBy,
53
+ organisationId: options?.organisationId
54
+ },
55
+ specialTreatement: options?.specialTreatment ?? true
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Remove seasons data from emission
61
+ * @param emissionId The ID of the emission for which to remove the data
62
+ */
63
+ async function resetSeasons(emissionId: number): Promise<void> {
64
+ return classicApi.putData({
65
+ api: ModuleApi.DEFAULT,
66
+ path: 'emission/seasons/reset/' + emissionId
67
+ });
68
+ }
28
69
  export const emissionApi = {
29
70
  get,
30
- getAllById
71
+ getAllById,
72
+ search,
73
+ resetSeasons
31
74
  };
@@ -1,4 +1,4 @@
1
- import { Podcast, PodcastProcessingStatus, SimplifiedPodcast } from '../stores/class/general/podcast';
1
+ import { Podcast, PodcastProcessingStatus, PodcastType, SimplifiedPodcast } from '../stores/class/general/podcast';
2
2
  import { ListClassicReturn } from '../stores/class/general/listReturn';
3
3
  import { useAuthStore } from '../stores/AuthStore';
4
4
  import classicApi from './classicApi';
@@ -17,7 +17,9 @@ export enum PodcastSort {
17
17
  POPULARITY = 'POPULARITY',
18
18
  SCORE = 'SCORE',
19
19
  UPDATE_ASC = 'UPDATE_ASC',
20
- UPDATE_DESC = 'UPDATE_DESC'
20
+ UPDATE_DESC = 'UPDATE_DESC',
21
+ /** Smart sort using seasons settings */
22
+ SEASONAL = 'SEASONAL'
21
23
  }
22
24
 
23
25
  export enum PodcastMonetisation {
@@ -68,11 +70,15 @@ export interface PodcastSearchOptions extends Paginable<PodcastSort> {
68
70
  tags?: Array<string>;
69
71
  /** Filter by beneficiaries/rights holder reference */
70
72
  beneficiaries?: Array<string>;
73
+ /** Filter by seasons */
74
+ season?: Array<number>;
75
+ /** Filter by season episode number */
76
+ seasonEpisode?: Array<number>;
77
+ /** Filter by episode type */
78
+ episodeType?: PodcastType;
71
79
  }
72
80
 
73
81
  async function downloadRegister(podcastId: number, parameters?: Record<string,unknown>): Promise<{ location: string; downloadId: number }> {
74
- const authStore = useAuthStore();
75
-
76
82
  return classicApi.fetchData<{
77
83
  location: string;
78
84
  downloadId: number;
@@ -137,7 +143,7 @@ function processSearchParameters(search: PodcastSearchOptions): FetchParam {
137
143
  * @param adaptParameters If true, some adjustments will be made to the parameters
138
144
  * @return A list of simplified podcasts
139
145
  */
140
- function search(options: PodcastSearchOptions, adaptParameters?: boolean): Promise<ListClassicReturn<SimplifiedPodcast>> {
146
+ async function search(options: PodcastSearchOptions, adaptParameters?: boolean): Promise<ListClassicReturn<SimplifiedPodcast>> {
141
147
  return classicApi.fetchData<ListClassicReturn<SimplifiedPodcast>>({
142
148
  api: ModuleApi.DEFAULT,
143
149
  path: 'v2/podcast/search',
@@ -146,6 +152,21 @@ function search(options: PodcastSearchOptions, adaptParameters?: boolean): Promi
146
152
  });
147
153
  }
148
154
 
155
+ /**
156
+ * Count podcasts matching the filters
157
+ * @param options The search criterias
158
+ * @param adaptParameters If true, some adjustments will be made to the parameters
159
+ * @return A list of simplified podcasts
160
+ */
161
+ async function count(options: PodcastSearchOptions, adaptParameters?: boolean): Promise<number> {
162
+ const result = await search({
163
+ ...options,
164
+ size: 0
165
+ }, adaptParameters);
166
+
167
+ return result.count;
168
+ }
169
+
149
170
  /**
150
171
  * Retrieve the podcasts matching the search criterias, with all their data.
151
172
  * This query is longer, because it also needs to retrieve organisations &
@@ -185,6 +206,7 @@ async function searchFull(options:PodcastSearchOptions, adaptParameters?: boolea
185
206
  }
186
207
 
187
208
  export const podcastApi = {
209
+ count,
188
210
  downloadRegister,
189
211
  get,
190
212
  search,
@@ -9,6 +9,7 @@ import dayjs from "dayjs";
9
9
 
10
10
  import { RouteProps } from "./types";
11
11
  import { EmissionGroup, groupsApi } from "../../../api/groupsApi";
12
+ import { useRights } from "../useRights";
12
13
 
13
14
  export const useAdvancedParamInit = (props: RouteProps, isEmission: boolean) => {
14
15
 
@@ -18,7 +19,7 @@ export const useAdvancedParamInit = (props: RouteProps, isEmission: boolean) =>
18
19
 
19
20
  const filterStore = useFilterStore();
20
21
  const authStore = useAuthStore();
21
-
22
+ const { canValidatePodcast } = useRights();
22
23
 
23
24
  const isInit = ref(false);
24
25
  const monetisable = ref("UNDEFINED");// UNDEFINED, YES, NO
@@ -125,8 +126,8 @@ export const useAdvancedParamInit = (props: RouteProps, isEmission: boolean) =>
125
126
  includeHidden.value = undefined !== organisation.value && organisationRight.value && "false"!==props.routeIncludeHidden;
126
127
  }
127
128
 
128
- function initValidity(){
129
- const cantDisplay = isPodcastmaker.value || isEmission || !includeHidden.value || !authStore.isRoleContribution || !organisationRight.value;
129
+ function initValidity() {
130
+ const cantDisplay = isPodcastmaker.value || isEmission || !includeHidden.value || !canValidatePodcast();
130
131
  if(cantDisplay){
131
132
  validity.value = "true";
132
133
  }else{
@@ -0,0 +1,196 @@
1
+ import { useAuthStore } from "../../stores/AuthStore";
2
+ import type { Emission } from "../../stores/class/general/emission";
3
+ import type { Podcast } from "../../stores/class/general/podcast";
4
+
5
+ type Role =
6
+ 'ADMIN'|'ORGANISATION'|
7
+ 'PRODUCTION'|'RESTRICTED_PRODUCTION'|'PODCAST_CRUD'|'PODCAST_VALIDATION'|
8
+ 'PLAYLISTS'|'RESTRICTED_ANIMATION';
9
+
10
+ export enum EditRight {
11
+ None, // User cannot edit
12
+ Restricted, // User cannot edit because element is used elsewhere
13
+ Full // User can edit
14
+ }
15
+ /**
16
+ * Composable to manage rights.
17
+ * Based on AuthStore, but converts roles to easily usable tests for various
18
+ * actions.
19
+ */
20
+ export const useRights = () => {
21
+ const authStore = useAuthStore();
22
+
23
+ function roleContainsAny(...roles: Role[]): boolean {
24
+ return (authStore.authRole as Role[]).findIndex((r: Role) => roles.includes(r)) > -1;
25
+ }
26
+
27
+ // Creation is limited by roles
28
+ function canCreateEmission(): boolean {
29
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION');
30
+ }
31
+
32
+ function canEditEmission(emission: Emission): boolean {
33
+ if (
34
+ // Can edit new emissions
35
+ (!emission.emissionId && canCreateEmission()) ||
36
+ // Can edit when with sufficient rights
37
+ roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION')
38
+ ) {
39
+ return true;
40
+ }
41
+
42
+ // Can only edit if has created the emission
43
+ return (roleContainsAny('RESTRICTED_PRODUCTION') && emission.createdByUserId === authStore.authProfile?.userId);
44
+ }
45
+
46
+ function canDeleteEmission(): boolean {
47
+ // In case of restricted production, it will only delete podcasts
48
+ // created by user, and delete the emission only if empty afterwards
49
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION');
50
+ }
51
+
52
+ function canEditCommentsConfigEmission(): boolean {
53
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION');
54
+ }
55
+
56
+ function canCreatePodcast(): boolean {
57
+ // All roles that can create podcasts
58
+ return roleContainsAny(
59
+ 'ADMIN',
60
+ 'ORGANISATION',
61
+ 'PRODUCTION',
62
+ 'PODCAST_CRUD',
63
+ 'RESTRICTED_PRODUCTION',
64
+ 'RESTRICTED_ANIMATION'
65
+ );
66
+ }
67
+
68
+ function canDuplicatePodcast(): boolean {
69
+ // Same as creationm but notably without PODCAST_CRUD and
70
+ // RESTRICTED_ANIMATION
71
+ return roleContainsAny(
72
+ 'ADMIN',
73
+ 'ORGANISATION',
74
+ 'PRODUCTION',
75
+ 'RESTRICTED_PRODUCTION',
76
+ );
77
+ }
78
+
79
+ function canEditPodcast(podcast: Podcast): boolean {
80
+ // Full rights users can edit any podcast
81
+ if (roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION')) {
82
+ return true;
83
+ }
84
+
85
+ // RESTRICTED users can only edit their own podcasts
86
+ if (roleContainsAny('RESTRICTED_PRODUCTION', 'RESTRICTED_ANIMATION')) {
87
+ return podcast.createdByUserId === authStore.authProfile?.userId;
88
+ }
89
+
90
+ // PODCAST_CRUD can only edit their own non-valid podcasts
91
+ if (roleContainsAny('PODCAST_CRUD')) {
92
+ return podcast.valid === false &&
93
+ podcast.publisher?.userId === authStore.authProfile?.userId;
94
+ }
95
+
96
+ return false;
97
+ }
98
+
99
+ function canDeletePodcast(podcast: Podcast): boolean {
100
+ // Same permissions as editing
101
+ return canEditPodcast(podcast);
102
+ }
103
+
104
+ function canValidatePodcast(): boolean {
105
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'PODCAST_VALIDATION');
106
+ }
107
+
108
+ function canEditCommentsConfigPodcast(): boolean {
109
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION');
110
+ }
111
+
112
+ function canCreatePlaylist(): boolean {
113
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PLAYLISTS');
114
+ }
115
+
116
+ function canEditPlaylist(): boolean {
117
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PLAYLISTS');
118
+ }
119
+
120
+ function canDeletePlaylist(): boolean {
121
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PLAYLISTS');
122
+ }
123
+
124
+ function canCreateParticipant(): boolean {
125
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION');
126
+ }
127
+
128
+ async function getParticipantEditRight(participantId: number|undefined): Promise<EditRight> {
129
+ // New participants can be edited, and also with sufficient rights
130
+ if(!participantId || roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION')) {
131
+ return EditRight.Full;
132
+ } else {
133
+ return EditRight.None;
134
+ }
135
+ }
136
+
137
+ async function canEditParticipant(participantId: number|undefined): Promise<boolean> {
138
+ const editRight = await getParticipantEditRight(participantId);
139
+ return editRight === EditRight.Full;
140
+ }
141
+
142
+
143
+ async function canDeleteParticipant(participantId: number|undefined): Promise<boolean> {
144
+ const editRight = await getParticipantEditRight(participantId);
145
+ return editRight === EditRight.Full;
146
+ }
147
+
148
+ function canEditCodeInsertPlayer(): boolean {
149
+ return roleContainsAny('ADMIN', 'ORGANISATION');
150
+ }
151
+
152
+ function canEditTranscript(): boolean {
153
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION');
154
+ }
155
+
156
+ function canSeeHistory(): boolean {
157
+ return roleContainsAny('ADMIN', 'ORGANISATION');
158
+ }
159
+
160
+ function isRestrictedProduction(): boolean {
161
+ return roleContainsAny('RESTRICTED_PRODUCTION') && !roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION');
162
+ }
163
+
164
+ return {
165
+ // Emissions
166
+ canCreateEmission,
167
+ canEditEmission,
168
+ canDeleteEmission,
169
+ canEditCommentsConfigEmission,
170
+
171
+ // Podcasts
172
+ canCreatePodcast,
173
+ canDuplicatePodcast,
174
+ canEditPodcast,
175
+ canDeletePodcast,
176
+ canValidatePodcast,
177
+ canEditCommentsConfigPodcast,
178
+
179
+ // Playlists
180
+ canCreatePlaylist,
181
+ canEditPlaylist,
182
+ canDeletePlaylist,
183
+
184
+ // Participants
185
+ canCreateParticipant,
186
+ getParticipantEditRight,
187
+ canEditParticipant,
188
+ canDeleteParticipant,
189
+
190
+ // Other
191
+ canEditCodeInsertPlayer,
192
+ canEditTranscript,
193
+ canSeeHistory,
194
+ isRestrictedProduction
195
+ }
196
+ }
@@ -0,0 +1,43 @@
1
+ import { Podcast } from "@/stores/class/general/podcast";
2
+ import { Emission, SeasonMode } from "../../stores/class/general/emission";
3
+
4
+ /**
5
+ * Composable to facilitate seasons operations
6
+ */
7
+ export const useSeasonsManagement = () => {
8
+ /**
9
+ * Indicates that seasons are enabled on the given emission
10
+ * @param emission The emission to check for seasons
11
+ * @returns True if seasons are enabled, false otherwise
12
+ */
13
+ function areSeasonsEnabled(emission: Emission): boolean {
14
+ return [
15
+ SeasonMode.SEASON_WITHOUT_PODCAST_NUMBERING,
16
+ SeasonMode.SEASON_WITH_PODCAST_NUMBERING
17
+ ].includes(emission.seasonMode);
18
+ }
19
+
20
+ /**
21
+ * Simple formatter to display season/episode of the given podcast
22
+ * @param podcast The podcast to check
23
+ * @returns A string describing the season/episode of the podcast or null
24
+ * if no seasons are defined
25
+ */
26
+ function formatSeason(podcast: Podcast): string|null {
27
+ switch (podcast.emission.seasonMode) {
28
+ case SeasonMode.NO_SEASON:
29
+ return null;
30
+
31
+ case SeasonMode.SEASON_WITHOUT_PODCAST_NUMBERING:
32
+ return `S${podcast.seasonNumber}`;
33
+
34
+ case SeasonMode.SEASON_WITH_PODCAST_NUMBERING:
35
+ return `S${podcast.seasonNumber}·E${podcast.seasonEpisodeNumber}`;
36
+ }
37
+ }
38
+
39
+ return {
40
+ areSeasonsEnabled,
41
+ formatSeason
42
+ }
43
+ }
@@ -254,9 +254,7 @@ function createArrayDays() {
254
254
  });
255
255
  }
256
256
  }
257
- async function fetchOccurrencesAndLives(): Promise<
258
- Array<PlanningOccurrence | PlanningLive>
259
- > {
257
+ async function fetchOccurrencesAndLives(): Promise<Array<PlanningOccurrence|PlanningLive>> {
260
258
  const params = {
261
259
  canalId: props.radio?.id,
262
260
  from:startOfDay.value,
@@ -275,6 +273,7 @@ async function fetchOccurrencesAndLives(): Promise<
275
273
  path: "live/list",
276
274
  parameters: params,
277
275
  });
276
+
278
277
  if (lives.length) {
279
278
  occurrences = occurrences.concat(lives);
280
279
  occurrences.sort((a, b) => {
@@ -286,6 +285,7 @@ async function fetchOccurrencesAndLives(): Promise<
286
285
  }
287
286
  return occurrences;
288
287
  }
288
+
289
289
  async function fetchOccurrences(): Promise<void> {
290
290
  if (planning.value[daySelected.value]) {
291
291
  return;
@@ -310,6 +310,7 @@ async function fetchOccurrences(): Promise<void> {
310
310
  ) {
311
311
  periodDayIndex += 1;
312
312
  }
313
+
313
314
  switch (periodOfDay.value[periodDayIndex].id) {
314
315
  case "morning":
315
316
  planning.value[daySelected.value].morning.push(occ);
@@ -325,8 +326,9 @@ async function fetchOccurrences(): Promise<void> {
325
326
  }
326
327
  planningLength.value[daySelected.value] += 1;
327
328
  }
328
- } catch {
329
+ } catch(e) {
329
330
  error.value = true;
331
+ console.error(e);
330
332
  }
331
333
  loading.value = false;
332
334
  }