@tekkare/romulus 0.1.251 → 0.1.253

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 (267) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/utils/projects.d.ts +8 -0
  3. package/dist/runtime/utils/projects.js +8 -5
  4. package/layer/app/app.config.ts +6 -0
  5. package/layer/app/components/OipPubmedSummary.vue +223 -0
  6. package/layer/app/components/OipResourceDrawer.vue +331 -0
  7. package/layer/app/components/OipResourceDrawerLink.vue +40 -0
  8. package/layer/app/components/OipResourceField.vue +41 -0
  9. package/layer/app/components/OipResourceGenericCard.vue +54 -0
  10. package/layer/app/components/OipResourceSource.vue +50 -0
  11. package/layer/app/components/OipResourceTags.vue +65 -0
  12. package/layer/app/components/OipResourceView.vue +107 -0
  13. package/layer/app/components/oip-drugs/fiches/AssociationFiche.vue +775 -0
  14. package/layer/app/components/oip-drugs/fiches/BrandFiche.vue +1111 -0
  15. package/layer/app/components/oip-drugs/fiches/IndicationFiche.vue +182 -0
  16. package/layer/app/components/oip-drugs/fiches/JoFiche.vue +101 -0
  17. package/layer/app/components/oip-drugs/fiches/PndsFiche.vue +279 -0
  18. package/layer/app/components/oip-drugs/heart/JoCard.vue +314 -0
  19. package/layer/app/components/oip-drugs/heart/TextHighlight.vue +326 -0
  20. package/layer/app/components/oip-drugs/heart/ansm/BrandCard.vue +187 -0
  21. package/layer/app/components/oip-drugs/heart/ansm/CisDetail.vue +363 -0
  22. package/layer/app/components/oip-drugs/heart/ansm/CisTable.vue +167 -0
  23. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyCard.vue +373 -0
  24. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyFiche.vue +372 -0
  25. package/layer/app/components/oip-drugs/heart/associations/TranscriptionCard.vue +55 -0
  26. package/layer/app/components/oip-drugs/heart/elastic/SourceContainerElastic.vue +103 -0
  27. package/layer/app/components/oip-drugs/heart/france/BrandGroupCard.vue +450 -0
  28. package/layer/app/components/oip-drugs/heart/mitm/Card.vue +199 -0
  29. package/layer/app/components/oip-drugs/heart/mitm/Source.vue +23 -0
  30. package/layer/app/components/oip-drugs/heart/mitm/details/Overview.vue +381 -0
  31. package/layer/app/components/oip-drugs/heart/put/Card.vue +212 -0
  32. package/layer/app/components/oip-drugs/heart/results/AccesCard.vue +696 -0
  33. package/layer/app/components/oip-drugs/heart/results/AlertCard.vue +342 -0
  34. package/layer/app/components/oip-drugs/heart/results/AsmrCursor.vue +229 -0
  35. package/layer/app/components/oip-drugs/heart/results/EvalCard.vue +370 -0
  36. package/layer/app/components/oip-drugs/heart/results/IndicCard.vue +743 -0
  37. package/layer/app/components/oip-drugs/heart/results/MatchExplain.vue +243 -0
  38. package/layer/app/components/oip-drugs/heart/results/PrefRows.vue +105 -0
  39. package/layer/app/components/oip-drugs/heart/results/details/HasHistory.vue +487 -0
  40. package/layer/app/components/oip-drugs/heart/results/details/InternationalCountryCard.vue +461 -0
  41. package/layer/app/components/oip-drugs/heart/results/details/InternationalOverview.vue +200 -0
  42. package/layer/app/components/oip-drugs/heart/results/details/InternationalPriceDrawer.vue +647 -0
  43. package/layer/app/components/oip-drugs/heart/results/details/Monograph.vue +128 -0
  44. package/layer/app/components/oip-drugs/heart/results/details/OpinionDrawer.vue +535 -0
  45. package/layer/app/components/oip-drugs/heart/results/details/PresentationDrawer.vue +738 -0
  46. package/layer/app/components/oip-drugs/heart/results/details/Price.vue +227 -0
  47. package/layer/app/components/oip-drugs/heart/results/details/PriceOverview.vue +1261 -0
  48. package/layer/app/components/oip-drugs/heart/results/details/RecordDrawer.vue +494 -0
  49. package/layer/app/components/oip-drugs/heart/results/details/RecordList.vue +426 -0
  50. package/layer/app/components/oip-drugs/heart/results/les/Card.vue +340 -0
  51. package/layer/app/components/oip-drugs/heart/results/les/Drawer.vue +67 -0
  52. package/layer/app/components/oip-drugs/heart/results/les/IndicationDetail.vue +149 -0
  53. package/layer/app/components/oip-drugs/heart/results/les/Source.vue +20 -0
  54. package/layer/app/components/oip-drugs/heart/results/les/SourceTag.vue +39 -0
  55. package/layer/app/components/oip-drugs/heart/searchPage/DynamicSource.vue +29 -0
  56. package/layer/app/components/oip-drugs/medicam/market/Chart.vue +121 -0
  57. package/layer/app/components/oip-drugs/outpatient/ProductEvolution.vue +236 -0
  58. package/layer/app/components/oip-drugs/resources/Ansm.vue +75 -0
  59. package/layer/app/components/oip-drugs/resources/Association.vue +30 -0
  60. package/layer/app/components/oip-drugs/resources/Brand.vue +65 -0
  61. package/layer/app/components/oip-drugs/resources/Contribution.vue +216 -0
  62. package/layer/app/components/oip-drugs/resources/Hospital.vue +151 -0
  63. package/layer/app/components/oip-drugs/resources/Indication.vue +54 -0
  64. package/layer/app/components/oip-drugs/resources/Jo.vue +57 -0
  65. package/layer/app/components/oip-drugs/resources/Medicam.vue +140 -0
  66. package/layer/app/components/oip-drugs/resources/Mitm.vue +84 -0
  67. package/layer/app/components/oip-drugs/resources/NationalAssembly.vue +93 -0
  68. package/layer/app/components/oip-drugs/resources/Outpatient.vue +97 -0
  69. package/layer/app/components/oip-drugs/resources/Pnds.vue +24 -0
  70. package/layer/app/components/oip-drugs/resources/Price.vue +106 -0
  71. package/layer/app/components/oip-drugs/resources/Put.vue +143 -0
  72. package/layer/app/components/oip-science/appel-a-projet/Cards.vue +162 -0
  73. package/layer/app/components/oip-science/clinical-trials/Cards.vue +382 -0
  74. package/layer/app/components/oip-science/clinical-trials/CardsGlobal.vue +273 -0
  75. package/layer/app/components/oip-science/clinical-trials/LinkedList.vue +366 -0
  76. package/layer/app/components/oip-science/clinical-trials/PairBadge.vue +25 -0
  77. package/layer/app/components/oip-science/clinical-trials/PairBody.vue +182 -0
  78. package/layer/app/components/oip-science/clinical-trials/SitesDrawer.vue +95 -0
  79. package/layer/app/components/oip-science/common/Form.vue +73 -0
  80. package/layer/app/components/oip-science/common/MatchAvgTag.vue +283 -0
  81. package/layer/app/components/oip-science/common/NoData.vue +16 -0
  82. package/layer/app/components/oip-science/common/ScoreBadge.vue +35 -0
  83. package/layer/app/components/oip-science/common/Source.vue +31 -0
  84. package/layer/app/components/oip-science/common/TableTags.vue +279 -0
  85. package/layer/app/components/oip-science/common/Timeline.vue +104 -0
  86. package/layer/app/components/oip-science/common/ToolTips.vue +89 -0
  87. package/layer/app/components/oip-science/cross/publicationPubmed.vue +484 -0
  88. package/layer/app/components/oip-science/crossData/CrossDataLocations.vue +71 -0
  89. package/layer/app/components/oip-science/ctgov/Investigators.vue +148 -0
  90. package/layer/app/components/oip-science/ctgov/Locations.vue +102 -0
  91. package/layer/app/components/oip-science/ctgov/Publications.vue +52 -0
  92. package/layer/app/components/oip-science/ctgov/Sponsors.vue +66 -0
  93. package/layer/app/components/oip-science/ctgov/StudyDescription.vue +1136 -0
  94. package/layer/app/components/oip-science/ctgov-centers/ClinicalTrials.vue +40 -0
  95. package/layer/app/components/oip-science/ctgov-centers/Publications.vue +50 -0
  96. package/layer/app/components/oip-science/ctgov-investigators/ClinicalTrials.vue +43 -0
  97. package/layer/app/components/oip-science/ctgov-investigators/Informations.vue +171 -0
  98. package/layer/app/components/oip-science/ctgov-investigators/Publications.vue +44 -0
  99. package/layer/app/components/oip-science/elastic/DateRangeFilter.vue +209 -0
  100. package/layer/app/components/oip-science/elastic/Filter.vue +719 -0
  101. package/layer/app/components/oip-science/elastic/MatchExplain.vue +152 -0
  102. package/layer/app/components/oip-science/establishments/Cards.vue +183 -0
  103. package/layer/app/components/oip-science/establishments/ClinicalTrials.vue +62 -0
  104. package/layer/app/components/oip-science/establishments/Hcps.vue +227 -0
  105. package/layer/app/components/oip-science/establishments/Informations.vue +263 -0
  106. package/layer/app/components/oip-science/establishments/LinkedList.vue +153 -0
  107. package/layer/app/components/oip-science/establishments/MatchedLabels.vue +194 -0
  108. package/layer/app/components/oip-science/establishments/PublicFinance.vue +62 -0
  109. package/layer/app/components/oip-science/euct/Investigators.vue +125 -0
  110. package/layer/app/components/oip-science/euct/Locations.vue +102 -0
  111. package/layer/app/components/oip-science/euct/Products.vue +101 -0
  112. package/layer/app/components/oip-science/euct/Publications.vue +89 -0
  113. package/layer/app/components/oip-science/euct/Sponsors.vue +86 -0
  114. package/layer/app/components/oip-science/euct/StudyDescription.vue +1075 -0
  115. package/layer/app/components/oip-science/euct-centers/ClinicalTrials.vue +40 -0
  116. package/layer/app/components/oip-science/euct-centers/Publications.vue +55 -0
  117. package/layer/app/components/oip-science/euct-investigators/ClinicalTrials.vue +41 -0
  118. package/layer/app/components/oip-science/euct-investigators/Informations.vue +171 -0
  119. package/layer/app/components/oip-science/euct-investigators/Publications.vue +51 -0
  120. package/layer/app/components/oip-science/hcps/AppelsAProjet.vue +62 -0
  121. package/layer/app/components/oip-science/hcps/Cards.vue +237 -0
  122. package/layer/app/components/oip-science/hcps/ClinicalTrials.vue +78 -0
  123. package/layer/app/components/oip-science/hcps/Informations.vue +296 -0
  124. package/layer/app/components/oip-science/hcps/LinkedList.vue +153 -0
  125. package/layer/app/components/oip-science/hcps/MatchedLabels.vue +207 -0
  126. package/layer/app/components/oip-science/hcps/Pubmed.vue +128 -0
  127. package/layer/app/components/oip-science/local/SliderTabs.vue +99 -0
  128. package/layer/app/components/oip-science/local/SliderTabsFull.vue +135 -0
  129. package/layer/app/components/oip-science/pubmed/Authors.vue +128 -0
  130. package/layer/app/components/oip-science/pubmed/Cards.vue +417 -0
  131. package/layer/app/components/oip-science/pubmed/ClinicalTrials.vue +71 -0
  132. package/layer/app/components/oip-science/resources/Phrc.vue +39 -0
  133. package/layer/app/composables/useOipChapterScroll.ts +43 -0
  134. package/layer/app/composables/useOipResourceHooks.ts +27 -0
  135. package/layer/app/composables/useOipResourceNav.ts +66 -0
  136. package/layer/app/composables/useOipResourceViews.ts +117 -0
  137. package/layer/app/composables/useOipResources.ts +83 -0
  138. package/layer/app/oip-drugs/PORTING.md +120 -0
  139. package/layer/app/oip-drugs/composables/useAssociationDetail.ts +220 -0
  140. package/layer/app/oip-drugs/composables/useAssociationExport.ts +63 -0
  141. package/layer/app/oip-drugs/composables/useAssociationLookup.ts +105 -0
  142. package/layer/app/oip-drugs/composables/useCardPrefOptions.ts +201 -0
  143. package/layer/app/oip-drugs/composables/useCurrency.ts +121 -0
  144. package/layer/app/oip-drugs/composables/useDirectusSources.ts +224 -0
  145. package/layer/app/oip-drugs/composables/useHeartBrandMatches.ts +112 -0
  146. package/layer/app/oip-drugs/composables/useHeartDetailSearches.ts +479 -0
  147. package/layer/app/oip-drugs/composables/useHeartIntlApprovals.ts +661 -0
  148. package/layer/app/oip-drugs/composables/useHeartLesIndication.ts +168 -0
  149. package/layer/app/oip-drugs/composables/useHeartMatchExplain.ts +231 -0
  150. package/layer/app/oip-drugs/composables/useHeartReports.ts +239 -0
  151. package/layer/app/oip-drugs/composables/useHeartSearchFields.ts +233 -0
  152. package/layer/app/oip-drugs/composables/useMergeByKey.ts +23 -0
  153. package/layer/app/oip-drugs/composables/useOipDrugsElastic.ts +621 -0
  154. package/layer/app/oip-drugs/composables/usePageEmpty.ts +58 -0
  155. package/layer/app/oip-drugs/composables/v-useSalesSource.ts +64 -0
  156. package/layer/app/oip-drugs/drugs.css +500 -0
  157. package/layer/app/oip-drugs/esIndices.ts +322 -0
  158. package/layer/app/oip-drugs/kinds.ts +264 -0
  159. package/layer/app/oip-drugs/locales/en.json +1571 -0
  160. package/layer/app/oip-drugs/locales/fr.json +1571 -0
  161. package/layer/app/oip-drugs/monographs/blincyto-has.vue +289 -0
  162. package/layer/app/oip-drugs/monographs/darzalex-has.vue +276 -0
  163. package/layer/app/oip-drugs/monographs/dupixent-has.vue +216 -0
  164. package/layer/app/oip-drugs/monographs/enhertu-has.vue +280 -0
  165. package/layer/app/oip-drugs/monographs/has-sein-2023.vue +1649 -0
  166. package/layer/app/oip-drugs/monographs/ibrance-has.vue +277 -0
  167. package/layer/app/oip-drugs/monographs/imfinzi-has.vue +293 -0
  168. package/layer/app/oip-drugs/monographs/keytruda-has.vue +334 -0
  169. package/layer/app/oip-drugs/monographs/kisqali-has.vue +201 -0
  170. package/layer/app/oip-drugs/monographs/opdivo-has.vue +231 -0
  171. package/layer/app/oip-drugs/monographs/ozempic-has.vue +186 -0
  172. package/layer/app/oip-drugs/monographs/tecentriq-has.vue +312 -0
  173. package/layer/app/oip-drugs/monographs/yescarta-has.vue +272 -0
  174. package/layer/app/oip-drugs/types/elastic.ts +113 -0
  175. package/layer/app/oip-drugs/types/es/agencies.ts +152 -0
  176. package/layer/app/oip-drugs/types/es/directory.ts +54 -0
  177. package/layer/app/oip-drugs/types/es/docByIndex.ts +151 -0
  178. package/layer/app/oip-drugs/types/es/openmedic.ts +67 -0
  179. package/layer/app/oip-drugs/types/es/outpatient.ts +71 -0
  180. package/layer/app/oip-drugs/types/es/portal.ts +130 -0
  181. package/layer/app/oip-drugs/types/es/retail.ts +107 -0
  182. package/layer/app/oip-drugs/types/es/statistics.ts +29 -0
  183. package/layer/app/oip-drugs/types/heart/CurrencyRate.ts +16 -0
  184. package/layer/app/oip-drugs/types/heart/ansm.ts +106 -0
  185. package/layer/app/oip-drugs/types/heart/tag.ts +29 -0
  186. package/layer/app/oip-drugs/utils/associationFields.ts +118 -0
  187. package/layer/app/oip-drugs/utils/contributions.ts +36 -0
  188. package/layer/app/oip-drugs/utils/countryMapping.ts +387 -0
  189. package/layer/app/oip-drugs/utils/esHighlight.ts +21 -0
  190. package/layer/app/oip-drugs/utils/exportSheets.ts +57 -0
  191. package/layer/app/oip-drugs/utils/heartIntlPrice.ts +228 -0
  192. package/layer/app/oip-drugs/utils/heartStyleAndFormat.ts +262 -0
  193. package/layer/app/oip-drugs/utils/heartStyleAnsm.ts +71 -0
  194. package/layer/app/oip-drugs/utils/latestDate.ts +31 -0
  195. package/layer/app/oip-drugs/utils/p-heartCardTags.ts +53 -0
  196. package/layer/app/oip-drugs/utils/priceExport.ts +269 -0
  197. package/layer/app/oip-drugs/utils/q-hasRatings.ts +43 -0
  198. package/layer/app/oip-drugs/utils/s-openCard.ts +20 -0
  199. package/layer/app/oip-drugs/utils/sourceUuids.ts +155 -0
  200. package/layer/app/oip-drugs/utils/styleAndFormat.ts +40 -0
  201. package/layer/app/oip-drugs/utils/v-salesSeries.ts +156 -0
  202. package/layer/app/oip-drugs/utils/v-scopeTags.ts +23 -0
  203. package/layer/app/oip-science/PORTING.md +93 -0
  204. package/layer/app/oip-science/assets/smartSelection.json +428 -0
  205. package/layer/app/oip-science/composables/useAuthorMatching.ts +342 -0
  206. package/layer/app/oip-science/composables/useCTCenter.ts +107 -0
  207. package/layer/app/oip-science/composables/useCTInvestigators.ts +107 -0
  208. package/layer/app/oip-science/composables/useEstablishmentCrossData.ts +139 -0
  209. package/layer/app/oip-science/composables/useEstablishmentMatching.ts +176 -0
  210. package/layer/app/oip-science/composables/useEuct.ts +418 -0
  211. package/layer/app/oip-science/composables/useEuctCenter.ts +107 -0
  212. package/layer/app/oip-science/composables/useEuctInvestigators.ts +107 -0
  213. package/layer/app/oip-science/composables/useGlobalClinicalTrials.ts +624 -0
  214. package/layer/app/oip-science/composables/useLinkedTrials.ts +83 -0
  215. package/layer/app/oip-science/composables/useOipElastic.ts +658 -0
  216. package/layer/app/oip-science/composables/useOipSearchExplain.ts +166 -0
  217. package/layer/app/oip-science/composables/usePubmed.ts +487 -0
  218. package/layer/app/oip-science/composables/useSearchFields.ts +153 -0
  219. package/layer/app/oip-science/indexConfig.json +794 -0
  220. package/layer/app/oip-science/locales/en.json +483 -0
  221. package/layer/app/oip-science/locales/fr.json +483 -0
  222. package/layer/app/oip-science/science.css +1182 -0
  223. package/layer/app/oip-science/utils/a-openCard.ts +23 -0
  224. package/layer/app/oip-science/utils/b-openCard.ts +35 -0
  225. package/layer/app/oip-science/utils/c-filterContext.ts +81 -0
  226. package/layer/app/oip-science/utils/f-types.ts +241 -0
  227. package/layer/app/oip-science/utils/frDiseaseTerms.ts +269 -0
  228. package/layer/app/oip-science/utils/matchMethod.ts +53 -0
  229. package/layer/app/oip-science/utils/sliderTabs.ts +13 -0
  230. package/layer/app/oip-science/utils/sources.ts +15 -0
  231. package/layer/app/oip-science/utils/styleAndFormat.ts +608 -0
  232. package/layer/app/oip-science/utils/toPresets.ts +7 -0
  233. package/layer/app/oip-science/utils/valueTable.ts +126 -0
  234. package/layer/app/plugins/oip-science-i18n.ts +17 -0
  235. package/layer/app/utils/oipFicheRoutes.ts +118 -0
  236. package/layer/app/utils/oipResources.ts +440 -0
  237. package/layer/nuxt.config.ts +22 -2
  238. package/layer/public/reports/blincyto-has.html +292 -0
  239. package/layer/public/reports/blincyto-has.pdf +0 -0
  240. package/layer/public/reports/darzalex-has.html +279 -0
  241. package/layer/public/reports/darzalex-has.pdf +0 -0
  242. package/layer/public/reports/dupixent-has.html +217 -0
  243. package/layer/public/reports/dupixent-has.pdf +0 -0
  244. package/layer/public/reports/enhertu-has.html +283 -0
  245. package/layer/public/reports/enhertu-has.pdf +0 -0
  246. package/layer/public/reports/has-sein-2023.html +1639 -0
  247. package/layer/public/reports/ibrance-has.html +280 -0
  248. package/layer/public/reports/ibrance-has.pdf +0 -0
  249. package/layer/public/reports/imfinzi-has.html +297 -0
  250. package/layer/public/reports/imfinzi-has.pdf +0 -0
  251. package/layer/public/reports/keytruda-has.html +337 -0
  252. package/layer/public/reports/keytruda-has.pdf +0 -0
  253. package/layer/public/reports/kisqali-has.html +202 -0
  254. package/layer/public/reports/kisqali-has.pdf +0 -0
  255. package/layer/public/reports/opdivo-has.html +232 -0
  256. package/layer/public/reports/opdivo-has.pdf +0 -0
  257. package/layer/public/reports/ozempic-has.html +188 -0
  258. package/layer/public/reports/ozempic-has.pdf +0 -0
  259. package/layer/public/reports/tecentriq-has.html +315 -0
  260. package/layer/public/reports/tecentriq-has.pdf +0 -0
  261. package/layer/public/reports/yescarta-has.html +275 -0
  262. package/layer/public/reports/yescarta-has.pdf +0 -0
  263. package/layer/server/api/oip/es.post.ts +99 -0
  264. package/layer/server/api/oip/pdf.get.ts +70 -0
  265. package/layer/server/api/oip/reports.get.ts +98 -0
  266. package/layer/server/api/oip/resource-types.get.ts +22 -0
  267. package/package.json +1 -1
