@tekkare/romulus 0.1.250 → 0.1.252

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 (270) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/BarChart.vue +1 -0
  3. package/dist/runtime/components/ChoroplethMap.vue +4 -1
  4. package/dist/runtime/internal/geo/fr-regions.json +1 -1
  5. package/dist/runtime/utils/chart.d.ts +6 -3
  6. package/dist/runtime/utils/chart.js +15 -2
  7. package/layer/app/app.config.ts +6 -0
  8. package/layer/app/components/OipPubmedSummary.vue +223 -0
  9. package/layer/app/components/OipResourceDrawer.vue +331 -0
  10. package/layer/app/components/OipResourceDrawerLink.vue +40 -0
  11. package/layer/app/components/OipResourceField.vue +41 -0
  12. package/layer/app/components/OipResourceGenericCard.vue +54 -0
  13. package/layer/app/components/OipResourceSource.vue +50 -0
  14. package/layer/app/components/OipResourceTags.vue +65 -0
  15. package/layer/app/components/OipResourceView.vue +107 -0
  16. package/layer/app/components/oip-drugs/fiches/AssociationFiche.vue +775 -0
  17. package/layer/app/components/oip-drugs/fiches/BrandFiche.vue +1111 -0
  18. package/layer/app/components/oip-drugs/fiches/IndicationFiche.vue +182 -0
  19. package/layer/app/components/oip-drugs/fiches/JoFiche.vue +101 -0
  20. package/layer/app/components/oip-drugs/fiches/PndsFiche.vue +279 -0
  21. package/layer/app/components/oip-drugs/heart/JoCard.vue +314 -0
  22. package/layer/app/components/oip-drugs/heart/TextHighlight.vue +326 -0
  23. package/layer/app/components/oip-drugs/heart/ansm/BrandCard.vue +187 -0
  24. package/layer/app/components/oip-drugs/heart/ansm/CisDetail.vue +363 -0
  25. package/layer/app/components/oip-drugs/heart/ansm/CisTable.vue +167 -0
  26. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyCard.vue +373 -0
  27. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyFiche.vue +372 -0
  28. package/layer/app/components/oip-drugs/heart/associations/TranscriptionCard.vue +55 -0
  29. package/layer/app/components/oip-drugs/heart/elastic/SourceContainerElastic.vue +103 -0
  30. package/layer/app/components/oip-drugs/heart/france/BrandGroupCard.vue +450 -0
  31. package/layer/app/components/oip-drugs/heart/mitm/Card.vue +199 -0
  32. package/layer/app/components/oip-drugs/heart/mitm/Source.vue +23 -0
  33. package/layer/app/components/oip-drugs/heart/mitm/details/Overview.vue +381 -0
  34. package/layer/app/components/oip-drugs/heart/put/Card.vue +212 -0
  35. package/layer/app/components/oip-drugs/heart/results/AccesCard.vue +696 -0
  36. package/layer/app/components/oip-drugs/heart/results/AlertCard.vue +342 -0
  37. package/layer/app/components/oip-drugs/heart/results/AsmrCursor.vue +229 -0
  38. package/layer/app/components/oip-drugs/heart/results/EvalCard.vue +370 -0
  39. package/layer/app/components/oip-drugs/heart/results/IndicCard.vue +743 -0
  40. package/layer/app/components/oip-drugs/heart/results/MatchExplain.vue +243 -0
  41. package/layer/app/components/oip-drugs/heart/results/PrefRows.vue +105 -0
  42. package/layer/app/components/oip-drugs/heart/results/details/HasHistory.vue +487 -0
  43. package/layer/app/components/oip-drugs/heart/results/details/InternationalCountryCard.vue +461 -0
  44. package/layer/app/components/oip-drugs/heart/results/details/InternationalOverview.vue +200 -0
  45. package/layer/app/components/oip-drugs/heart/results/details/InternationalPriceDrawer.vue +647 -0
  46. package/layer/app/components/oip-drugs/heart/results/details/Monograph.vue +128 -0
  47. package/layer/app/components/oip-drugs/heart/results/details/OpinionDrawer.vue +535 -0
  48. package/layer/app/components/oip-drugs/heart/results/details/PresentationDrawer.vue +738 -0
  49. package/layer/app/components/oip-drugs/heart/results/details/Price.vue +227 -0
  50. package/layer/app/components/oip-drugs/heart/results/details/PriceOverview.vue +1261 -0
  51. package/layer/app/components/oip-drugs/heart/results/details/RecordDrawer.vue +494 -0
  52. package/layer/app/components/oip-drugs/heart/results/details/RecordList.vue +426 -0
  53. package/layer/app/components/oip-drugs/heart/results/les/Card.vue +340 -0
  54. package/layer/app/components/oip-drugs/heart/results/les/Drawer.vue +67 -0
  55. package/layer/app/components/oip-drugs/heart/results/les/IndicationDetail.vue +149 -0
  56. package/layer/app/components/oip-drugs/heart/results/les/Source.vue +20 -0
  57. package/layer/app/components/oip-drugs/heart/results/les/SourceTag.vue +39 -0
  58. package/layer/app/components/oip-drugs/heart/searchPage/DynamicSource.vue +29 -0
  59. package/layer/app/components/oip-drugs/medicam/market/Chart.vue +121 -0
  60. package/layer/app/components/oip-drugs/outpatient/ProductEvolution.vue +236 -0
  61. package/layer/app/components/oip-drugs/resources/Ansm.vue +75 -0
  62. package/layer/app/components/oip-drugs/resources/Association.vue +30 -0
  63. package/layer/app/components/oip-drugs/resources/Brand.vue +65 -0
  64. package/layer/app/components/oip-drugs/resources/Contribution.vue +216 -0
  65. package/layer/app/components/oip-drugs/resources/Hospital.vue +151 -0
  66. package/layer/app/components/oip-drugs/resources/Indication.vue +54 -0
  67. package/layer/app/components/oip-drugs/resources/Jo.vue +57 -0
  68. package/layer/app/components/oip-drugs/resources/Medicam.vue +140 -0
  69. package/layer/app/components/oip-drugs/resources/Mitm.vue +84 -0
  70. package/layer/app/components/oip-drugs/resources/NationalAssembly.vue +93 -0
  71. package/layer/app/components/oip-drugs/resources/Outpatient.vue +97 -0
  72. package/layer/app/components/oip-drugs/resources/Pnds.vue +24 -0
  73. package/layer/app/components/oip-drugs/resources/Price.vue +106 -0
  74. package/layer/app/components/oip-drugs/resources/Put.vue +143 -0
  75. package/layer/app/components/oip-science/appel-a-projet/Cards.vue +162 -0
  76. package/layer/app/components/oip-science/clinical-trials/Cards.vue +382 -0
  77. package/layer/app/components/oip-science/clinical-trials/CardsGlobal.vue +273 -0
  78. package/layer/app/components/oip-science/clinical-trials/LinkedList.vue +366 -0
  79. package/layer/app/components/oip-science/clinical-trials/PairBadge.vue +25 -0
  80. package/layer/app/components/oip-science/clinical-trials/PairBody.vue +182 -0
  81. package/layer/app/components/oip-science/clinical-trials/SitesDrawer.vue +95 -0
  82. package/layer/app/components/oip-science/common/Form.vue +73 -0
  83. package/layer/app/components/oip-science/common/MatchAvgTag.vue +283 -0
  84. package/layer/app/components/oip-science/common/NoData.vue +16 -0
  85. package/layer/app/components/oip-science/common/ScoreBadge.vue +35 -0
  86. package/layer/app/components/oip-science/common/Source.vue +31 -0
  87. package/layer/app/components/oip-science/common/TableTags.vue +279 -0
  88. package/layer/app/components/oip-science/common/Timeline.vue +104 -0
  89. package/layer/app/components/oip-science/common/ToolTips.vue +89 -0
  90. package/layer/app/components/oip-science/cross/publicationPubmed.vue +484 -0
  91. package/layer/app/components/oip-science/crossData/CrossDataLocations.vue +71 -0
  92. package/layer/app/components/oip-science/ctgov/Investigators.vue +148 -0
  93. package/layer/app/components/oip-science/ctgov/Locations.vue +102 -0
  94. package/layer/app/components/oip-science/ctgov/Publications.vue +52 -0
  95. package/layer/app/components/oip-science/ctgov/Sponsors.vue +66 -0
  96. package/layer/app/components/oip-science/ctgov/StudyDescription.vue +1136 -0
  97. package/layer/app/components/oip-science/ctgov-centers/ClinicalTrials.vue +40 -0
  98. package/layer/app/components/oip-science/ctgov-centers/Publications.vue +50 -0
  99. package/layer/app/components/oip-science/ctgov-investigators/ClinicalTrials.vue +43 -0
  100. package/layer/app/components/oip-science/ctgov-investigators/Informations.vue +171 -0
  101. package/layer/app/components/oip-science/ctgov-investigators/Publications.vue +44 -0
  102. package/layer/app/components/oip-science/elastic/DateRangeFilter.vue +209 -0
  103. package/layer/app/components/oip-science/elastic/Filter.vue +719 -0
  104. package/layer/app/components/oip-science/elastic/MatchExplain.vue +152 -0
  105. package/layer/app/components/oip-science/establishments/Cards.vue +183 -0
  106. package/layer/app/components/oip-science/establishments/ClinicalTrials.vue +62 -0
  107. package/layer/app/components/oip-science/establishments/Hcps.vue +227 -0
  108. package/layer/app/components/oip-science/establishments/Informations.vue +263 -0
  109. package/layer/app/components/oip-science/establishments/LinkedList.vue +153 -0
  110. package/layer/app/components/oip-science/establishments/MatchedLabels.vue +194 -0
  111. package/layer/app/components/oip-science/establishments/PublicFinance.vue +62 -0
  112. package/layer/app/components/oip-science/euct/Investigators.vue +125 -0
  113. package/layer/app/components/oip-science/euct/Locations.vue +102 -0
  114. package/layer/app/components/oip-science/euct/Products.vue +101 -0
  115. package/layer/app/components/oip-science/euct/Publications.vue +89 -0
  116. package/layer/app/components/oip-science/euct/Sponsors.vue +86 -0
  117. package/layer/app/components/oip-science/euct/StudyDescription.vue +1075 -0
  118. package/layer/app/components/oip-science/euct-centers/ClinicalTrials.vue +40 -0
  119. package/layer/app/components/oip-science/euct-centers/Publications.vue +55 -0
  120. package/layer/app/components/oip-science/euct-investigators/ClinicalTrials.vue +41 -0
  121. package/layer/app/components/oip-science/euct-investigators/Informations.vue +171 -0
  122. package/layer/app/components/oip-science/euct-investigators/Publications.vue +51 -0
  123. package/layer/app/components/oip-science/hcps/AppelsAProjet.vue +62 -0
  124. package/layer/app/components/oip-science/hcps/Cards.vue +237 -0
  125. package/layer/app/components/oip-science/hcps/ClinicalTrials.vue +78 -0
  126. package/layer/app/components/oip-science/hcps/Informations.vue +296 -0
  127. package/layer/app/components/oip-science/hcps/LinkedList.vue +153 -0
  128. package/layer/app/components/oip-science/hcps/MatchedLabels.vue +207 -0
  129. package/layer/app/components/oip-science/hcps/Pubmed.vue +128 -0
  130. package/layer/app/components/oip-science/local/SliderTabs.vue +99 -0
  131. package/layer/app/components/oip-science/local/SliderTabsFull.vue +135 -0
  132. package/layer/app/components/oip-science/pubmed/Authors.vue +128 -0
  133. package/layer/app/components/oip-science/pubmed/Cards.vue +417 -0
  134. package/layer/app/components/oip-science/pubmed/ClinicalTrials.vue +71 -0
  135. package/layer/app/components/oip-science/resources/Phrc.vue +39 -0
  136. package/layer/app/composables/useOipChapterScroll.ts +43 -0
  137. package/layer/app/composables/useOipResourceHooks.ts +27 -0
  138. package/layer/app/composables/useOipResourceNav.ts +66 -0
  139. package/layer/app/composables/useOipResourceViews.ts +117 -0
  140. package/layer/app/composables/useOipResources.ts +83 -0
  141. package/layer/app/oip-drugs/PORTING.md +120 -0
  142. package/layer/app/oip-drugs/composables/useAssociationDetail.ts +220 -0
  143. package/layer/app/oip-drugs/composables/useAssociationExport.ts +63 -0
  144. package/layer/app/oip-drugs/composables/useAssociationLookup.ts +105 -0
  145. package/layer/app/oip-drugs/composables/useCardPrefOptions.ts +201 -0
  146. package/layer/app/oip-drugs/composables/useCurrency.ts +121 -0
  147. package/layer/app/oip-drugs/composables/useDirectusSources.ts +224 -0
  148. package/layer/app/oip-drugs/composables/useHeartBrandMatches.ts +112 -0
  149. package/layer/app/oip-drugs/composables/useHeartDetailSearches.ts +479 -0
  150. package/layer/app/oip-drugs/composables/useHeartIntlApprovals.ts +661 -0
  151. package/layer/app/oip-drugs/composables/useHeartLesIndication.ts +168 -0
  152. package/layer/app/oip-drugs/composables/useHeartMatchExplain.ts +231 -0
  153. package/layer/app/oip-drugs/composables/useHeartReports.ts +239 -0
  154. package/layer/app/oip-drugs/composables/useHeartSearchFields.ts +233 -0
  155. package/layer/app/oip-drugs/composables/useMergeByKey.ts +23 -0
  156. package/layer/app/oip-drugs/composables/useOipDrugsElastic.ts +621 -0
  157. package/layer/app/oip-drugs/composables/usePageEmpty.ts +58 -0
  158. package/layer/app/oip-drugs/composables/v-useSalesSource.ts +64 -0
  159. package/layer/app/oip-drugs/drugs.css +500 -0
  160. package/layer/app/oip-drugs/esIndices.ts +322 -0
  161. package/layer/app/oip-drugs/kinds.ts +264 -0
  162. package/layer/app/oip-drugs/locales/en.json +1571 -0
  163. package/layer/app/oip-drugs/locales/fr.json +1571 -0
  164. package/layer/app/oip-drugs/monographs/blincyto-has.vue +289 -0
  165. package/layer/app/oip-drugs/monographs/darzalex-has.vue +276 -0
  166. package/layer/app/oip-drugs/monographs/dupixent-has.vue +216 -0
  167. package/layer/app/oip-drugs/monographs/enhertu-has.vue +280 -0
  168. package/layer/app/oip-drugs/monographs/has-sein-2023.vue +1649 -0
  169. package/layer/app/oip-drugs/monographs/ibrance-has.vue +277 -0
  170. package/layer/app/oip-drugs/monographs/imfinzi-has.vue +293 -0
  171. package/layer/app/oip-drugs/monographs/keytruda-has.vue +334 -0
  172. package/layer/app/oip-drugs/monographs/kisqali-has.vue +201 -0
  173. package/layer/app/oip-drugs/monographs/opdivo-has.vue +231 -0
  174. package/layer/app/oip-drugs/monographs/ozempic-has.vue +186 -0
  175. package/layer/app/oip-drugs/monographs/tecentriq-has.vue +312 -0
  176. package/layer/app/oip-drugs/monographs/yescarta-has.vue +272 -0
  177. package/layer/app/oip-drugs/types/elastic.ts +113 -0
  178. package/layer/app/oip-drugs/types/es/agencies.ts +152 -0
  179. package/layer/app/oip-drugs/types/es/directory.ts +54 -0
  180. package/layer/app/oip-drugs/types/es/docByIndex.ts +151 -0
  181. package/layer/app/oip-drugs/types/es/openmedic.ts +67 -0
  182. package/layer/app/oip-drugs/types/es/outpatient.ts +71 -0
  183. package/layer/app/oip-drugs/types/es/portal.ts +130 -0
  184. package/layer/app/oip-drugs/types/es/retail.ts +107 -0
  185. package/layer/app/oip-drugs/types/es/statistics.ts +29 -0
  186. package/layer/app/oip-drugs/types/heart/CurrencyRate.ts +16 -0
  187. package/layer/app/oip-drugs/types/heart/ansm.ts +106 -0
  188. package/layer/app/oip-drugs/types/heart/tag.ts +29 -0
  189. package/layer/app/oip-drugs/utils/associationFields.ts +118 -0
  190. package/layer/app/oip-drugs/utils/contributions.ts +36 -0
  191. package/layer/app/oip-drugs/utils/countryMapping.ts +387 -0
  192. package/layer/app/oip-drugs/utils/esHighlight.ts +21 -0
  193. package/layer/app/oip-drugs/utils/exportSheets.ts +57 -0
  194. package/layer/app/oip-drugs/utils/heartIntlPrice.ts +228 -0
  195. package/layer/app/oip-drugs/utils/heartStyleAndFormat.ts +262 -0
  196. package/layer/app/oip-drugs/utils/heartStyleAnsm.ts +71 -0
  197. package/layer/app/oip-drugs/utils/latestDate.ts +31 -0
  198. package/layer/app/oip-drugs/utils/p-heartCardTags.ts +53 -0
  199. package/layer/app/oip-drugs/utils/priceExport.ts +269 -0
  200. package/layer/app/oip-drugs/utils/q-hasRatings.ts +43 -0
  201. package/layer/app/oip-drugs/utils/s-openCard.ts +20 -0
  202. package/layer/app/oip-drugs/utils/sourceUuids.ts +155 -0
  203. package/layer/app/oip-drugs/utils/styleAndFormat.ts +40 -0
  204. package/layer/app/oip-drugs/utils/v-salesSeries.ts +156 -0
  205. package/layer/app/oip-drugs/utils/v-scopeTags.ts +23 -0
  206. package/layer/app/oip-science/PORTING.md +93 -0
  207. package/layer/app/oip-science/assets/smartSelection.json +428 -0
  208. package/layer/app/oip-science/composables/useAuthorMatching.ts +342 -0
  209. package/layer/app/oip-science/composables/useCTCenter.ts +107 -0
  210. package/layer/app/oip-science/composables/useCTInvestigators.ts +107 -0
  211. package/layer/app/oip-science/composables/useEstablishmentCrossData.ts +139 -0
  212. package/layer/app/oip-science/composables/useEstablishmentMatching.ts +176 -0
  213. package/layer/app/oip-science/composables/useEuct.ts +418 -0
  214. package/layer/app/oip-science/composables/useEuctCenter.ts +107 -0
  215. package/layer/app/oip-science/composables/useEuctInvestigators.ts +107 -0
  216. package/layer/app/oip-science/composables/useGlobalClinicalTrials.ts +624 -0
  217. package/layer/app/oip-science/composables/useLinkedTrials.ts +83 -0
  218. package/layer/app/oip-science/composables/useOipElastic.ts +658 -0
  219. package/layer/app/oip-science/composables/useOipSearchExplain.ts +166 -0
  220. package/layer/app/oip-science/composables/usePubmed.ts +487 -0
  221. package/layer/app/oip-science/composables/useSearchFields.ts +153 -0
  222. package/layer/app/oip-science/indexConfig.json +794 -0
  223. package/layer/app/oip-science/locales/en.json +483 -0
  224. package/layer/app/oip-science/locales/fr.json +483 -0
  225. package/layer/app/oip-science/science.css +1182 -0
  226. package/layer/app/oip-science/utils/a-openCard.ts +23 -0
  227. package/layer/app/oip-science/utils/b-openCard.ts +35 -0
  228. package/layer/app/oip-science/utils/c-filterContext.ts +81 -0
  229. package/layer/app/oip-science/utils/f-types.ts +241 -0
  230. package/layer/app/oip-science/utils/frDiseaseTerms.ts +269 -0
  231. package/layer/app/oip-science/utils/matchMethod.ts +53 -0
  232. package/layer/app/oip-science/utils/sliderTabs.ts +13 -0
  233. package/layer/app/oip-science/utils/sources.ts +15 -0
  234. package/layer/app/oip-science/utils/styleAndFormat.ts +608 -0
  235. package/layer/app/oip-science/utils/toPresets.ts +7 -0
  236. package/layer/app/oip-science/utils/valueTable.ts +126 -0
  237. package/layer/app/plugins/oip-science-i18n.ts +17 -0
  238. package/layer/app/utils/oipFicheRoutes.ts +118 -0
  239. package/layer/app/utils/oipResources.ts +440 -0
  240. package/layer/nuxt.config.ts +22 -2
  241. package/layer/public/reports/blincyto-has.html +292 -0
  242. package/layer/public/reports/blincyto-has.pdf +0 -0
  243. package/layer/public/reports/darzalex-has.html +279 -0
  244. package/layer/public/reports/darzalex-has.pdf +0 -0
  245. package/layer/public/reports/dupixent-has.html +217 -0
  246. package/layer/public/reports/dupixent-has.pdf +0 -0
  247. package/layer/public/reports/enhertu-has.html +283 -0
  248. package/layer/public/reports/enhertu-has.pdf +0 -0
  249. package/layer/public/reports/has-sein-2023.html +1639 -0
  250. package/layer/public/reports/ibrance-has.html +280 -0
  251. package/layer/public/reports/ibrance-has.pdf +0 -0
  252. package/layer/public/reports/imfinzi-has.html +297 -0
  253. package/layer/public/reports/imfinzi-has.pdf +0 -0
  254. package/layer/public/reports/keytruda-has.html +337 -0
  255. package/layer/public/reports/keytruda-has.pdf +0 -0
  256. package/layer/public/reports/kisqali-has.html +202 -0
  257. package/layer/public/reports/kisqali-has.pdf +0 -0
  258. package/layer/public/reports/opdivo-has.html +232 -0
  259. package/layer/public/reports/opdivo-has.pdf +0 -0
  260. package/layer/public/reports/ozempic-has.html +188 -0
  261. package/layer/public/reports/ozempic-has.pdf +0 -0
  262. package/layer/public/reports/tecentriq-has.html +315 -0
  263. package/layer/public/reports/tecentriq-has.pdf +0 -0
  264. package/layer/public/reports/yescarta-has.html +275 -0
  265. package/layer/public/reports/yescarta-has.pdf +0 -0
  266. package/layer/server/api/oip/es.post.ts +99 -0
  267. package/layer/server/api/oip/pdf.get.ts +70 -0
  268. package/layer/server/api/oip/reports.get.ts +98 -0
  269. package/layer/server/api/oip/resource-types.get.ts +22 -0
  270. package/package.json +1 -1
