@paris-ias/list 1.0.137 → 1.0.138

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 (46) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/actions/DenseItem.vue +64 -0
  3. package/dist/runtime/components/actions/ExpandedItem.vue +17 -14
  4. package/dist/runtime/components/actions/RowsItem.vue +56 -10
  5. package/dist/runtime/components/actions/View.vue +5 -17
  6. package/dist/runtime/components/affiliation/DenseItem.vue +26 -10
  7. package/dist/runtime/components/affiliation/RowsItem.vue +26 -10
  8. package/dist/runtime/components/apps/DenseItem.vue +26 -10
  9. package/dist/runtime/components/apps/RowsItem.vue +26 -10
  10. package/dist/runtime/components/disciplines/DenseItem.vue +26 -10
  11. package/dist/runtime/components/disciplines/RowsItem.vue +26 -10
  12. package/dist/runtime/components/events/DenseItem.vue +12 -3
  13. package/dist/runtime/components/events/RowsItem.vue +12 -5
  14. package/dist/runtime/components/fellowships/DenseItem.vue +14 -0
  15. package/dist/runtime/components/fellowships/RowsItem.vue +16 -2
  16. package/dist/runtime/components/fellowships/View.vue +2 -2
  17. package/dist/runtime/components/files/DenseItem.vue +25 -10
  18. package/dist/runtime/components/files/RowsItem.vue +26 -10
  19. package/dist/runtime/components/list/atoms/FiltersMenu.vue +9 -0
  20. package/dist/runtime/components/list/atoms/PerPage.vue +3 -2
  21. package/dist/runtime/components/list/atoms/ResetButton.vue +5 -1
  22. package/dist/runtime/components/list/atoms/SearchInput.vue +15 -2
  23. package/dist/runtime/components/list/atoms/SearchString.vue +169 -133
  24. package/dist/runtime/components/list/atoms/SortMenu.vue +22 -18
  25. package/dist/runtime/components/list/atoms/ViewMenu.vue +26 -14
  26. package/dist/runtime/components/list/molecules/Filters.vue +4 -4
  27. package/dist/runtime/components/list/molecules/Header.vue +10 -19
  28. package/dist/runtime/components/list/molecules/Pagination.vue +51 -48
  29. package/dist/runtime/components/list/organisms/List.vue +93 -73
  30. package/dist/runtime/components/mailing/RowsItem.vue +26 -10
  31. package/dist/runtime/components/news/DenseItem.vue +59 -45
  32. package/dist/runtime/components/news/RowsItem.vue +11 -7
  33. package/dist/runtime/components/people/DenseItem.vue +10 -8
  34. package/dist/runtime/components/people/RowsItem.vue +11 -2
  35. package/dist/runtime/components/projects/DenseItem.vue +11 -4
  36. package/dist/runtime/components/projects/RowsItem.vue +10 -3
  37. package/dist/runtime/components/publications/DenseItem.vue +12 -8
  38. package/dist/runtime/components/publications/RowsItem.vue +12 -4
  39. package/dist/runtime/components/tags/RowsItem.vue +23 -10
  40. package/dist/runtime/components/users/DenseItem.vue +24 -10
  41. package/dist/runtime/components/users/RowsItem.vue +24 -10
  42. package/dist/runtime/composables/useUtils.js +1 -1
  43. package/dist/runtime/plugins/pinia.js +5 -2
  44. package/dist/runtime/stores/root.d.ts +10 -9
  45. package/dist/runtime/stores/root.js +82 -117
  46. package/package.json +1 -1
@@ -1,23 +1,26 @@
1
1
  <template>
2
- <v-menu>
2
+ <v-menu :disabled="$stores[type].loading">
3
3
  <template #activator="{ props: menu }">
4
4
  <v-tooltip location="top">
5
5
  <template #activator="{ props: tooltip }">