@@ -0,0 +1,381 @@
1
+ <!--
2
+ Onglet MITM d'une specialite.
3
+
4
+ L'index MITM tient, pour un code CIS, l'integralite des avis de la Commission
5
+ de la transparence : une quarantaine de SMR et autant d'ASMR, chacun avec son
6
+ texte complet. En trois tableaux empiles, on lisait des colonnes tronquees
7
+ sans jamais voir un avis en entier. Ici, l'identite du produit et ses deux
8
+ derniers niveaux se lisent en tete, puis chaque avis devient une carte,
9
+ SMR et ASMR reunis par code, le texte integral s'ouvrant dans le tiroir.
10
+ -->
11
+ <script setup lang="ts">
12
+ import { HEART_SOURCE_UUIDS } from "../../../../../oip-drugs/utils/sourceUuids";
13
+ import useDirectusSources from "../../../../../oip-drugs/composables/useDirectusSources";
14
+
15
+ // Libelle de source : Directus, comme le panneau « Sources » du rail.
16
+ const { load: loadSourceMeta, metaByUuid } = useDirectusSources();
17
+ loadSourceMeta([HEART_SOURCE_UUIDS.HAS]);
18
+ const props = defineProps<{
19
+ /** Le document MITM du CIS choisi (_source). */
20
+ cis: any;
21
+ }>();
22
+
23
+ const emit = defineEmits<{ (event: "select", record: any): void }>();
24
+
25
+ const { locale } = useI18n();
26
+ const hasSourceName = computed(
27
+ () => metaByUuid(HEART_SOURCE_UUIDS.HAS, locale.value === "en" ? "en" : "fr").name,
28
+ );
29
+
30
+ /** `20260708` dans l'index, une date ailleurs. */
31
+ function isoDate(raw: string | null | undefined) {
32
+ if (!raw) return null;
33
+ const value = String(raw);
34
+ if (/^\d{8}$/.test(value)) {
35
+ return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`;
36
+ }
37
+ return value;
38
+ }
39
+
40
+ /** Les libelles d'avis portent des `<br>` : on les rend au lecteur, pas au DOM. */
41
+ function plain(text: string | null | undefined) {
42
+ if (!text) return "";
43
+ return text
44
+ .replace(/<br\s*\/?>/gi, "\n")
45
+ .replace(/<[^>]+>/g, "")
46
+ .trim();
47
+ }
48
+
49
+ /** Important, Modere, Faible, Insuffisant. */
50
+ function smrTone(value: string) {
51
+ const v = (value || "").toLowerCase();
52
+ const bad = v.includes("insuffisant");
53
+ const good = v.includes("important");
54
+ // Un avis peut etre important sur une indication et insuffisant sur une autre.
55
+ if (bad && good) return "neutral" as const;
56
+ if (bad) return "bad" as const;
57
+ if (good) return "good" as const;
58
+ return "neutral" as const;
59
+ }
60
+
61
+ /** ASMR I a V : plus le niveau est haut, plus le progres est net. */
62
+ function asmrTone(value: string) {
63
+ const level = (value || "").trim().match(/^(I{1,3}|IV|V)\b/)?.[1];
64
+ if (level === "I" || level === "II") return "good" as const;
65
+ if (level === "III") return "post" as const;
66
+ if (level === "IV") return "warn" as const;
67
+ return "neutral" as const;
68
+ }
69
+
70
+ // Premier niveau renseigne : l'index met des lignes de remplissage nulles en
71
+ // tete de `last_smr`/`last_asmr`, le KPI affichait un tiret a tort.
72
+ const lastSmr = computed(() => (props.cis?.last_smr ?? []).find((s: any) => s?.value) ?? null);
73
+ const lastAsmr = computed(() => (props.cis?.last_asmr ?? []).find((s: any) => s?.value) ?? null);
74
+
75
+ const atcLabel = computed(() =>
76
+ locale.value === "en"
77
+ ? props.cis?.atc5_english_code_label
78
+ : props.cis?.atc5_french_code_label,
79
+ );
80
+
81
+ /**
82
+ * Les avis, un par code HAS.
83
+ *
84
+ * SMR et ASMR sont deux listes separees dans l'index alors qu'ils sont rendus
85
+ * dans le meme avis : on les rassemble par `has_code`, sinon chaque decision
86
+ * apparaissait deux fois.
87
+ */
88
+ const opinions = computed(() => {
89
+ const byCode = new Map<string, any>();
90
+
91
+ const push = (entry: any, kind: "smr" | "asmr") => {
92
+ // L'index porte des lignes de remplissage entierement nulles : sans ce
93
+ // garde, chacune devenait un avis vide dans la liste.
94
+ if (!entry?.has_code && !entry?.has_date && !entry?.label && !entry?.value) return;
95
+ const code = entry.has_code || `${kind}-${entry.has_date}`;
96
+ const found = byCode.get(code) ?? {
97
+ code: entry.has_code || null,
98
+ date: isoDate(entry.has_date),
99
+ reason: entry.has_reason || null,
100
+ };
101
+ // Un meme avis peut rendre plusieurs niveaux (important ici, insuffisant
102
+ // la) : on les cumule au lieu de garder le dernier.
103
+ const previous = found[kind];
104
+ found[kind] = {
105
+ value: [previous?.value, entry.value].filter(Boolean).join(", ") || null,
106
+ label: [previous?.label, plain(entry.label)].filter(Boolean).join("\n\n"),
107
+ };
108
+ byCode.set(code, found);
109
+ };
110
+
111
+ (props.cis?.smr ?? []).forEach((entry: any) => push(entry, "smr"));
112
+ (props.cis?.asmr ?? []).forEach((entry: any) => push(entry, "asmr"));
113
+
114
+ // `has_date` peut etre nul : NaN dans le comparateur rendait l'ordre
115
+ // aleatoire, on classe ces avis en fin de liste.
116
+ const time = (d: string | null) => (d ? new Date(d).getTime() : 0);
117
+ return [...byCode.values()].sort((a, b) => time(b.date) - time(a.date));
118
+ });
119
+
120
+ const records = computed(() =>
121
+ opinions.value.map((opinion) => ({
122
+ key: opinion.code || opinion.date,
123
+ typeLabel:
124
+ opinion.reason || (locale.value === "en" ? "Transparency opinion" : "Avis de transparence"),
125
+ icon: "FileText",
126
+ date: opinion.date,
127
+ ref: opinion.code,
128
+ title:
129
+ opinion.smr?.label ||
130
+ opinion.asmr?.label ||
131
+ (locale.value === "en" ? "Transparency opinion" : "Avis de transparence"),
132
+ excerpt: opinion.smr?.label ? opinion.asmr?.label : null,
133
+ status: opinion.smr?.value ? `SMR ${opinion.smr.value}` : null,
134
+ statusTone: smrTone(opinion.smr?.value),
135
+ badge: opinion.asmr?.value ? `ASMR ${opinion.asmr.value}` : null,
136
+ badgeTone: asmrTone(opinion.asmr?.value),
137
+ sourceLabel: hasSourceName.value,
138
+ sections: [
139
+ {
140
+ key: "smr",
141
+ label: locale.value === "en" ? "Actual benefit (SMR)" : "Service médical rendu (SMR)",
142
+ body: opinion.smr?.label ?? "",
143
+ },
144
+ {
145
+ key: "asmr",
146
+ label:
147
+ locale.value === "en"
148
+ ? "Improvement in actual benefit (ASMR)"
149
+ : "Amélioration du service médical rendu (ASMR)",
150
+ body: opinion.asmr?.label ?? "",
151
+ },
152
+ ],
153
+ })),
154
+ );
155
+
156
+ const attached = computed(() =>
157
+ (props.cis?.molecule ?? []).filter((row: any) => row?.brand || row?.drug_label || row?.cis_code),
158
+ );
159
+ </script>
160
+
161
+ <template>
162
+ <section class="oip-drugs mi">
163
+ <p class="mi_scope">
164
+ <RmIcon name="Info" :size="14" />
165
+ {{
166
+ locale === "en"
167
+ ? `Opinions cover the presentation ${cis.cis_label}.`
168
+ : `Les avis portent sur la présentation ${cis.cis_label}.`
169
+ }}
170
+ <a
171
+ v-if="cis.url_link"
172
+ class="mi_link"
173
+ :href="cis.url_link"
174
+ target="_blank"
175
+ rel="noopener noreferrer"
176
+ >
177
+ {{ locale === "en" ? "Public drug database" : "Base publique du médicament" }} →
178
+ </a>
179
+ </p>
180
+
181
+ <!-- Meme bandeau de synthese que l'onglet Prix : les niveaux en vigueur et
182
+ l'ampleur de l'historique, avant d'entrer dans le detail. -->
183
+ <div class="mi_kpis">
184
+ <div class="mi_kpi">
185
+ <p class="mi_kpi_value" :class="`is_${smrTone(lastSmr?.value)}`">
186
+ {{ lastSmr?.value || "-" }}
187
+ </p>
188
+ <p class="mi_kpi_label">{{ locale === "en" ? "Current SMR" : "SMR en vigueur" }}</p>
189
+ </div>
190
+
191
+ <div class="mi_kpi">
192
+ <p class="mi_kpi_value" :class="`is_${asmrTone(lastAsmr?.value)}`">
193
+ {{ lastAsmr?.value || "-" }}
194
+ </p>
195
+ <p class="mi_kpi_label">{{ locale === "en" ? "Current ASMR" : "ASMR en vigueur" }}</p>
196
+ </div>
197
+
198
+ <div class="mi_kpi">
199
+ <p class="mi_kpi_value">{{ records.length }}</p>
200
+ <p class="mi_kpi_label">
201
+ {{ locale === "en" ? "Opinions recorded" : "Avis recensés" }}
202
+ </p>
203
+ </div>
204
+
205
+ <div class="mi_kpi">
206
+ <p class="mi_kpi_value">
207
+ <RmDate
208
+ v-if="isoDate(lastSmr?.has_date || lastAsmr?.has_date)"
209
+ :date="isoDate(lastSmr?.has_date || lastAsmr?.has_date)!"
210
+ :lang="locale"
211
+ />
212
+ <template v-else>-</template>
213
+ </p>
214
+ <p class="mi_kpi_label">
215
+ {{ locale === "en" ? "Last assessment" : "Dernière évaluation" }}
216
+ </p>
217
+ </div>
218
+ </div>
219
+
220
+ <OipDrugsHeartResultsDetailsRecordList
221
+ :title="locale === 'en' ? 'Transparency Committee opinions' : 'Avis de la Commission de la transparence'"
222
+ :subtitle="locale === 'en' ? 'opinions recorded for this presentation' : 'avis recensés pour cette présentation'"
223
+ :items="records"
224
+ :empty-label="locale === 'en' ? 'No opinion recorded.' : 'Aucun avis recensé.'"
225
+ @select="emit('select', $event)"
226
+ />
227
+
228
+ <template v-if="attached.length">
229
+ <h3 class="mi_section">
230
+ {{ locale === "en" ? "Products attached to this molecule" : "Produits rattachés à cette molécule" }}
231
+ </h3>
232
+ <ul class="mi_attached">
233
+ <li v-for="row in attached" :key="row.cis_code || row.drug_code">
234
+ <b>{{ row.brand || "-" }}</b>
235
+ <span>{{ row.drug_label }}</span>
236
+ <span class="mi_attached_meta">
237
+ <template v-if="row.cis_code">CIS {{ row.cis_code }}</template>
238
+ <template v-if="row.owner_regroup"> · {{ row.owner_regroup }}</template>
239
+ </span>
240
+ </li>
241
+ </ul>
242
+ </template>
243
+
244
+ <p class="mi_source">
245
+ <OipDrugsHeartMitmSource
246
+ :last-label="$t('oipDrugs.heart.sources.lastUpdate')"
247
+ :last-date="cis?.update_date"
248
+ />
249
+ </p>
250
+ </section>
251
+ </template>
252
+
253
+ <style scoped>
254
+ .mi {
255
+ display: flex;
256
+ flex-direction: column;
257
+ gap: 16px;
258
+ box-sizing: border-box;
259
+ width: 100%;
260
+ }
261
+
262
+ /* La liste d'avis pose deja sa propre gouttiere de 32px ; les autres blocs la
263
+ prennent en marge et non en retrait interne, pour que leur cadre s'aligne sur
264
+ celui des cartes plutot que de le deborder. */
265
+ .mi > :not(.rl) {
266
+ margin-inline: 32px;
267
+ }
268
+
269
+ .mi_scope {
270
+ display: flex;
271
+ flex-wrap: wrap;
272
+ align-items: center;
273
+ gap: 6px;
274
+ color: var(--medium);
275
+ font-size: 13px;
276
+ }
277
+
278
+ .mi_link {
279
+ color: var(--primary);
280
+ font-size: 13px;
281
+ font-weight: 600;
282
+ }
283
+
284
+ /* Bandeau de synthese : meme dessin que celui de l'onglet Prix. */
285
+ .mi_kpis {
286
+ display: grid;
287
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
288
+ border: 1px solid var(--light);
289
+ /* Le filet degrade court d'un bord a l'autre : arrondir le haut le tronquait
290
+ a ses extremites. */
291
+ border-radius: 0 0 12px 12px;
292
+ background: var(--white);
293
+ overflow: hidden;
294
+ box-shadow: var(--small-shadow);
295
+ }
296
+
297
+ .mi_kpis::before {
298
+ content: "";
299
+ grid-column: 1 / -1;
300
+ height: 3px;
301
+ background: linear-gradient(to right, var(--primary), color-mix(in srgb, var(--primary) 35%, var(--white)));
302
+ }
303
+
304
+ .mi_kpi {
305
+ display: flex;
306
+ flex-direction: column;
307
+ align-items: center;
308
+ gap: 6px;
309
+ padding: 20px 16px;
310
+ border-left: 1px solid var(--light);
311
+ text-align: center;
312
+ }
313
+
314
+ .mi_kpi:first-of-type {
315
+ border-left: none;
316
+ }
317
+
318
+ .mi_kpi_value {
319
+ color: var(--primary);
320
+ font-size: 19px;
321
+ font-weight: 800;
322
+ white-space: nowrap;
323
+ }
324
+
325
+ /* Le niveau porte sa propre lecture : favorable, moyen, insuffisant. */
326
+ .mi_kpi_value.is_good {
327
+ color: var(--system-success-hover);
328
+ }
329
+
330
+ .mi_kpi_value.is_bad {
331
+ color: var(--system-alert-hover);
332
+ }
333
+
334
+ .mi_kpi_value.is_warn {
335
+ color: #b45309;
336
+ }
337
+
338
+ .mi_kpi_label {
339
+ color: var(--medium);
340
+ font-size: 11px;
341
+ font-weight: 600;
342
+ letter-spacing: 0.04em;
343
+ text-transform: uppercase;
344
+ white-space: nowrap;
345
+ }
346
+
347
+ .mi_section {
348
+ margin-top: 8px;
349
+ font-size: 16px;
350
+ font-weight: 800;
351
+ }
352
+
353
+ .mi_attached {
354
+ display: flex;
355
+ flex-direction: column;
356
+ gap: 8px;
357
+ list-style: none;
358
+ }
359
+
360
+ .mi_attached li {
361
+ display: flex;
362
+ flex-wrap: wrap;
363
+ align-items: baseline;
364
+ gap: 8px;
365
+ padding: 10px 14px;
366
+ border: 1px solid var(--light);
367
+ border-radius: 12px;
368
+ background: var(--white);
369
+ font-size: 14px;
370
+ }
371
+
372
+ .mi_attached_meta {
373
+ color: var(--medium);
374
+ font-size: 13px;
375
+ }
376
+
377
+ .mi_source {
378
+ color: var(--medium);
379
+ font-size: 13px;
380
+ }
381
+ </style>
@@ -0,0 +1,212 @@
1
+ <!--
2
+ Carte d'un P.U.T. (protocole d'utilisation therapeutique).
3
+
4
+ Meme modele que les cartes de marque, LES, MITM et PNDS (RmResultCard) : une
5
+ specialite, ses documents, son objectif et son indication. Le detail vit dans
6
+ la fiche marque, onglet PUT, ouverte au clic sur la carte entiere.
7
+
8
+ En mode `inDetails` (tete de la fiche), la carte n'est plus cliquable, le
9
+ libelle s'affiche en entier et les documents sont listes.
10
+ -->
11
+ <template>
12
+ <RmResultCard
13
+ class="oip-drugs"
14
+ :clickable="!inDetails"
15
+ :count="documentCount"
16
+ :count-label="countLabel"
17
+ :more-label="inDetails ? '' : locale === 'fr' ? 'Voir le détail' : 'See details'"
18
+ :lang="dateLang"
19
+ :link="detailsHref"
20
+ @click="!inDetails && openDrugsCard(nav, $event, detailsPath)"
21
+ >
22
+ <template #title>
23
+ <OipDrugsHeartTextHighlight :text="product.specialty_dci" :queries="storeSearchBar" />
24
+ </template>
25
+ <template v-if="subtitle" #subtitle>{{ subtitle }}</template>
26
+
27
+ <template #actions>
28
+ <!-- Emplacement libre pour un appelant (la fiche projet y pose son
29
+ bouton « Commenter ») avant le bouton projet : vide partout ailleurs. -->
30
+ <slot name="hubr-actions" />
31
+ <component
32
+ :is="projectButton"
33
+ v-if="projectButton"
34
+ :resource-id="productId"
35
+ type="FrHasPut"
36
+ :label="product.specialty_dci"
37
+ compact
38
+ />
39
+ </template>
40
+
41
+ <!-- Export masque pour l'instant (demande produit), comme sur les autres cartes.
42
+ <template #actions>
43
+ <RmButton v-if="allLinks.length" variant="tertiary" ghost prefix-icon="DownloadSimple" @click.stop.prevent="openPdf(allLinks[0])">Export</RmButton>
44
+ </template>
45
+ -->
46
+
47
+ <template #meta>
48
+ <span v-if="product.authorization_date" class="rm-rcard__label">
49
+ {{ t('oipDrugs.heart.put.card.date') }} ·
50
+ <RmDate :date="product.authorization_date" :lang="dateLang" />
51
+ </span>
52
+ <RmTag v-if="statusLabel" :variant="statusVariant">{{ statusLabel }}</RmTag>
53
+ </template>
54
+
55
+ <p v-if="objectiveLabel" class="rm-rcard__text">
56
+ <strong>{{ t('oipDrugs.heart.put.card.objectives') }} :</strong> {{ objectiveLabel }}
57
+ </p>
58
+ <p v-if="efficacyLabel" class="rm-rcard__desc">
59
+ <strong>{{ t('oipDrugs.heart.put.card.efficacy') }} :</strong> {{ efficacyLabel }}
60
+ </p>
61
+
62
+ <p class="rm-rcard__lead" :class="{ 'rm-rcard__clamp': !inDetails }">
63
+ <OipDrugsHeartTextHighlight :text="fullIndication" :queries="storeSearchBar" />
64
+ </p>
65
+
66
+ <!-- Rangees choisies dans « Personnaliser ». -->
67
+ <OipDrugsHeartResultsPrefRows scope="put" :values="prefValues" />
68
+
69
+ <details v-if="inDetails && allLinks.length > 0" class="put-docs">
70
+ <summary class="put-docs-summary">
71
+ <RmIcon name="CaretRight" :size="16" class="put-docs-caret" />
72
+ <strong>Documents ({{ allLinks.length }})</strong>
73
+ </summary>
74
+ <div class="oip_list gap-8 put-docs-body">
75
+ <RmButton
76
+ v-for="(link, i) in allLinks"
77
+ :key="'doc-' + i"
78
+ variant="secondary"
79
+ size="sm"
80
+ prefix-icon="DownloadSimple"
81
+ @click.stop.prevent="openPdf(link)"
82
+ >
83
+ {{ locale === 'fr' ? 'Télécharger' : 'Download' }} - {{ heartDocLabel(link) }}
84
+ </RmButton>
85
+ </div>
86
+ </details>
87
+
88
+ <!-- Ce que le moteur a reellement matche sur cette autorisation. -->
89
+ <OipDrugsHeartResultsMatchExplain :hit="{ highlight }" />
90
+
91
+ <template #footer>
92
+ <OipDrugsHeartElasticSourceContainerElastic
93
+ :uuid="HEART_SOURCE_UUIDS.PUT"
94
+ :last-label="t('oipDrugs.heart.sources.lastAuthorization')"
95
+ :last-date="product?.authorization_date"
96
+ />
97
+ </template>
98
+ </RmResultCard>
99
+ </template>
100
+
101
+ <script lang="ts" setup>
102
+ import { HEART_SOURCE_UUIDS } from '../../../../oip-drugs/utils/sourceUuids';
103
+ import { heartDocLabel } from '../../../../oip-drugs/utils/heartStyleAndFormat';
104
+ import { drugsBrandPath } from '../../../../oip-drugs/kinds';
105
+ import { openDrugsCard } from '../../../../oip-drugs/utils/s-openCard';
106
+ const { t, locale } = useI18n();
107
+ const nav = useOipResourceNav();
108
+ const { projectButton } = useOipResourceHooks();
109
+
110
+ const props = withDefaults(defineProps<{
111
+ product: any;
112
+ productId: string;
113
+ storeSearchBar?: string[];
114
+ inDetails?: boolean;
115
+ /** Extraits surlignes du hit, pour l'explication de la correspondance. */
116
+ highlight?: Record<string, string[]>;
117
+ }>(), {
118
+ storeSearchBar: () => [],
119
+ inDetails: false,
120
+ });
121
+
122
+ const dateLang = computed(() => (locale.value === 'en' ? 'en' : 'fr'));
123
+
124
+ /** La fiche marque, onglet PUT, ouverte sur ce protocole. */
125
+ const detailsPath = computed(() => {
126
+ const path = drugsBrandPath(props.product.brand);
127
+ return path ? `${path}?tab=put&put=${encodeURIComponent(props.productId ?? '')}` : '';
128
+ });
129
+ const detailsHref = computed(() => (detailsPath.value ? nav.appHref('fr_drugs_app', detailsPath.value) : undefined));
130
+
131
+ const statusLabel = computed(() => {
132
+ return locale.value === 'fr' ? props.product.status_fr : props.product.status_en;
133
+ });
134
+
135
+ /** Vert quand le protocole tourne, ambre quand il est clos. */
136
+ const statusVariant = computed(() => {
137
+ const lower = String(statusLabel.value ?? '').toLowerCase().trim();
138
+ if (lower === 'en cours' || lower === 'ongoing') return 'green';
139
+ if (['cloturé', 'closed', 'terminé', 'finished'].includes(lower)) return 'amber';
140
+ return 'default';
141
+ });
142
+
143
+ /** « TYPE D'ACCÈS · PÉRIODICITÉ », le cadre reglementaire du protocole. */
144
+ const subtitle = computed(() =>
145
+ [
146
+ locale.value === 'fr' ? props.product.aap_type_fr : props.product.aap_type_en,
147
+ locale.value === 'fr' ? props.product.put_periodicity_fr : props.product.put_periodicity_en,
148
+ ]
149
+ .filter(Boolean)
150
+ .join(' · ')
151
+ );
152
+
153
+ const objectiveLabel = computed(() => {
154
+ return (locale.value === 'fr' ? props.product.objective_fr : props.product.objective_en) || null;
155
+ });
156
+
157
+ const efficacyLabel = computed(() => {
158
+ return (locale.value === 'fr' ? props.product.efficacy_precision_fr : props.product.efficacy_precision_en) || null;
159
+ });
160
+
161
+ const fullIndication = computed(() => {
162
+ return (locale.value === 'fr' ? props.product.indication_fr : props.product.indication_en) || '-';
163
+ });
164
+
165
+ const docLinks = computed<string[]>(() => props.product.tekkare_doc_links || []);
166
+ const pdfLinks = computed<string[]>(() => props.product.tekkare_pdf_links || []);
167
+ const allLinks = computed(() => [...docLinks.value, ...pdfLinks.value]);
168
+
169
+ const documentCount = computed(() => allLinks.value.length);
170
+
171
+ const countLabel = computed(() => (documentCount.value > 1 ? 'documents' : 'document'));
172
+
173
+ /**
174
+ * Ce que la carte peut montrer en plus, si « Personnaliser » le demande.
175
+ * Les documents sont nommes par leur fichier : l'URL HAS est illisible.
176
+ */
177
+ const prefValues = computed(() => ({
178
+ brand: props.product.brand,
179
+ dci: props.product.dci,
180
+ therapeuticArea: locale.value === 'fr' ? props.product.therapeutic_area_fr : props.product.therapeutic_area_en,
181
+ docs: allLinks.value.map((link) => heartDocLabel(link)),
182
+ }));
183
+
184
+ function openPdf(url: string) {
185
+ if (url) window.open(url, '_blank');
186
+ }
187
+ </script>
188
+
189
+ <style scoped>
190
+ /* Accordéon Documents (natif <details>). */
191
+ .put-docs > summary {
192
+ list-style: none;
193
+ display: flex;
194
+ align-items: center;
195
+ gap: 8px;
196
+ margin-top: 4px;
197
+ cursor: pointer;
198
+ user-select: none;
199
+ }
200
+ .put-docs > summary::-webkit-details-marker {
201
+ display: none;
202
+ }
203
+ .put-docs-caret {
204
+ transition: transform 0.15s;
205
+ }
206
+ .put-docs[open] .put-docs-caret {
207
+ transform: rotate(90deg);
208
+ }
209
+ .put-docs-body {
210
+ margin-top: 12px;
211
+ }
212
+ </style>