@paris-ias/list 1.1.18 → 1.1.20

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.
package/README.md CHANGED
@@ -17,12 +17,13 @@ Find and replace all on all files (CMD+SHIFT+F):
17
17
  My new Nuxt module for doing amazing things.
18
18
 
19
19
  - [✨  Release Notes](/CHANGELOG.md)
20
- <!-- - [🏀 Online playground](https://stackblitz.com/github/your-org/my-module?file=playground%2Fapp.vue) -->
21
- <!-- - [📖 &nbsp;Documentation](https://example.com) -->
20
+ <!-- - [🏀 Online playground](https://stackblitz.com/github/your-org/my-module?file=playground%2Fapp.vue) -->
21
+ <!-- - [📖 &nbsp;Documentation](https://example.com) -->
22
22
 
23
23
  ## Features
24
24
 
25
25
  <!-- Highlight some of the features your module provide here -->
26
+
26
27
  - ⛰ &nbsp;Foo
27
28
  - 🚠 &nbsp;Bar
28
29
  - 🌲 &nbsp;Baz
@@ -37,7 +38,6 @@ npx nuxi module add my-module
37
38
 
38
39
  That's it! You can now use My Module in your Nuxt app ✨
39
40
 
40
-
41
41
  ## Contribution
42
42
 
43
43
  <details>
@@ -69,16 +69,50 @@ That's it! You can now use My Module in your Nuxt app ✨
69
69
 
70
70
  </details>
71
71
 
72
-
73
72
  <!-- Badges -->
73
+
74
74
  [npm-version-src]: https://img.shields.io/npm/v/my-module/latest.svg?style=flat&colorA=020420&colorB=00DC82
75
75
  [npm-version-href]: https://npmjs.com/package/my-module
76
-
77
76
  [npm-downloads-src]: https://img.shields.io/npm/dm/my-module.svg?style=flat&colorA=020420&colorB=00DC82
78
77
  [npm-downloads-href]: https://npm.chart.dev/my-module
79
-
80
78
  [license-src]: https://img.shields.io/npm/l/my-module.svg?style=flat&colorA=020420&colorB=00DC82
81
79
  [license-href]: https://npmjs.com/package/my-module
82
-
83
80
  [nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
84
81
  [nuxt-href]: https://nuxt.com
82
+
83
+ | archeology | ARCHAEOLOGY |
84
+ | ------------------------------------------- | -------------------------------------- |
85
+ | architecture-and-spatial-planning | ARCHITECTURE_AND_URBAN_PLANNING |
86
+ | art-and-art-history | ART_AND_HISTORY_OF_ART |
87
+ | | BIOLOGY |
88
+ | | CHEMISTRY |
89
+ | classical-studies | CLASSICAL_STUDIES |
90
+ | | COMPUTER_SCIENCE |
91
+ | | DEMOGRAPHY |
92
+ | digital-humanities | DIGITAL_HUMANITIES |
93
+ | economics-and-finance | ECONOMICS |
94
+ | education | EDUCATION_SCIENCES |
95
+ | environment | ENVIRONMENTAL_SCIENCES |
96
+ | gender-studies | SOCIOLOGY |
97
+ | geography | GEOGRAPHY |
98
+ | history-philosophy-and-sociology-of-science | STUDIES_IN_SCIENCE_AND_TECHNOLOGY |
99
+ | history | HISTORY |
100
+ | information-and-communication-studies | INFORMATION_AND_COMMUNICATION_SCIENCES |
101
+ | | INTERNATIONAL_RELATIONS |
102
+ | law | LAW |
103
+ | linguistics | LINGUISTICS |
104
+ | literature | LITERATURE |
105
+ | | MANAGEMENT_AND_PUBLIC_ADMINISTRATION |
106
+ | medicine-pharmacy | MEDICINE |
107
+ | music-musicology-and-performance-arts | ART_AND_HISTORY_OF_ART |
108
+ | neuroscience | NEUROSCIENCES_AND_COGNITIVE_SCIENCES |
109
+ | others | OTHERS |
110
+ | philosophy | PHILOSOPHY |
111
+ | physics-and-mathematics | PHYSICS_MATHEMATICS_AND_ENGINEERING |
112
+ | political-science | POLITICAL_SCIENCE |
113
+ | psychology | PSYCHOLOGY |
114
+ | | STUDIES_IN_SCIENCE_AND_TECHNOLOGY |
115
+ | | THEOLOGY |
116
+ | sciences-of-the-universe | PHYSICS_MATHEMATICS_AND_ENGINEERING |
117
+ | social-anthropology-and-ethnology | ANTHROPOLOGY_AND_ETHNOLOGY |
118
+ | sociology | SOCIOLOGY |
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@paris-ias/list",
3
3
  "configKey": "list",
4
- "version": "1.1.18",
4
+ "version": "1.1.20",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.4",
7
7
  "unbuild": "2.0.0"
@@ -64,8 +64,9 @@
64
64
  v-else
65
65
  :items="[
66
66
  $t('list.filters.fellowships.fellowshipType.' + item.fellowshipType),
67
- ...(props.item?.disciplines?.map((discipline) => discipline.name) ??
68
- []),
67
+ ...(props.item?.disciplines.map((discipline) =>
68
+ $t('list.filters.disciplines.' + discipline),
69
+ ) ?? []),
69
70
  ]"
70
71
  class="mt-2"
71
72
  />
@@ -14,9 +14,13 @@
14
14
  width="60"
15
15
  height="60"
16
16
  />
17
- <v-btn v-else icon variant="text">
18
- <v-icon>mdi-{{ $stores[type].sort[currentSort]?.icon }}</v-icon>
19
- </v-btn>
17
+ <v-btn
18
+ v-else
19
+ x-large
20
+ tile
21
+ flat
22
+ :icon="'mdi-' + $stores[type].sort[currentSort]?.icon"
23
+ />
20
24
  </span>
21
25
  </template>
22
26
 
@@ -29,7 +33,7 @@
29
33
  </v-tooltip>
30
34
  </template>
31
35
 
32
- <<v-list density="compact">
36
+ <v-list density="compact">
33
37
  <v-list-item
34
38
  v-for="(item, key) in $stores[type].sort"
35
39
  :key="key"
@@ -127,9 +127,13 @@ const computeVisibility = (filterKey) => {
127
127
  const filters = $stores[props.type].filters
128
128
  const show = filters?.[filterKey]?.show
129
129
 
130
+ // No show config -> always visible.
130
131
  if (!show) return true
131
- if (show.default) return true
132
- if (!Array.isArray(show.switchIf)) return false
132
+
133
+ const def = show.default === true
134
+
135
+ // Without switch rules, visibility is simply the default.
136
+ if (!Array.isArray(show.switchIf) || show.switchIf.length === 0) return def
133
137
 
134
138
  const checkRule = (rule) =>
135
139
  Object.entries(rule).every(([depKey, expected]) => {
@@ -146,9 +150,14 @@ const computeVisibility = (filterKey) => {
146
150
  return Array.isArray(expected) ? expected.includes(cur) : cur === expected
147
151
  })
148
152
 
149
- return show.disjonctive !== false
150
- ? show.switchIf.some(checkRule)
151
- : show.switchIf.every(checkRule)
153
+ // disjonctive (default true) -> any rule matches; otherwise all rules match.
154
+ const matched =
155
+ show.disjonctive !== false
156
+ ? show.switchIf.some(checkRule)
157
+ : show.switchIf.every(checkRule)
158
+
159
+ // A matched switch flips the default; otherwise keep the default.
160
+ return matched ? !def : def
152
161
  }
153
162
  </script>
154
163
 
@@ -122,7 +122,10 @@
122
122
  ]
123
123
  "
124
124
  />
125
- <div v-if="item && item.description" class="mt-md-n2 mx-4 mx-sm-8 mx-md-0">
125
+ <div
126
+ v-if="item && item.description"
127
+ class="mt-md-n2 mx-4 mx-sm-8 mx-md-0"
128
+ >
126
129
  <MDC :value="item.description" />
127
130
  </div>
128
131
  </v-col>
@@ -181,5 +184,5 @@ const today = new Date()
181
184
  )
182
185
 
183
186
  ) */
184
- $stores.news.loading = false
187
+ $stores.publications.loading = false
185
188
  </script>
@@ -7,16 +7,20 @@
7
7
  <template v-else-if="item.groups">
8
8
  <template v-for="(value, key, index) in item.groups" :key="key + index">
9
9
  <template v-if="value && key === 'vintage'">
10
+ <!-- One pill per calendar year a vintage spans: an entry running
11
+ from start to stop across a year boundary yields a pill for
12
+ each year (e.g. 2025, 2026). Entries without dates fall back
13
+ to their single `year`. -->
10
14
  <v-chip
11
- v-for="(vintage, index2) in item.groups.vintage"
12
- :key="index2"
15
+ v-for="year in vintageYears"
16
+ :key="year"
13
17
  size="small"
14
18
  class="mt-3 mr-1 mr-md-3"
15
19
  variant="outlined"
16
20
  tile
17
21
  style="background-color: white; color: black"
18
22
  >
19
- {{ $t("vintage", [vintage.year]) }}
23
+ {{ $t("vintage", [year]) }}
20
24
  </v-chip>
21
25
  </template>
22
26
 
@@ -59,7 +63,7 @@
59
63
 
60
64
  <script setup>
61
65
  import { useRootStore } from "../../stores/root"
62
- import { useNuxtApp } from "#imports"
66
+ import { computed, useNuxtApp } from "#imports"
63
67
  import { useDisplay } from "vuetify"
64
68
  const { mdAndUp } = useDisplay()
65
69
  const rootStore = useRootStore()
@@ -71,6 +75,45 @@ const props = defineProps({
71
75
  default: () => ({}),
72
76
  },
73
77
  })
78
+
79
+ // Expand a start→stop span into the list of calendar years it covers.
80
+ // e.g. 2024-09-01 → 2025-07-30 yields [2024, 2025]. Returns [] if either
81
+ // bound is missing or unparseable.
82
+ const spanYears = (start, stop) => {
83
+ if (!start || !stop) return []
84
+ const from = new Date(start).getFullYear()
85
+ const to = new Date(stop).getFullYear()
86
+ if (Number.isNaN(from) || Number.isNaN(to) || to < from) return []
87
+ const years = []
88
+ for (let y = from; y <= to; y++) years.push(y)
89
+ return years
90
+ }
91
+
92
+ // Years to render as vintage pills, deduped and sorted ascending.
93
+ //
94
+ // `groups.vintage[]` is the authoritative list of fellowships (a fellow may
95
+ // have several) but is queried without start/stop, so it only contributes its
96
+ // single `year`. `latest` carries the actual start/stop, so its span is
97
+ // expanded into one year per calendar year — this is what turns a cross-year
98
+ // fellowship into two pills (e.g. 2025, 2026).
99
+ const vintageYears = computed(() => {
100
+ const years = new Set()
101
+
102
+ const latest = props.item?.latest
103
+ // latest is the Vintage|Position union; only a Vintage carries a fellowship
104
+ // span. Position (staff) has start/stop too but no `year`, so guard on year.
105
+ if (latest?.year != null) {
106
+ const span = spanYears(latest.start, latest.stop)
107
+ if (span.length) span.forEach((y) => years.add(y))
108
+ else years.add(latest.year)
109
+ }
110
+
111
+ for (const v of props.item?.groups?.vintage ?? []) {
112
+ if (v?.year != null) years.add(v.year)
113
+ }
114
+
115
+ return [...years].sort((a, b) => a - b)
116
+ })
74
117
  </script>
75
118
 
76
119
  <style lang="scss" scoped></style>
@@ -17,53 +17,92 @@
17
17
  />
18
18
  </v-col>
19
19
  <v-col align-self="start" class="dense ml-md-4">
20
+ <!-- NAME ROW -->
20
21
  <v-skeleton-loader v-if="loading" type="heading" />
21
22
  <div
22
23
  v-else
23
24
  class="d-flex justify-space-between text-title text-h5 align-center pt-md-2"
24
25
  >
25
- <span
26
- v-html="
27
- searchQuery.length
28
- ? highlightAndTruncate(
29
- 300,
30
- item.firstname + ' ' + item.lastname,
31
- searchQuery.split(' '),
32
- )
33
- : item.firstname + ' ' + item.lastname
34
- "
35
- />
26
+ <div class="d-flex align-center flex-wrap" style="gap: 8px">
27
+ <span
28
+ v-html="
29
+ searchQuery.length
30
+ ? highlightAndTruncate(
31
+ 300,
32
+ item.firstname + ' ' + item.lastname,
33
+ searchQuery.split(' '),
34
+ )
35
+ : item.firstname + ' ' + item.lastname
36
+ "
37
+ />
38
+ <span v-if="isPresent" class="present-pill">
39
+ <span class="present-dot" />
40
+ {{ $t("present") }}
41
+ </span>
42
+ </div>
43
+
36
44
  <v-spacer />
37
- <PeopleBadges :item="item" />
45
+
46
+ <!-- year badges: one pill per calendar year the fellowship spans, so a
47
+ cross-year vintage (start→stop over a boundary) shows e.g. 2025 and
48
+ 2026. Falls back to the single `year` when dates are absent. -->
49
+ <div class="d-flex flex-wrap justify-end" style="gap: 4px">
50
+ <v-chip
51
+ v-for="year in vintageYears"
52
+ :key="year"
53
+ size="small"
54
+ variant="outlined"
55
+ tile
56
+ style="background-color: white; color: black"
57
+ >
58
+ {{ $t("vintage", [year]) }}
59
+ </v-chip>
60
+ </div>
38
61
  </div>
39
- <div
40
- v-if="item.group && item.groups.vintage && item.groups.vintage[0].theme"
41
- class="text-body-1 font-weight-light paragraph"
42
- v-html="
43
- searchQuery.length
44
- ? highlightAndTruncate(
45
- 300,
46
- item.groups.vintage[0].theme,
47
- searchQuery.split(' '),
48
- )
49
- : item.groups.vintage[0].theme
50
- "
62
+
63
+ <!-- DISCIPLINES (translated, capped at 3 + overflow) -->
64
+ <v-skeleton-loader
65
+ v-if="loading"
66
+ type="chip@3"
67
+ class="discipline-skeleton mt-2"
51
68
  />
52
- <v-skeleton-loader v-if="loading" type="paragraph" />
69
+ <div
70
+ v-else-if="disciplines.length"
71
+ class="d-flex flex-wrap mt-2"
72
+ style="gap: 6px"
73
+ >
74
+ <v-chip
75
+ v-for="(d, i) in shownDisciplines"
76
+ :key="d + i"
77
+ size="small"
78
+ tile
79
+ variant="flat"
80
+ color="grey-lighten-3"
81
+ class="discipline-chip"
82
+ >
83
+ {{ disciplineLabel(d) }}
84
+ </v-chip>
85
+ <v-chip
86
+ v-if="extraDisciplines"
87
+ size="small"
88
+ variant="flat"
89
+ color="grey-lighten-3"
90
+ class="discipline-chip"
91
+ >
92
+ +{{ extraDisciplines }}
93
+ </v-chip>
94
+ </div>
53
95
 
96
+ <!-- THEME (Vintage only; non-fellows show nothing) -->
97
+ <v-skeleton-loader v-if="loading" type="text" />
54
98
  <div
55
- v-else-if="item.summary && item.summary.length && mdAndUp"
56
- class="text-body-1 font-weight-light paragraph"
57
- :style="
58
- '-webkit-line-clamp:' +
59
- [1, 1, 1, 2, 3, 3][
60
- ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].indexOf(displayName || 'md')
61
- ]
62
- "
99
+ v-else-if="latestTheme"
100
+ class="theme-label mt-2"
101
+ :style="'-webkit-line-clamp:' + themeLineClamp"
63
102
  v-html="