6
- <v-btn
7
- x-large
8
- tile
9
- flat
10
- :icon="'mdi-' + current?.icon || defaultView?.icon"
11
- :class="{
12
- 'mt-3': isXsDisplay,
13
- }"
14
- v-bind="mergeProps(menu, tooltip)"
15
- />
6
+ <template v-if="$stores[type].loading">
7
+ <v-skeleton-loader type="button" :class="{ 'mt-3': isXsDisplay }" />
8
+ </template>
9
+ <template v-else>
10
+ <v-btn
11
+ x-large
12
+ tile
13
+ flat
14
+ :icon="'mdi-' + (current?.icon || defaultView?.icon)"
15
+ :class="{ 'mt-3': isXsDisplay }"
16
+ v-bind="mergeProps(menu, tooltip)"
17
+ />
18
+ </template>
16
19
  </template>
17
20
  <div
18
21
  v-html="
19
22
  $t('list.view-mode') +
20
- $t('list.' + current.name || defaultView.name)
23
+ $t('list.' + (current?.name || defaultView?.name))
21
24
  "
22
25
  />
23
26
  </v-tooltip>
@@ -26,6 +29,7 @@
26
29
  <v-list-item
27
30
  v-for="(value, key, index) in items"
28
31
  :key="index"
32
+ :disabled="$stores[type].loading"
29
33
  @click="updateView(value.name || key)"
30
34
  >
31
35
  <template #prepend>
@@ -43,7 +47,7 @@
43
47
  import { mergeProps } from "vue"
44
48
  import { useDisplay } from "vuetify"
45
49
  import { useRootStore } from "../../../stores/root"
46
- import { useNuxtApp, ref, useI18n } from "#imports"
50
+ import { useNuxtApp, ref, useI18n, computed } from "#imports"
47
51
  const { locale } = useI18n()
48
52
  const { $stores } = useNuxtApp()
49
53
 
@@ -61,8 +65,16 @@ const items = ref($stores[props.type].views)
61
65
 
62
66
  const current = ref($stores[props.type].view)
63
67
 
68
+ const defaultView = ref(
69
+ $stores[props.type].views[
70
+ Object.keys($stores[props.type].views).find(
71
+ (k) => $stores[props.type].views[k]?.default === true,
72
+ )
73
+ ] || { name: "list", icon: "view-list" },
74
+ )
75
+
64
76
  const updateView = async (value) => {
65
- await rootStore.updateView({ value, type: props.type, lang: locale.value })
77
+ rootStore.updateView({ value, type: props.type, lang: locale.value })
66
78
  }
67
79
  </script>
68
80
 
@@ -1,15 +1,16 @@
1
1
  <template>
2
2
  <v-row>
3
- <template v-for="filterItem in Object.keys($stores[type].filters)">
3
+ <template v-for="(filterItem, index) in Object.keys($stores[type].filters)">
4
4
  <v-col
5
5
  v-if="computeVisibility(filterItem)"
6
- :key="type + filterItem"
6
+ :key="type + index + filterItem"
7
7
  cols="12"
8
8
  sm="6"
9
9
  md="4"
10
10
  >
11
11
  <component
12
12
  :is="ComponentName(filterItem)"
13
+ :id="type + index + filterItem"
13
14
  tile
14
15
  :name="filterItem"
15
16
  hide-details
@@ -53,7 +54,6 @@ const getItems = (name) => {
53
54
  }
54
55
 