@@ -0,0 +1,487 @@
1
+ <!--
2
+ Historique des avis HAS d'une marque, en cartes groupees par annee.
3
+
4
+ Le tableau d'historique disait la meme chose en lignes serrees : on lisait
5
+ mal ce que chaque avis avait decide. Ici chaque avis est une carte — son type,
6
+ sa date, sa reference, ce qu'il vise et ce qu'il conclut — et le detail
7
+ complet s'ouvre dans un tiroir plutot que de pousser la page.
8
+ -->
9
+ <script setup lang="ts">
10
+ import { hasRatingTags } from "../../../../../oip-drugs/utils/q-hasRatings";
11
+ const props = defineProps<{
12
+ /** Le document de la marque (_source), avec son brand_history. */
13
+ product: any;
14
+ /** Codes HAS des avis qui repondent a la recherche en cours. */
15
+ matchedCodes?: Set<string>;
16
+ /** Termes saisis, surlignes dans le texte des cartes. */
17
+ searchTerms?: string[];
18
+ }>();
19
+
20
+ const emit = defineEmits<{ (event: "select", opinion: any): void }>();
21
+
22
+ const { locale } = useI18n();
23
+
24
+ /** Combien d'avis on montre avant de demander « voir plus ». */
25
+ const PAGE = 10;
26
+ const shown = ref(PAGE);
27
+
28
+ const AWAITING_TRANSLATION = /^\s*translation coming soon/i;
29
+
30
+ /** Premiere valeur exploitable : l'anglais quand il existe, le francais sinon. */
31
+ function text(opinion: any, field: string) {
32
+ const candidates =
33
+ locale.value === "en" ? [`${field}_en`, field] : [field, `${field}_en`];
34
+ for (const key of candidates) {
35
+ const value = opinion?.[key];
36
+ if (typeof value === "string" && value.trim() && !AWAITING_TRANSLATION.test(value)) {
37
+ return value.trim();
38
+ }
39
+ }
40
+ return "";
41
+ }
42
+
43
+ /** Les avis, alertes exclues, du plus recent au plus ancien. */
44
+ const opinions = computed<any[]>(() =>
45
+ [...(props.product?.brand_history ?? [])]
46
+ .filter((h: any) => h?.document_type !== "alert")
47
+ .sort(
48
+ (a, b) => new Date(b.decision_date).getTime() - new Date(a.decision_date).getTime(),
49
+ ),
50
+ );
51
+
52
+ const visible = computed(() => opinions.value.slice(0, shown.value));
53
+
54
+ /** Groupes par annee de decision, dans l'ordre d'affichage. */
55
+ const years = computed(() => {
56
+ const groups = new Map<string, any[]>();
57
+ visible.value.forEach((opinion) => {
58
+ const year = String(new Date(opinion.decision_date).getFullYear() || "-");
59
+ if (!groups.has(year)) groups.set(year, []);
60
+ groups.get(year)!.push(opinion);
61
+ });
62
+ return [...groups.entries()].map(([year, items]) => ({ year, items }));
63
+ });
64
+
65
+ function typeLabel(opinion: any) {
66
+ if (opinion.opinion_type === "early_access_decision") {
67
+ return locale.value === "en" ? "Early access" : "Accès précoce";
68
+ }
69
+ if (opinion.opinion_type === "eval") {
70
+ return locale.value === "en" ? "Assessment" : "Évaluation";
71
+ }
72
+ return locale.value === "en" ? "Opinion" : "Avis";
73
+ }
74
+
75
+ function typeIcon(opinion: any) {
76
+ return opinion.opinion_type === "early_access_decision" ? "Lightning" : "FileText";
77
+ }
78
+
79
+ /** Ce que vise l'avis : le libelle court quand il existe, l'indication sinon. */
80
+ function title(opinion: any) {
81
+ return (
82
+ text(opinion, "disease_name") ||
83
+ text(opinion, "relevant_indication") ||
84
+ text(opinion, "indication") ||
85
+ (locale.value === "en" ? "Opinion" : "Avis")
86
+ );
87
+ }
88
+
89
+ /** Ce qu'il conclut, en deux lignes. */
90
+ function excerpt(opinion: any) {
91
+ return (
92
+ text(opinion, "recommendation") ||
93
+ text(opinion, "conclusion") ||
94
+ text(opinion, "indication")
95
+ );
96
+ }
97
+
98
+ /** « avis favorable » vient de l'index en minuscules. */
99
+ function statusLabel(opinion: any) {
100
+ const status = text(opinion, "opinion_status");
101
+ return status ? status.charAt(0).toUpperCase() + status.slice(1) : "";
102
+ }
103
+
104
+ /** L'avis repond-il a la recherche qui a mene ici ? */
105
+ function isMatched(opinion: any) {
106
+ return !!opinion?.has_code && !!props.matchedCodes?.has(String(opinion.has_code));
107
+ }
108
+
109
+ /** Les avis matches d'abord : c'est ce qu'on vient lire. */
110
+ const matchedCount = computed(() => opinions.value.filter(isMatched).length);
111
+
112
+ function statusTone(opinion: any) {
113
+ const status = text(opinion, "opinion_status").toLowerCase();
114
+ if (status.includes("défavorable") || status.includes("unfavourable")) return "is_bad";
115
+ if (status.includes("favorable")) return "is_good";
116
+ return "is_neutral";
117
+ }
118
+ </script>
119
+
120
+ <template>
121
+ <section class="hh oip-drugs">
122
+ <header class="hh_head">
123
+ <div>
124
+ <h2 class="hh_title">
125
+ {{
126
+ locale === "en"
127
+ ? "French National Authority for Health opinions"
128
+ : "Avis de la Haute Autorité de santé"
129
+ }}
130
+ </h2>
131
+ <p class="hh_count">
132
+ {{ opinions.length }}
133
+ {{
134
+ locale === "en"
135
+ ? `opinions recorded for the brand · ${visible.length} shown`
136
+ : `avis recensés pour la marque · ${visible.length} affichés`
137
+ }}
138
+ </p>
139
+ </div>
140
+ <p class="hh_source">Source : <b>HAS</b></p>
141
+ </header>
142
+
143
+ <!-- Ce que la recherche a trouve ici : sans ce rappel, on arrive sur des
144
+ dizaines d'avis sans savoir lesquels ont repondu aux termes saisis. -->
145
+ <p v-if="matchedCount" class="hh_match_note">
146
+ <RmIcon name="Target" :size="14" />
147
+ <template v-if="locale === 'en'">
148
+ <b>{{ matchedCount }}</b> opinion(s) match your search
149
+ <template v-if="searchTerms?.length">
150
+ for <b>{{ searchTerms.join(", ") }}</b>
151
+ </template>
152
+ </template>
153
+ <template v-else>
154
+ <b>{{ matchedCount }}</b> avis correspondent à votre recherche
155
+ <template v-if="searchTerms?.length">
156
+ <b>{{ searchTerms.join(", ") }}</b>
157
+ </template>
158
+ </template>
159
+ </p>
160
+
161
+ <div v-for="group in years" :key="group.year" class="hh_year">
162
+ <p class="hh_year_label">{{ group.year }}</p>
163
+
164
+ <article
165
+ v-for="opinion in group.items"
166
+ :key="opinion.has_code || opinion.opinion_id"
167
+ class="hh_card"
168
+ :class="{ is_matched: isMatched(opinion) }"
169
+ @click="emit('select', opinion)"
170
+ >
171
+ <div class="hh_card_head">
172
+ <p class="hh_type">
173
+ <span class="hh_type_icon"><RmIcon :name="typeIcon(opinion)" :size="14" /></span>
174
+ {{ typeLabel(opinion) }}
175
+ <span v-if="isMatched(opinion)" class="hh_match_tag">
176
+ {{ locale === "en" ? "Matches your search" : "Correspond à votre recherche" }}
177
+ </span>
178
+ <RmTag
179
+ v-for="tag in hasRatingTags(opinion, locale)"
180
+ :key="tag.key"
181
+ :variant="tag.variant"
182
+ >
183
+ {{ tag.label }}
184
+ </RmTag>
185
+ </p>
186
+
187
+ <div class="hh_meta">
188
+ <RmDate :date="opinion.decision_date" :lang="locale" />
189
+ <span v-if="opinion.has_code" class="hh_ref">réf. HAS {{ opinion.has_code }}</span>
190
+ <a
191
+ v-if="opinion.has_url"
192
+ class="hh_link"
193
+ :href="opinion.has_url"
194
+ target="_blank"
195
+ rel="noopener noreferrer"
196
+ :data-tip="locale === 'en' ? 'Download' : 'Télécharger'"
197
+ @click.stop
198
+ >
199
+ <RmIcon name="FilePdf" :size="16" />
200
+ </a>
201
+ </div>
202
+ </div>
203
+
204
+ <div class="hh_card_body">
205
+ <h3 class="hh_card_title">
206
+ <OipDrugsHeartTextHighlight :text="title(opinion)" :queries="searchTerms ?? []" />
207
+ </h3>
208
+ <p v-if="excerpt(opinion)" class="hh_excerpt">
209
+ <OipDrugsHeartTextHighlight :text="excerpt(opinion)" :queries="searchTerms ?? []" />
210
+ </p>
211
+
212
+ <p v-if="statusLabel(opinion)" class="hh_status" :class="statusTone(opinion)">
213
+ {{ statusLabel(opinion) }}
214
+ </p>
215
+
216
+ <p class="hh_more">
217
+ {{ locale === "en" ? "See the opinion" : "Consulter l'avis" }} →
218
+ </p>
219
+ </div>
220
+ </article>
221
+ </div>
222
+
223
+ <button
224
+ v-if="shown < opinions.length"
225
+ type="button"
226
+ class="hh_load"
227
+ @click="shown += PAGE"
228
+ >
229
+ {{ locale === "en" ? "Show more opinions" : "Voir plus d'avis" }}
230
+ </button>
231
+ </section>
232
+ </template>
233
+
234
+ <style scoped>
235
+ .hh {
236
+ display: flex;
237
+ flex-direction: column;
238
+ gap: 12px;
239
+ width: 100%;
240
+ /* La gouttiere doit rentrer dans la colonne, pas s'y ajouter : la feuille
241
+ Auriga remet `box-sizing` a `content-box`, et la section debordait de 24px
242
+ de chaque cote au lieu de s'en ecarter. */
243
+ box-sizing: border-box;
244
+ padding-inline: 32px;
245
+ }
246
+
247
+ .hh_head {
248
+ display: flex;
249
+ align-items: flex-start;
250
+ justify-content: space-between;
251
+ gap: 16px;
252
+ }
253
+
254
+ .hh_title {
255
+ font-size: 18px;
256
+ font-weight: 800;
257
+ }
258
+
259
+ .hh_count {
260
+ margin-top: 2px;
261
+ color: var(--medium);
262
+ font-size: 14px;
263
+ }
264
+
265
+ .hh_source {
266
+ color: var(--medium);
267
+ font-size: 14px;
268
+ }
269
+
270
+ .hh_year {
271
+ display: flex;
272
+ flex-direction: column;
273
+ gap: 8px;
274
+ }
275
+
276
+ .hh_year_label {
277
+ margin-top: 12px;
278
+ color: var(--medium);
279
+ font-size: 14px;
280
+ font-weight: 700;
281
+ }
282
+
283
+ .hh_card {
284
+ border: 1px solid var(--light);
285
+ border-radius: 12px;
286
+ background: var(--white);
287
+ overflow: hidden;
288
+ cursor: pointer;
289
+ transition: border-color 0.15s linear, box-shadow 0.15s linear;
290
+ }
291
+
292
+ /* Un avis qui repond a la recherche se repere avant d'etre lu. */
293
+ .hh_card.is_matched {
294
+ border-color: color-mix(in srgb, var(--primary) 45%, var(--light));
295
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--primary) 20%, transparent);
296
+ }
297
+
298
+ .hh_match_tag {
299
+ padding: 2px 8px;
300
+ border-radius: 999px;
301
+ background: color-mix(in srgb, var(--primary) 12%, transparent);
302
+ color: var(--primary);
303
+ font-size: 11px;
304
+ font-weight: 700;
305
+ }
306
+
307
+ .hh_match_note {
308
+ display: flex;
309
+ align-items: center;
310
+ gap: 6px;
311
+ padding: 8px 12px;
312
+ border-radius: 8px;
313
+ background: color-mix(in srgb, var(--primary) 6%, transparent);
314
+ color: var(--dark);
315
+ font-size: 13px;
316
+ }
317
+
318
+ .hh_card:hover {
319
+ border-color: color-mix(in srgb, var(--primary) 35%, var(--light));
320
+ box-shadow: var(--medium-shadow);
321
+ }
322
+
323
+ /* Bandeau de carte : le type a gauche, la date et la reference a droite. */
324
+ .hh_card_head {
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: space-between;
328
+ gap: 16px;
329
+ padding: 10px 16px;
330
+ background: color-mix(in srgb, var(--primary) 4%, transparent);
331
+ border-bottom: 1px solid var(--light);
332
+ }
333
+
334
+ .hh_type {
335
+ display: flex;
336
+ flex-wrap: wrap;
337
+ align-items: center;
338
+ gap: 8px;
339
+ color: var(--primary);
340
+ font-size: 14px;
341
+ font-weight: 700;
342
+ }
343
+
344
+ .hh_type_icon {
345
+ display: inline-flex;
346
+ align-items: center;
347
+ justify-content: center;
348
+ width: 24px;
349
+ height: 24px;
350
+ border-radius: 999px;
351
+ background: color-mix(in srgb, var(--primary) 12%, transparent);
352
+ }
353
+
354
+ .hh_meta {
355
+ display: flex;
356
+ align-items: center;
357
+ gap: 10px;
358
+ color: var(--medium);
359
+ font-size: 13px;
360
+ }
361
+
362
+ .hh_ref {
363
+ padding: 2px 8px;
364
+ border-radius: 4px;
365
+ background: var(--lightest);
366
+ color: var(--dark);
367
+ font-family: "Courier New", monospace;
368
+ font-size: 12px;
369
+ }
370
+
371
+ /* Le PDF s'annonce au survol : pastille rouge et infobulle sous le bouton,
372
+ comme les autres actions d'icone du hub. */
373
+ .hh_link {
374
+ position: relative;
375
+ display: inline-flex;
376
+ align-items: center;
377
+ justify-content: center;
378
+ width: 28px;
379
+ height: 28px;
380
+ border-radius: 999px;
381
+ color: var(--medium);
382
+ transition: background-color 0.15s linear, color 0.15s linear;
383
+ }
384
+
385
+ .hh_link:hover {
386
+ background: rgba(239, 68, 68, 0.12);
387
+ color: var(--system-alert);
388
+ }
389
+
390
+ .hh_link::after {
391
+ content: attr(data-tip);
392
+ position: absolute;
393
+ top: calc(100% + 8px);
394
+ right: 0;
395
+ z-index: 5;
396
+ padding: 6px 10px;
397
+ border-radius: 6px;
398
+ background: var(--darkest);
399
+ color: var(--white);
400
+ font-size: 13px;
401
+ font-weight: 500;
402
+ white-space: nowrap;
403
+ opacity: 0;
404
+ pointer-events: none;
405
+ transition: opacity 0.15s linear;
406
+ }
407
+
408
+ .hh_link:hover::after {
409
+ opacity: 1;
410
+ }
411
+
412
+ .hh_card_body {
413
+ display: flex;
414
+ flex-direction: column;
415
+ gap: 8px;
416
+ padding: 16px;
417
+ }
418
+
419
+ /* A defaut de libelle court, c'est l'indication reglementaire qui sert de
420
+ titre : plusieurs paragraphes, a borner comme l'amorce. */
421
+ .hh_card_title {
422
+ display: -webkit-box;
423
+ -webkit-line-clamp: 2;
424
+ -webkit-box-orient: vertical;
425
+ overflow: hidden;
426
+ font-size: 16px;
427
+ font-weight: 800;
428
+ line-height: 1.35;
429
+ }
430
+
431
+ /* L'avis complet vit dans le tiroir : la carte n'en montre que l'amorce. */
432
+ .hh_excerpt {
433
+ display: -webkit-box;
434
+ -webkit-line-clamp: 2;
435
+ -webkit-box-orient: vertical;
436
+ overflow: hidden;
437
+ color: var(--dark);
438
+ font-size: 14px;
439
+ line-height: 1.5;
440
+ }
441
+
442
+ .hh_status {
443
+ align-self: flex-start;
444
+ padding: 2px 8px;
445
+ border-radius: 4px;
446
+ font-size: 13px;
447
+ font-weight: 600;
448
+ }
449
+
450
+ .hh_status.is_good {
451
+ background: rgba(34, 197, 94, 0.14);
452
+ color: var(--system-success-hover);
453
+ }
454
+
455
+ .hh_status.is_bad {
456
+ background: rgba(239, 68, 68, 0.12);
457
+ color: var(--system-alert-hover);
458
+ }
459
+
460
+ .hh_status.is_neutral {
461
+ background: var(--lightest);
462
+ color: var(--dark);
463
+ }
464
+
465
+ .hh_more {
466
+ color: var(--primary);
467
+ font-size: 14px;
468
+ font-weight: 600;
469
+ }
470
+
471
+ .hh_load {
472
+ align-self: center;
473
+ margin-top: 8px;
474
+ padding: 8px 16px;
475
+ border: 1px solid var(--light);
476
+ border-radius: 999px;
477
+ background: var(--white);
478
+ color: var(--primary);
479
+ font-size: 14px;
480
+ font-weight: 600;
481
+ cursor: pointer;
482
+ }
483
+
484
+ .hh_load:hover {
485
+ background: var(--lightest);
486
+ }
487
+ </style>