64
103
  searchQuery.length
65
- ? highlightAndTruncate(100, item.summary, searchQuery.split(' '))
66
- : item.summary
104
+ ? highlightAndTruncate(300, latestTheme, searchQuery.split(' '))
105
+ : latestTheme
67
106
  "
68
107
  />
69
108
  </v-col>
@@ -73,16 +112,19 @@
73
112
  <script setup>
74
113
  import { useRootStore } from "../../stores/root"
75
114
  import { highlightAndTruncate } from "../../composables/useUtils"
76
- import { computed, useRoute, useNuxtApp } from "#imports"
115
+ import { computed, useRoute, useNuxtApp, useI18n } from "#imports"
77
116
  import { useDisplay } from "vuetify"
78
- const { name } = useRoute()
79
117
 
118
+ const { name } = useRoute()
80
119
  const { mdAndUp, name: displayName } = useDisplay()
120
+ const { t } = useI18n()
81
121
  const rootStore = useRootStore()
82
122
  const { $stores } = useNuxtApp()
123
+
83
124
  const searchQuery = computed(() =>
84
125
  name.startsWith("search") ? rootStore.search : $stores["people"].search || "",
85
126
  )
127
+
86
128
  const props = defineProps({
87
129
  item: {
88
130
  type: Object,
@@ -92,14 +134,83 @@ const props = defineProps({
92
134
  type: Number,
93
135
  required: true,
94
136
  },
95
-
96
137
  loading: {
97
138
  type: Boolean,
98
139
  required: false,
99
140
  default: false,
100
141
  },
101
142
  })
143
+
144
+ // latest union (field-presence based; __typename not selected in the query)
145
+ const latest = computed(() => props.item?.latest ?? null)
146
+ const latestTheme = computed(() => latest.value?.theme ?? null) // Vintage only
147
+
148
+ // Expand a start→stop span into the calendar years it covers, e.g.
149
+ // 2024-09-01 → 2025-07-30 yields [2024, 2025]. [] if a bound is missing/invalid.
150
+ const spanYears = (start, stop) => {
151
+ if (!start || !stop) return []
152
+ const from = new Date(start).getFullYear()
153
+ const to = new Date(stop).getFullYear()
154
+ if (Number.isNaN(from) || Number.isNaN(to) || to < from) return []
155
+ const years = []
156
+ for (let y = from; y <= to; y++) years.push(y)
157
+ return years
158
+ }
159
+
160
+ // Vintage year pills (deduped, ascending). Mirrors PeopleBadges: `latest`
161
+ // carries start/stop so its span expands to one year per calendar year (a
162
+ // cross-year fellowship → two pills); `groups.vintage[]` is queried without
163
+ // dates so each only contributes its single `year`. Falls back to `year` when
164
+ // no span is available.
165
+ const vintageYears = computed(() => {
166
+ const years = new Set()
167
+
168
+ const l = latest.value
169
+ if (l?.year != null) {
170
+ const span = spanYears(l.start, l.stop)
171
+ if (span.length) span.forEach((y) => years.add(y))
172
+ else years.add(l.year)
173
+ }
174
+
175
+ for (const v of props.item?.groups?.vintage ?? []) {
176
+ if (v?.year != null) years.add(v.year)
177
+ }
178
+
179
+ return [...years].sort((a, b) => a - b)
180
+ })
181
+
182
+ // "present" = currently in residence/post: started on or before today and not
183
+ // yet ended. Works for both Vintage (fellowship period) and Position (staff role)
184
+ // once latest.start/latest.stop are fetched. Degrades to false when absent.
185
+ const isPresent = computed(() => {
186
+ const l = latest.value
187
+ if (!l?.start) return false
188
+ const now = Date.now()
189
+ if (new Date(l.start).getTime() > now) return false
190
+ if (!l.stop) return true
191
+ return new Date(l.stop).getTime() >= now
192
+ })
193
+
194
+ // disciplines: scalar enum array → translated labels, capped at 3
195
+ const MAX_DISCIPLINES = 3
196
+ const disciplines = computed(() =>
197
+ Array.isArray(props.item?.disciplines) ? props.item.disciplines : [],
198
+ )
199
+ const shownDisciplines = computed(() =>
200
+ disciplines.value.slice(0, MAX_DISCIPLINES),
201
+ )
202
+ const extraDisciplines = computed(() =>
203
+ Math.max(0, disciplines.value.length - MAX_DISCIPLINES),
204
+ )
205
+ const disciplineLabel = (d) => t("list.filters.disciplines." + d)
206
+
207
+ // responsive line-clamp for the theme (mirrors ExpandableDenseItem)
208
+ const breakpointIndex = computed(() =>
209
+ ["xs", "sm", "md", "lg", "xl", "xxl"].indexOf(displayName.value || "md"),
210
+ )
211
+ const themeLineClamp = computed(() => [1, 1, 2, 2, 3, 3][breakpointIndex.value])
102
212
  </script>
103
- <style>
104
- .paragraph{display:-webkit-box;max-width:83ch!important;-webkit-box-orient:vertical;overflow:hidden}
213
+
214
+ <style scoped>
215
+ .theme-label{display:-webkit-box;font-size:.875rem;font-style:italic;line-height:1.4;max-width:83ch;opacity:.7;-webkit-box-orient:vertical;overflow:hidden}.discipline-chip{border-radius:6px;color:rgba(0,0,0,.78);font-weight:500}.discipline-skeleton :deep(.v-skeleton-loader__chip){margin:0 6px 0 0}.present-pill{align-items:center;background-color:rgba(0,0,0,.06);border-radius:999px;color:rgba(0,0,0,.7);display:inline-flex;font-size:.625rem;font-weight:500;gap:5px;letter-spacing:.04em;line-height:1;padding:3px 8px;text-transform:uppercase}.present-dot{background-color:#4caf50;border-radius:50%;height:6px;width:6px}
105
216
  </style>
@@ -10,10 +10,12 @@
10
10
  >
11
11
  <!-- PEOPLE IMAGE -->
12
12
  <MiscAtomsImageContainer
13
- v-if="mdAndUp && item && item.image"
13
+ v-if="mdAndUp"
14
14
  cover
15
15
  :loading="loading"
16
- :src="item.image"
16
+ :src="
17
+ item && item.image && item.image.url ? item.image : '/default.png'
18
+ "
17
19
  :ratio="1 / 1"
18
20
  :width="
19
21
  [200, 250, 250, 300][
@@ -47,11 +49,19 @@
47
49
  <template v-else>
48
50
  <div
49
51
  v-if="item && item.firstname && item.lastname"
50
- class="my-8 text-h3 align-self-center text-wrap"
52
+ class="mt-8 mb-2 text-h3 align-self-center text-wrap"
51
53
  >
52
54
  {{ item.firstname + " " + item.lastname
53
55
  }}<!-- TODO : call a composable to format people names (multiple, initials, capped & al. ) -->
54
56
  </div>
57
+ <!-- PRESENT -->
58
+ <div v-if="isPresent" class="mb-6 d-flex justify-center">
59
+ <span class="present-pill">
60
+ <span class="present-dot" />
61
+ {{ $t("present") }}
62
+ </span>
63
+ </div>
64
+ <div v-else class="mb-6" />
55
65
  <!-- SOCIALS -->
56
66
  <div class="text-center">
57
67
  <MiscAtomsSocials
@@ -63,6 +73,41 @@
63
73
  <div class="mt-6 align-self-center">
64
74
  <PeopleBadges v-if="item && item.groups" :item="item" />
65
75
  </div>
76
+
77
+ <!-- FELLOWSHIP -->
78
+ <div v-if="fellowName || fellowTheme || fellowDates" class="mt-6">
79
+ <div v-if="fellowName" class="text-h6 font-weight-regular">
80
+ {{ fellowName }}
81
+ </div>
82
+
83
+ <!-- DISCIPLINES -->
84
+ <div
85
+ v-if="disciplines.length"
86
+ class="mt-4 d-flex flex-wrap justify-center"
87
+ style="gap: 6px"
88
+ >
89
+ <v-chip
90
+ v-for="(d, i) in disciplines"
91
+ :key="d + i"
92
+ size="small"
93
+ variant="flat"
94
+ tile
95
+ color="grey-lighten-3"
96
+ class="discipline-chip"
97
+ >
98
+ {{ disciplineLabel(d) }}
99
+ </v-chip>
100
+ </div>
101
+ <div v-if="fellowTheme" class="text-body-1 mt-1 font-italic">
102
+ {{ fellowTheme }}
103
+ </div>
104
+ <div
105
+ v-if="fellowDates"
106
+ class="text-body-2 mt-1 text-medium-emphasis"
107
+ >
108
+ {{ fellowDates }}
109
+ </div>
110
+ </div>
66
111
  </template>
67
112
  </v-col>
68
113
  </v-row>
@@ -165,9 +210,10 @@
165
210
 
166
211
  <script setup>
167
212
  import { useDisplay } from "vuetify"
168
- import { useNuxtApp, useI18n } from "#imports"
213
+ import { computed, useNuxtApp, useI18n } from "#imports"
214
+ import { formatDate } from "../../composables/useUtils"
169
215
 
170
- const { locale } = useI18n()
216
+ const { t, locale } = useI18n()
171
217
  const { $stores } = useNuxtApp()
172
218
  const { name, mdAndUp } = useDisplay()
173
219
  const props = defineProps({
@@ -176,5 +222,42 @@ const props = defineProps({
176
222
  })
177
223
  $stores.people.loading = false
178
224
 
179
- console.log("people", props.item)
225
+ // disciplines: scalar enum array → translated labels
226
+ const disciplines = computed(() =>
227
+ Array.isArray(props.item?.disciplines) ? props.item.disciplines : [],
228
+ )
229
+ const disciplineLabel = (d) => t("list.filters.disciplines." + d)
230
+
231
+ // latest is a union Vintage | Position; the fellowship program lives on the
232
+ // Vintage branch (name = program name, theme = fellowship theme) and carries the
233
+ // arrival/departure dates (start/stop). Guard by field presence.
234
+ const latest = computed(() => props.item?.latest ?? null)
235
+ const fellowName = computed(() => latest.value?.name ?? null)
236
+ const fellowTheme = computed(() => latest.value?.theme ?? null)
237
+
238
+ // "present" = currently in residence/post: started on or before today and not
239
+ // yet ended. Works for both Vintage and Position; degrades to false if start/stop
240
+ // are absent (e.g. before the trees query change is published).
241
+ const isPresent = computed(() => {
242
+ const l = latest.value
243
+ if (!l?.start) return false
244
+ const now = Date.now()
245
+ if (new Date(l.start).getTime() > now) return false
246
+ if (!l.stop) return true
247
+ return new Date(l.stop).getTime() >= now
248
+ })
249
+
250
+ // Arrival/departure dates straight from latest.start/latest.stop.
251
+ const fellowDates = computed(() => {
252
+ const l = latest.value
253
+ if (!l?.start) return null
254
+ return t("from {0} to {1}", [
255
+ formatDate(l.start, locale.value),
256
+ (l.stop && formatDate(l.stop, locale.value)) || t("present"),
257
+ ])
258
+ })
180
259
  </script>
260
+
261
+ <style scoped>
262
+ .discipline-chip{border-radius:6px;color:rgba(0,0,0,.78);font-weight:500}.present-pill{align-items:center;background-color:rgba(0,0,0,.06);border-radius:999px;color:rgba(0,0,0,.7);display:inline-flex;font-size:.6875rem;font-weight:500;gap:5px;letter-spacing:.04em;line-height:1;padding:4px 10px;text-transform:uppercase}.present-dot{background-color:#4caf50;border-radius:50%;height:6px;width:6px}
263
+ </style>
@@ -286,6 +286,7 @@
286
286
  },
287
287
  "disciplines": {
288
288
  "label": "Discipline",
289
+ "ALL": "All disciplines",
289
290
  "ANTHROPOLOGY_AND_ETHNOLOGY": "Anthropology and Ethnology",
290
291
  "ARCHITECTURE_AND_URBAN_PLANNING": "Architecture and Urban Planning",
291
292
  "ARCHAEOLOGY": "Archaeology",
@@ -311,6 +312,7 @@
311
312
  "SOCIOLOGY": "Sociology",
312
313
  "STUDIES_IN_SCIENCE_AND_TECHNOLOGY": "Studies in science and technology",
313
314
  "THEOLOGY": "Theology",
315
+ "OTHERS": "Others",
314
316
  "BIOLOGY": "Biology",
315
317
  "CHEMISTRY": "Chemistry",
316
318
  "COMPUTER_SCIENCE": "Computer science",
@@ -13,7 +13,7 @@
13
13
  "see-more": "Voir plus d'événements"
14
14
  },
15
15
  "filters": "filtre | filtre | Filtres",
16
- "from {0} to {1}": "Du {0} à {1}",
16
+ "from {0} to {1}": "Du {0} au {1}",
17
17
  "gallery": "Galerie",
18
18
  "hybrid-event": "Évènement hybride",
19
19
  "inscription-gratuite-et-obligatoire": "Enregistrement gratuit et obligatoire",
@@ -275,6 +275,7 @@
275
275
  }
276
276
  },
277
277
  "disciplines": {
278
+ "ALL": "Toutes les disciplines",
278
279
  "label": "Discipline",
279
280
  "ANTHROPOLOGY_AND_ETHNOLOGY": "Anthropologie et ethnologie",
280
281
  "ARCHITECTURE_AND_URBAN_PLANNING": "Architecture et urbanisme",
@@ -301,6 +302,7 @@
301
302
  "SOCIOLOGY": "Sociologie",
302
303
  "STUDIES_IN_SCIENCE_AND_TECHNOLOGY": "Études en sciences et technologies",
303
304
  "THEOLOGY": "Théologie",
305
+ "OTHERS": "Autres",
304
306
  "BIOLOGY": "Biologie",
305
307
  "CHEMISTRY": "Chimie",
306
308
  "COMPUTER_SCIENCE": "Informatique",
@@ -322,6 +324,7 @@
322
324
  "IDENTITIES_GENDER_AND_SEXUALITIES": "Identités, genre et sexualités",
323
325
  "INEQUALITIES_INCLUSION_SOCIAL_INNOVATION": "Inégalités, inclusion, innovation sociale",
324
326
  "MIGRATIONS": "Migrations",
327
+ "OTHERS": "Autres",
325
328
  "OCEANS": "Océans",
326
329
  "PUBLIC_POLICIES": "Politiques publiques",
327
330
  "RADICALISATION_VIOLENCE_EXTREMISM": "Radicalisations, violences, extrémismes",
@@ -439,7 +442,7 @@
439
442
  "visit": "Nous rendre visite",
440
443
  "visit-the-project-website": "Visiter le site web",
441
444
  "groups": "Catégorie",
442
- "present": "aujourd'hui",
445
+ "present": "Présent",
443
446
  "visit-this-project-website": "Visitez le site Web du projet",
444
447
  "visit-this-publications-website": "Visitez la page Web de cette publication",
445
448
  "no-biography": "Aucune biographie disponible",
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "license": "AGPL-3.0-only",
3
3
  "main": "./dist/module.mjs",
4
- "version": "1.1.18",
4
+ "version": "1.1.20",
5
5
  "name": "@paris-ias/list",
6
6
  "repository": {
7
7
  "url": "git+https://github.com/IEA-Paris/list.git",
8
8
  "type": "git"
9
9
  },
10
10
  "dependencies": {
11
- "@paris-ias/trees": "^2.2.9"
11
+ "@paris-ias/trees": "^2.2.14"
12
12
  },
13
13
  "description": "Paris IAS List Module",
14
14
  "peerDependencies": {