55
56
  if ($filters?.[props.type]?.[name]) {
56
- /* console.log("filters found for ", name, $filters[props.type][name]) */
57
57
  return $filters[props.type][name]
58
58
  .filter((key) => key !== "label")
59
59
  .map((item) => ({
@@ -68,7 +68,7 @@ const getItems = (name) => {
68
68
  if (
69
69
  messages.value[locale.value].list.filters[props.type][name] === undefined
70
70
  ) {
71
- console.log("name not found, no item for this filmter: ", name)
71
+ console.log("name not found, no item for this filter: ", name)
72
72
  return []
73
73
  }
74
74
  // TODO replace with package based values
@@ -34,25 +34,6 @@ import { ref, computed } from "vue"
34
34
  import { useNuxtApp } from "#imports"
35
35
  const { $stores } = useNuxtApp()
36
36
 
37
- const filtersOpen = ref(false)
38
- const visible = computed(() => {
39
- console.log(
40
- "$stores[props.type]?.filtersCount > 0: ",
41
- $stores[props.type]?.filtersCount > 0
42
- )
43
- console.log(
44
- "$stores[props.type]?.filtersCount: ",
45
- $stores[props.type]?.filtersCount
46
- )
47
- console.log(
48
- !!(
49
- $stores[props.type]?.filtersCount && $stores[props.type]?.filtersCount > 0
50
- )
51
- )
52
- return !!(
53
- $stores[props.type]?.filtersCount && $stores[props.type]?.filtersCount > 0
54
- )
55
- })
56
37
  const props = defineProps({
57
38
  type: {
58
39
  type: String,
@@ -60,4 +41,14 @@ const props = defineProps({
60
41
  default: "",
61
42
  },
62
43
  })
44
+ const visible = computed(() => {
45
+ console.log(
46
+ "SHOULD DISPLAY FILTERS:",
47
+ $stores[props.type]?.filtersCount && $stores[props.type]?.filtersCount > 0,
48
+ )
49
+ return !!(
50
+ $stores[props.type]?.filtersCount && $stores[props.type]?.filtersCount > 0
51
+ )
52
+ })
53
+ const filtersOpen = ref(unref(visible))
63
54
  </script>
@@ -9,9 +9,10 @@
9
9
  <v-btn
10
10
  v-if="!(hidePrevNext && isFirstPage)"
11
11
  :disabled="isFirstPage"
12
- min-width="35"
13
- height="35"
14
- width="35"
12
+ min-width="40"
13
+ height="40"
14
+ width="40"
15
+ class="prev-btn"
15
16
  :tabindex="isFirstPage && hidePrevNext ? -1 : 0"
16
17
  aria-label="Previous Page"
17
18
  @click="onChange(currentPage - 1)"
@@ -19,16 +20,16 @@
19
20
  >
20
21
  <v-icon>mdi-chevron-left</v-icon>
21
22
  </v-btn>
22
-
23
23
  <!-- Page buttons and gaps -->
24
24
  <template v-for="(page, index) in renderPages" :key="page.key">
25
25
  <!-- Ellipsis gap -->
26
26
  <v-btn
27
+ class="ellipsis-btn"
27
28
  v-if="page.isGap"
28
- icon
29
- min-width="35"
30
- height="35"
31
- width="35"
29
+ min-width="40"
30
+ tile
31
+ height="40"
32
+ width="40"
32
33
  @click="onChange(getGapPage(index))"
33
34
  @keyup.enter="onChange(getGapPage(index))"
34
35
  >
@@ -40,10 +41,10 @@
40
41
  v-else
41
42
  :class="['page-button', { 'active-page': page.current }]"
42
43
  tabindex="0"
43
- min-width="35"
44
- height="35"
44
+ min-width="40"
45
+ height="40"
45
46
  tile
46
- width="35"
47
+ width="40"
47
48
  :aria-current="page.current ? 'page' : undefined"
48
49
  :aria-label="
49
50
  page.current
@@ -63,9 +64,11 @@
63
64
  :disabled="isLastPage"
64
65
  :tabindex="isLastPage && hidePrevNext ? -1 : 0"
65
66
  aria-label="Next Page"
66
- min-width="35"
67
- height="35"
68
- width="35"
67
+ min-width="40"
68
+ tile
69
+ class="next-btn"
70
+ height="40"
71
+ width="40"
69
72
  @click="onChange(currentPage + 1)"
70
73
  @keyup.enter="onChange(currentPage + 1)"
71
74
  >
@@ -75,7 +78,7 @@
75
78
  </template>
76
79
 
77
80
  <script setup>
78
- import { computed } from "vue";
81
+ import { computed } from "vue"
79
82
 
80
83
  const props = defineProps({
81
84
  currentPage: { type: Number, required: true },
@@ -83,15 +86,15 @@ const props = defineProps({
83
86
  pagePadding: { type: Number, default: 1, validator: (v) => v > 0 },
84
87
  pageGap: { type: Number, default: 2, validator: (v) => v > 0 },
85
88
  hidePrevNext: { type: Boolean, default: false },
86
- });
89
+ })
87
90
 
88
- const emit = defineEmits(["update"]);
91
+ const emit = defineEmits(["update"])
89
92
 
90
93
  // Computed state for prev/next disabled
91
- const isFirstPage = computed(() => props.currentPage === 1);
94
+ const isFirstPage = computed(() => props.currentPage === 1)
92
95
  const isLastPage = computed(
93
- () => props.currentPage === props.totalPages || props.totalPages === 0
94
- );
96
+ () => props.currentPage === props.totalPages || props.totalPages === 0,
97
+ )
95
98
 
96
99
  // Generate pages and gap positions
97
100
  const renderPages = computed(() => {
@@ -99,41 +102,41 @@ const renderPages = computed(() => {
99
102
  key: `page-${pageIndex}`,
100
103
  value: pageIndex,
101
104
  current: pageIndex === props.currentPage,
102
- });
103
- const createGap = (pageIndex) => ({ key: `gap-${pageIndex}`, isGap: true });
105
+ })
106
+ const createGap = (pageIndex) => ({ key: `gap-${pageIndex}`, isGap: true })
104
107
 
105
- const pages = [];
108
+ const pages = []
106
109
  for (let i = 1; i <= props.totalPages; i++) {
107
110
  if (
108
111
  i === props.currentPage ||
109
112
  i < props.pageGap ||
110
113
  i > props.totalPages - props.pageGap + 1
111
114
  ) {
112
- pages.push(createPage(i));
113
- continue;
115
+ pages.push(createPage(i))
116
+ continue
114
117
  }
115
118
 
116
- let min, max;
119
+ let min, max
117
120
  if (props.currentPage <= props.pageGap + props.pagePadding) {
118
- min = props.pageGap + 1;
119
- max = min + props.pagePadding * 2;
121
+ min = props.pageGap + 1
122
+ max = min + props.pagePadding * 2
120
123
  } else if (
121
124
  props.currentPage >=
122
125
  props.totalPages - props.pageGap - props.pagePadding
123
126
  ) {
124
- max = props.totalPages - props.pageGap;
125
- min = max - props.pagePadding * 2;
127
+ max = props.totalPages - props.pageGap
128
+ min = max - props.pagePadding * 2
126
129
  } else {
127
- min = props.currentPage - props.pagePadding;
128
- max = props.currentPage + props.pagePadding;
130
+ min = props.currentPage - props.pagePadding
131
+ max = props.currentPage + props.pagePadding
129
132
  }
130
133
 
131
134
  if (
132
135
  (i >= min && i <= props.currentPage) ||
133
136
  (i <= max && i >= props.currentPage)
134
137
  ) {
135
- pages.push(createPage(i));
136
- continue;
138
+ pages.push(createPage(i))
139
+ continue
137
140
  }
138
141
 
139
142
  if (i === props.pageGap) {
@@ -141,11 +144,11 @@ const renderPages = computed(() => {
141
144
  min > props.pageGap + 1 &&
142
145
  props.currentPage > props.pageGap + props.pagePadding + 1
143
146
  ) {
144
- pages.push(createGap(i));
147
+ pages.push(createGap(i))
145
148
  } else {
146
- pages.push(createPage(i));
149
+ pages.push(createPage(i))
147
150
  }
148
- continue;
151
+ continue
149
152
  }
150
153
 
151
154
  if (i === props.totalPages - props.pageGap + 1) {
@@ -153,28 +156,28 @@ const renderPages = computed(() => {
153
156
  max < props.totalPages - props.pageGap &&
154
157
  props.currentPage < props.totalPages - props.pageGap - props.pagePadding
155
158
  ) {
156
- pages.push(createGap(i));
159
+ pages.push(createGap(i))
157
160
  } else {
158
- pages.push(createPage(i));
161
+ pages.push(createPage(i))
159
162
  }
160
- continue;
163
+ continue
161
164
  }
162
165
  }
163
- return pages;
164
- });
166
+ return pages
167
+ })
165
168
 
166
169
  // Calculate page to jump when clicking gap
167
170
  const getGapPage = (index) => {
168
- const before = renderPages.value[index - 1];
169
- const after = renderPages.value[index + 1] || { value: props.totalPages };
170
- return Math.floor((before.value + after.value) / 2);
171
- };
171
+ const before = renderPages.value[index - 1]
172
+ const after = renderPages.value[index + 1] || { value: props.totalPages }
173
+ return Math.floor((before.value + after.value) / 2)
174
+ }
172
175
 
173
176
  function onChange(page) {
174
- emit("update", page);
177
+ emit("update", page)
175
178
  }
176
179
  </script>
177
180
 
178
181
  <style scoped>
179
- .page-button{background-color:transparent;border:1px solid rgba(0,0,0,.2);color:#000;transition:background-color .3s ease,color .3s ease,transform .2s ease}.page-button:hover{background-color:#f0f0f0}.page-button.active-page{background-color:#000!important;color:#fff!important;transform:scale(1.05)}.page-button:focus{box-shadow:0 0 0 2px rgba(0,0,0,.3);outline:none}
182
+ .ellipsis-btn,.next-btn,.page-button,.prev-btn{background-color:transparent;border:1px solid rgba(0,0,0,.2);font-size:16px;transition:background-color .3s ease,color .3s ease,transform .2s ease}.page-button:hover{background-color:#f0f0f0}.page-button.active-page{background-color:#000!important;color:#fff!important;transform:scale(1.05)}.page-button:focus{box-shadow:0 0 0 2px rgba(0,0,0,.3);outline:none}
180
183
  </style>
@@ -1,34 +1,30 @@
1
1
  <template>
2
2
  <ListMoleculesHeader :type="type" />
3
- <component :is="view">
3
+ <component
4
+ :is="view"
5
+ :loading="$stores[type] && $stores[type].loading && pending"
6
+ >
4
7
  <component
5
8
  :is="itemTemplate"
6
9
  v-for="(item, index) in items"
7
- :key="index"
8
- :item="item"
9
- :index="index"
10
+ :key="(item.name || item.lastname) + type + index"
11
+ :item
12
+ :index
13
+ :loading="$stores[type] && $stores[type].loading && pending"
10
14
  :pathPrefix="
11
- localePath({
12
- name: pathPrefix,
13
- params: { slug: item.slug },
14
- })
15
+ localePath({ name: pathPrefix, params: { slug: item.slug } })
15
16
  "
16
17
  />
17
18
  </component>
18
19
  <div class="text-center">
19
- <ListAtomsPerPage
20
- v-if="numberOfPages > 1"
21
- :type="type"
22
- class="float-right"
23
- />
20
+ <ListAtomsPerPage :type="type" class="float-right" />
24
21
 
25
22
  <ListMoleculesPagination
26
- v-if="numberOfPages > 1"
27
- :type="type"
28
- color="black"
23
+ v-if="$stores[type].numberOfPages > 1"
24
+ :type
29
25
  large
30
- :current-page="page"
31
- :total-pages="numberOfPages"
26
+ :current-page="$stores[type].page"
27
+ :total-pages="$stores[type].numberOfPages"
32
28
  :page-padding="1"
33
29
  :page-gap="2"
34
30
  :hide-prev-next="false"
@@ -38,21 +34,20 @@
38
34
  </template>
39
35
 
40
36
  <script setup>
41
- import { nextTick, watch } from "vue"
37
+ import { computed, onUpdated, onMounted, watch } from "vue"
42
38
  import { useRootStore } from "../../../stores/root"
43
39
  import { capitalize } from "../../../composables/useUtils"
44
40
  import {
45
41
  useNuxtApp,
46
42
  resolveComponent,
47
- computed,
48
43
  onBeforeUnmount,
49
- onMounted,
50
44
  useI18n,
51
45
  useRoute,
52
- navigateTo,
53
46
  useLocalePath,
47
+ useAsyncQuery,
54
48
  } from "#imports"
55
- const { $stores } = useNuxtApp()
49
+
50
+ const { $stores, $queries } = useNuxtApp()
56
51
  const { locale } = useI18n()
57
52
  const route = useRoute()
58
53
  const rootStore = useRootStore()
@@ -69,7 +64,7 @@ const props = defineProps({
69
64
  default: "people",
70
65
  required: true,
71
66
  },
72
- layout: {
67
+ /* layout: {
73
68
  type: Object,
74
69
  required: false,
75
70
  default: () => {
@@ -78,94 +73,119 @@ const props = defineProps({
78
73
  xl: 12,
79
74
  }
80
75
  },
81
- },
76
+ }, */
82
77
  pathPrefix: {
83
78
  type: String,
84
79
  required: true,
85
80
  },
86
- pagination: {
87
- type: Object,
88
- required: false,
89
- default: () => {
90
- return {}
91
- },
92
- },
81
+
93
82
  addButton: {
94
83
  type: Boolean,
95
84
  required: false,
96
85
  default: false,
97
86
  },
98
- items: [Object],
99
87
  })
100
88
 
89
+ // Initialize loading state
90
+ console.log("start llocal loading from setup")
91
+ rootStore.setLoading(true, props.type)
92
+
93
+ // Initial route -> store (single source-of-truth on first load)
94
+ rootStore.loadRouteQuery(props.type)
95
+
96
+ // Computed properties for dynamic components
101
97
  const view = computed(() =>
102
98
  props.customView
103
99
  ? resolveComponent("ListViews" + capitalize(props.customView))
104
- : resolveComponent("ListViews" + capitalize($stores[props.type].view.name)),
100
+ : resolveComponent(
101
+ "ListViews" + capitalize($stores[props.type]?.view?.name || "list"),
102
+ ),
105
103
  )
106
104
  const itemTemplate = computed(() =>
107
105
  resolveComponent(
108
106
  (
109
107
  capitalize(props.type) +
110
- capitalize($stores[props.type].view.name) +
108
+ capitalize($stores[props.type]?.view?.name || "list") +
111
109
  "Item"
112
110
  ).toString(),
113
111
  ),
114
112
  )
115
- const numberOfPages = computed(() => $stores[props.type].numberOfPages)
116
113
 
117
- const page = computed(() => {
118
- const p = parseInt(route.query.page, 10)
119
- return !isNaN(p) && p > 0 ? p : 1
114
+ // Apollo: reactive query using variables computed from store
115
+ const variables = computed(() => {
116
+ console.log("computed variables loop")
117
+ return rootStore.buildListVariables(props.type, locale.value)
120
118
  })
119
+ console.log("Starting query for type: ", props.type)
120
+ console.log("Using variables: ", variables.value)
121
121
 
122
- const items = computed(() => $stores[props.type].items)
123
- console.log("setup list")
124
-
125
- // On mounted: load filters and data
126
- onMounted(async () => {
127
- // Load any route filters
128
- rootStore.loadRouteQuery(props.type)
129
- // Initialize store page from URL
130
- const pageParam = parseInt(route.query.page, 10)
131
- if (!isNaN(pageParam) && pageParam > 1) {
132
- await rootStore.updatePage({
133
- page: pageParam,
134
- type: props.type,
135
- lang: locale.value,
136
- })
137
- }
138
- // Fetch initial items
139
- try {
140
- await rootStore.update(props.type, locale.value)
141
- } catch (e) {
142
- console.error("Error fetching list:", e)
143
- }
144
- })
122
+ // Apollo GraphQL query with proper reactivity
123
+ const { data, pending, error, refresh } = await useAsyncQuery(
124
+ $queries[props.type]?.list,
125
+ variables, // Pass the reactive computed, not its value
126
+ {
127
+ key: `list-${props.type}`, // Unique key for caching
128
+ server: true, // Enable SSR
129
+ },
130
+ )
131
+ if (error.value) {
132
+ console.error("GraphQL query error: ", error.value)
133
+ } else {
134
+ console.log("Query result data: ", data.value.items?.length)
135
+ }
145
136
 
137
+ // Apply data to store immediately if available
138
+ if (data.value) {
139
+ console.log("Applying data to store directly [first load scenario]")
140
+ rootStore.applyListResult(props.type, data.value)
141
+ }
142
+
143
+ // Watch for variable changes to refresh and apply new data
146
144
  watch(
147
- () => page.value,
148
- async (newPage) => {
149
- const query = {
150
- ...route.query,
151
- page: newPage > 1 ? String(newPage) : undefined,
145
+ variables,
146
+ async (newVars, oldVars) => {
147
+ if (newVars && JSON.stringify(newVars) !== JSON.stringify(oldVars)) {
148
+ console.log("Variables changed, refreshing query, newVars: ", newVars)
149
+ console.log("start local loading from computed")
150
+ rootStore.setLoading(true, props.type)
151
+ await refresh()
152
+ if (data.value) {
153
+ console.log("Applying refreshed data to store")
154
+ rootStore.applyListResult(props.type, data.value)
155
+ }
156
+ rootStore.setLoading(false, props.type)
152
157
  }
153
- navigateTo({ query }, { replace: true })
154
- await nextTick()
155
- window.scrollTo({ top: 0, behavior: "smooth" })
156
158
  },
159
+ { deep: true },
157
160
  )
161
+
162
+ // Reactive items computed from the store (single source of truth)
163
+ const items = computed(() => $stores[props.type]?.items || [])
164
+
165
+ onMounted(() => {
166
+ // On initial mount: clear loading state
167
+ console.log("STOP local loading from mounted")
168
+ rootStore.setLoading(false, props.type)
169
+ })
170
+
158
171
  onBeforeUnmount(() => {
159
172
  rootStore.resetState(props.type, locale.value)
160
173
  })
161
174
 
162
175
  async function onPageChange(newPage) {
163
- await rootStore.updatePage({
176
+ console.log("onPageChange: ", newPage)
177
+ rootStore.updatePage({
164
178
  page: newPage,
165
179
  type: props.type,
166
180
  lang: locale.value,
167
181
  })
182
+ if (typeof window !== "undefined") {
183
+ window.scrollTo({ top: 0, behavior: "smooth" })
184
+ }
168
185
  }
169
-
170
- console.log("pathPrefix", itemTemplate.value)
186
+ /*
187
+ onUpdated(() => {
188
+ console.log("STOP local loading from updated")
189
+ rootStore.setLoading(false, props.type)
190
+ }) */
171
191
  </script>
@@ -1,18 +1,34 @@
1
1
  <template>
2
- {{ item }}
2
+ <v-row
3
+ class="highlight-on-hover pa-3"
4
+ no-gutters
5
+ @click="$router.push(pathPrefix)"
6
+ >
7
+ <v-col cols="12" class="px-6">
8
+ <v-skeleton-loader v-if="isLoading" type="heading, text@8, button" />
9
+ <template v-else>
10
+ <div class="text-h5">{{ item.name }}</div>
11
+ <div v-if="item.summary" class="mt-2">
12
+ <MDC :value="item.summary" />
13
+ </div>
14
+ </template>
15
+ </v-col>
16
+ </v-row>
17
+ <v-divider />
3
18
  </template>
4
19
 
5
20
  <script setup>
6
- defineProps({
7
- item: {
8
- type: Object,
9
- required: true,
10
- },
11
- pathPrefix: {
12
- type: String,
13
- required: true,
14
- },
21
+ import { computed } from "#imports"
22
+ import { useRootStore } from "../../stores/root"
23
+
24
+ const rootStore = useRootStore()
25
+ const props = defineProps({
26
+ item: { type: Object, required: true },
27
+ pathPrefix: { type: String, required: true },
28
+ loading: { type: Boolean, default: false },
15
29
  })
30
+
31
+ const isLoading = computed(() => rootStore.loading || props.loading)
16
32
  </script>
17
33
 
18
34
  <style></style>