AuthorSongsList.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. <template>
  2. <div>
  3. <v-expansion-panels v-model="modelValue" @update:modelValue="onModelValueChange">
  4. <v-expansion-panel
  5. v-for="author in authors"
  6. :key="author.id"
  7. >
  8. <v-expansion-panel-title>
  9. {{ author.name }}
  10. </v-expansion-panel-title>
  11. <v-expansion-panel-text>
  12. <v-card v-if="loadingSongs[author.id]" class="text-center pa-4">
  13. <v-progress-circular :indeterminate="true" />
  14. <div class="mt-2">Loading songs...</div>
  15. </v-card>
  16. <v-card v-else-if="songErrors[author.id]" class="text-center pa-4 error--text">
  17. <v-icon color="error" large>mdi-alert-circle</v-icon>
  18. <div class="mt-2">Error loading songs: {{ songErrors[author.id] }}</div>
  19. </v-card>
  20. <v-list v-else>
  21. <v-list-item v-for="song in authorSongs[author.id]" :key="song.id">
  22. <v-list-item-title>{{ song.title }}</v-list-item-title>
  23. </v-list-item>
  24. <v-list-item v-if="authorSongs[author.id] && authorSongs[author.id].length === 0">
  25. <v-list-item-title class="text-center">No songs found for this author</v-list-item-title>
  26. </v-list-item>
  27. </v-list>
  28. </v-expansion-panel-text>
  29. </v-expansion-panel>
  30. </v-expansion-panels>
  31. </div>
  32. </template>
  33. <script setup lang="ts">
  34. import { ref, onMounted, watch } from 'vue'
  35. // Define props
  36. const props = defineProps<{
  37. authors: Array<{
  38. id: number;
  39. name: string;
  40. }>;
  41. modelValue?: number | number[];
  42. }>();
  43. // Define emits
  44. const emit = defineEmits<{
  45. (e: 'update:modelValue', value: number | number[]): void;
  46. }>();
  47. // Local model value
  48. const modelValue = ref<number | number[]>(props.modelValue ?? -1);
  49. // Define types
  50. interface Song {
  51. id: number;
  52. title: string;
  53. author: {
  54. id: number;
  55. name: string;
  56. } | string; // Can be either an object or an IRI string
  57. }
  58. // State for songs
  59. const authorSongs = ref<Record<number, Song[]>>({});
  60. const loadingSongs = ref<Record<number, boolean>>({});
  61. const songErrors = ref<Record<number, string>>({});
  62. // Watch for expansion panel changes
  63. const fetchSongsForAuthor = async (authorId: number) => {
  64. // Skip if already loaded
  65. if (authorSongs.value[authorId]) {
  66. return;
  67. }
  68. loadingSongs.value[authorId] = true;
  69. try {
  70. const response = await fetch(`https://local.api.snc-demo.fr/api/songs?author=${authorId}`);
  71. if (!response.ok) {
  72. throw new Error(`HTTP error. Status: ${response.status}`);
  73. }
  74. const data = await response.json();
  75. authorSongs.value[authorId] = data['hydra:member'] || [];
  76. } catch (err) {
  77. songErrors.value[authorId] = err instanceof Error ? err.message : 'Unknown error';
  78. console.error(`Error fetching songs for author ${authorId}:`, err);
  79. } finally {
  80. loadingSongs.value[authorId] = false;
  81. }
  82. };
  83. // Method to be called when an expansion panel is opened
  84. const onPanelChange = (panel: number | number[]) => {
  85. if (Array.isArray(panel)) {
  86. // Multiple panels can be open in some configurations
  87. panel.forEach(index => {
  88. if (index >= 0 && index < props.authors.length) {
  89. fetchSongsForAuthor(props.authors[index].id);
  90. }
  91. });
  92. } else if (panel >= 0 && panel < props.authors.length) {
  93. fetchSongsForAuthor(props.authors[panel].id);
  94. }
  95. };
  96. // Handle model value change
  97. const onModelValueChange = (panel: number | number[]) => {
  98. // Update local model value
  99. modelValue.value = panel;
  100. // Emit the update event
  101. emit('update:modelValue', panel);
  102. // Fetch songs for the opened panel(s)
  103. onPanelChange(panel);
  104. };
  105. // Watch for prop changes
  106. watch(() => props.modelValue, (newValue) => {
  107. if (newValue !== undefined) {
  108. modelValue.value = newValue;
  109. }
  110. });
  111. // Expose method to parent component
  112. defineExpose({
  113. onPanelChange
  114. });
  115. </script>