@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,450 @@
1
+ <!--
2
+ Une marque du suivi des prix, ses presentations dans un tableau.
3
+
4
+ L'en-tete donne la marque, son laboratoire, le nombre de presentations et la
5
+ date du dernier mouvement de prix ; le tableau qui suit detaille chaque
6
+ presentation (son code CIP, son PFHT, l'evolution depuis le tarif precedent,
7
+ la date de prise d'effet et le taux de remboursement). Le prix public s'en
8
+ deduit (marges de distribution et TVA) : il vit dans la fiche produit.
9
+ -->
10
+ <script setup lang="ts">
11
+ import type { EsIndexName } from "../../../../oip-drugs/esIndices";
12
+ import { ES_INDEX } from "../../../../oip-drugs/esIndices";
13
+ import { HEART_SOURCE_UUIDS } from "../../../../oip-drugs/utils/sourceUuids";
14
+ import { drugsBrandPath } from "../../../../oip-drugs/kinds";
15
+ import { valueTable } from "../../../../utils/valueTable";
16
+
17
+ const props = withDefaults(
18
+ defineProps<{
19
+ /** Un groupe renvoye par `useHeartGroupedPrice`. */
20
+ group: any;
21
+ /** Termes de la recherche, surlignes dans le nom de marque. */
22
+ searchQueries?: any[];
23
+ /** Taux de change du jour, passes a l'export de la marque. */
24
+ rates?: any[] | null;
25
+ indexQuery?: EsIndexName;
26
+ /** Rendu serre de la fiche projet : l'en-tete seul, sans le tableau. */
27
+ dense?: boolean;
28
+ }>(),
29
+ { searchQueries: () => [], rates: null, indexQuery: ES_INDEX.DRUG_CARDS, dense: false },
30
+ );
31
+
32
+ const { locale } = useI18n();
33
+ const nav = useOipResourceNav();
34
+ const { projectButton } = useOipResourceHooks();
35
+
36
+ /**
37
+ * Tarif courant d'une presentation, et celui qui le precede.
38
+ *
39
+ * `reimbursement_history` n'est pas trie : prendre la premiere entree ferait
40
+ * passer un vieux tarif pour le tarif en vigueur.
41
+ *
42
+ * Il empile aussi un tarif par circuit de distribution (retrocession, T2A,
43
+ * ville) a la meme date : comparer la premiere entree a la deuxieme comparait
44
+ * deux circuits entre eux et annoncait 0 % la ou le prix avait bouge. Le tarif
45
+ * precedent est donc celui du meme circuit, a une date anterieure, sauf quand
46
+ * le tarif courant arrive sans circuit, voir `isKnownChannel`.
47
+ */
48
+ function tariffs(product: any) {
49
+ const history = Array.isArray(product?.reimbursement_history)
50
+ ? [...product.reimbursement_history].sort(
51
+ (a: any, b: any) =>
52
+ new Date(b.application_date).getTime() - new Date(a.application_date).getTime(),
53
+ )
54
+ : [];
55
+ const current = history[0] ?? {};
56
+ const sameChannel = isKnownChannel(current.distribution_type)
57
+ ? history.filter((entry: any) => entry.distribution_type === current.distribution_type)
58
+ : history;
59
+ const previous =
60
+ sameChannel.find(
61
+ (entry: any) =>
62
+ entry.application_date !== current.application_date && entry.factory_price != null,
63
+ ) ?? null;
64
+ return { current, previous };
65
+ }
66
+
67
+ /**
68
+ * Un circuit de distribution nomme. La source livre ses derniers tarifs sans
69
+ * circuit (« Non renseigne ») alors que l'historique en porte un (TAA,
70
+ * Retrocession) : exiger le meme circuit ne trouvait alors aucun tarif
71
+ * precedent et toute la colonne Evolution tombait a NA. Faute de circuit
72
+ * connu, on compare au tarif precedent quel qu'il soit.
73
+ */
74
+ const UNKNOWN_CHANNELS = new Set(["", "nan", "na", "non renseigne", "unknown"]);
75
+
76
+ function isKnownChannel(value: any) {
77
+ if (!value) return false;
78
+ const normalized = String(value)
79
+ .trim()
80
+ .toLowerCase()
81
+ .normalize("NFD")
82
+ .replace(/[\u0300-\u036f]/g, "");
83
+ return !UNKNOWN_CHANNELS.has(normalized);
84
+ }
85
+
86
+ /** Variation en pourcentage d'un prix par rapport au precedent. */
87
+ function variation(now: number | null | undefined, before: number | null | undefined) {
88
+ if (now === null || now === undefined || !before) return null;
89
+ return ((Number(now) - Number(before)) / Number(before)) * 100;
90
+ }
91
+
92
+ function money(value: number | null | undefined) {
93
+ if (value === null || value === undefined) return "NA";
94
+ return new Intl.NumberFormat(locale.value === "en" ? "en-US" : "fr-FR", {
95
+ style: "currency",
96
+ currency: "EUR",
97
+ maximumFractionDigits: 2,
98
+ }).format(Number(value));
99
+ }
100
+
101
+ function shortDate(raw: string | null | undefined) {
102
+ if (!raw) return "NA";
103
+ const date = new Date(raw);
104
+ if (Number.isNaN(date.getTime())) return "NA";
105
+ return date.toLocaleDateString(locale.value === "en" ? "en-GB" : "fr-FR", {
106
+ day: "numeric",
107
+ month: "short",
108
+ year: "numeric",
109
+ });
110
+ }
111
+
112
+ const heads = computed(() =>
113
+ locale.value === "en"
114
+ ? ["Presentation", "Ex-factory", "Change", "Effective date", "Reimb."]
115
+ : ["Présentation", "PFHT", "Évolution", "Prise d'effet", "Rembours."],
116
+ );
117
+
118
+ const rows = computed(() =>
119
+ (props.group?.products ?? []).map((product: any) => {
120
+ const { current, previous } = tariffs(product);
121
+ // Un prix inchange depuis toujours n'a pas d'evolution a montrer, mais il
122
+ // garde sa date de prise d'effet.
123
+ const change = variation(current?.factory_price, previous?.factory_price);
124
+ return {
125
+ values: [
126
+ {
127
+ cip: product.local_drug_id ?? null,
128
+ label: product.presentation_label || product.label || "NA",
129
+ },
130
+ money(current?.factory_price ?? null),
131
+ { change },
132
+ shortDate(current?.application_date),
133
+ current.reimbursement_rate != null
134
+ ? `${Math.round(current.reimbursement_rate * 100)} %`
135
+ : "NA",
136
+ ],
137
+ };
138
+ }),
139
+ );
140
+
141
+ /**
142
+ * Une variation se lit avec son sens : la fleche le porte, la couleur ne fait
143
+ * que la redoubler. Ni rouge ni vert — une hausse de prix n'est bonne ni
144
+ * mauvaise en soi, ca depend de qui lit.
145
+ */
146
+ function formatChange(change: number) {
147
+ const rounded = Math.abs(change) >= 10 ? change.toFixed(0) : change.toFixed(1);
148
+ const arrow = change > 0 ? "↑" : change < 0 ? "↓" : "=";
149
+ return `${arrow} ${change > 0 ? "+" : ""}${rounded} %`;
150
+ }
151
+
152
+ /**
153
+ * Fiche ouverte au clic sur la carte : la fiche produit du hub, sur son onglet
154
+ * Prix. La carte porte une marque, la fiche aussi (l'ancienne page de detail
155
+ * tarifaire, elle, s'adressait a une seule presentation).
156
+ */
157
+ const detailsPath = computed(() => {
158
+ const path = drugsBrandPath(props.group?.brand);
159
+ return path ? `${path}?tab=price` : null;
160
+ });
161
+
162
+ /**
163
+ * Toute la carte mene a la fiche, sauf ce qui a sa propre action : la
164
+ * pagination du tableau, un lien, un bouton.
165
+ */
166
+ function openDetails(event: MouseEvent) {
167
+ const target = event.target as HTMLElement | null;
168
+ if (target?.closest("a, button, .rcd-pagination, .rcd-pagination-navigate")) return;
169
+ if (!detailsPath.value) return;
170
+ if (event.metaKey || event.ctrlKey || event.shiftKey) {
171
+ window.open(nav.appHref("fr_drugs_app", detailsPath.value), "_blank", "noopener");
172
+ return;
173
+ }
174
+ nav.openApp("fr_drugs_app", detailsPath.value);
175
+ }
176
+
177
+ const countLabel = computed(() => {
178
+ const count = props.group?.count ?? 0;
179
+ if (locale.value === "en") return `${count} presentation${count > 1 ? "s" : ""}`;
180
+ return `${count} présentation${count > 1 ? "s" : ""}`;
181
+ });
182
+
183
+ // Le tableau des presentations etait ecrit pour `arg-table` : des libelles de
184
+ // tete et des lignes `{ values: [...] }`. La conversion en colonnes de RmTable
185
+ // est mecanique, cf. `valueTable`. Il ne se trie pas : la carte montre les
186
+ // presentations les plus recemment mises a jour, dans cet ordre.
187
+ const presTable = computed(() => {
188
+ const converted = valueTable(heads.value, rows.value, [
189
+ "presentation",
190
+ "",
191
+ "change",
192
+ "date",
193
+ "",
194
+ ]);
195
+ return {
196
+ ...converted,
197
+ columns: converted.columns.map((column) => ({ ...column, sortable: false })),
198
+ };
199
+ });
200
+ </script>
201
+
202
+ <template>
203
+ <!-- `rm-card` n'est pas un style : c'est ce par quoi le menu d'actions
204
+ (la roue crantee) reconnait la carte qui le porte. -->
205
+ <article class="oip-drugs bg_card rm-card" @click="openDetails">
206
+ <header class="bg_head">
207
+ <span class="bg_identity">
208
+ <span class="bg_brand">
209
+ <OipDrugsHeartTextHighlight
210
+ :text="group.brand"
211
+ :queries="searchQueries ?? []"
212
+ />
213
+ </span>
214
+ <span class="bg_meta">
215
+ <template v-if="group.molecules?.length">{{ group.molecules.join(" · ") }}</template>
216
+ <template v-if="group.manufacturers?.length">
217
+ · {{ group.manufacturers.join(" · ") }}
218
+ </template>
219
+ </span>
220
+ </span>
221
+
222
+ <span class="bg_facts">
223
+ <span class="bg_count">{{ countLabel }}</span>
224
+ <span class="bg_date">
225
+ <template v-if="group.last_price_update">
226
+ {{ locale === "en" ? "Latest change" : "Dernier changement" }} :
227
+ {{ shortDate(group.last_price_update) }}
228
+ </template>
229
+ <template v-else>
230
+ {{ locale === "en" ? "No price change" : "Aucun changement de prix" }}
231
+ </template>
232
+ </span>
233
+ </span>
234
+
235
+ <!-- Le tableau ci-dessous ne montre que les 25 presentations les plus
236
+ recentes, sans leur historique : l'export, lui, porte la marque
237
+ entiere. Il se demande par la roue crantee, au meme endroit que sur
238
+ les cartes d'avis HAS. -->
239
+ <!-- Le projet range la marque entiere : toutes ses presentations, pas
240
+ seulement celles du tableau (cf. `ids`, useHeartGroupedPrice). -->
241
+ <!-- Emplacement libre pour un appelant (la fiche projet y pose son
242
+ bouton « Commenter ») avant le bouton projet : vide partout ailleurs. -->
243
+ <slot name="hubr-actions" />
244
+ <component
245
+ :is="projectButton"
246
+ v-if="projectButton"
247
+ :resource-id="group.ids ?? (group.products ?? []).map((product: any) => product.id)"
248
+ type="FrDrugPrice"
249
+ :label="group.brand"
250
+ compact
251
+ />
252
+ </header>
253
+
254
+ <div v-if="!dense" class="bg_body">
255
+ <RmTable
256
+ flat
257
+ :columns="presTable.columns"
258
+ :rows="presTable.rows"
259
+ :page-size="5"
260
+ >
261
+ <!-- Le CIP identifie la presentation : il la coiffe plutot que de
262
+ prendre une colonne pour lui seul. -->
263
+ <template v-for="key in presTable.typeKeys.presentation" :key="key" #[key]="{ value }">
264
+ <span class="pres">
265
+ <RmTag v-if="value.cip" variant="gray">{{ value.cip }}</RmTag>
266
+ <span class="pres_label">{{ value.label }}</span>
267
+ </span>
268
+ </template>
269
+
270
+ <template v-for="key in presTable.typeKeys.change" :key="key" #[key]="{ value }">
271
+ <span
272
+ v-if="value.change !== null"
273
+ class="pc_change"
274
+ :class="value.change > 0 ? 'is_up' : value.change < 0 ? 'is_down' : 'is_flat'"
275
+ >
276
+ {{ formatChange(value.change) }}
277
+ </span>
278
+ <!-- Pas d'evolution : un seul tarif connu, rien a comparer. -->
279
+ <span v-else class="pc_none">NA</span>
280
+ </template>
281
+
282
+ <!-- « 15 mars 2024 » tient sur une ligne : coupe en deux, la date se
283
+ lit comme deux valeurs. -->
284
+ <template v-for="key in presTable.typeKeys.date" :key="key" #[key]="{ value }">
285
+ <span class="pc_date">{{ value }}</span>
286
+ </template>
287
+ </RmTable>
288
+
289
+ <p v-if="group.count > group.products.length" class="bg_note">
290
+ {{
291
+ locale === "en"
292
+ ? `Showing the ${group.products.length} most recently updated presentations out of ${group.count}.`
293
+ : `Les ${group.products.length} présentations les plus récemment mises à jour, sur ${group.count}.`
294
+ }}
295
+ </p>
296
+ </div>
297
+
298
+ <!-- Meme pied que les cartes d'avis HAS : la source, et la date du dernier
299
+ tarif publie pour la marque. -->
300
+ <div v-if="!dense" class="bg_footer">
301
+ <OipDrugsHeartElasticSourceContainerElastic
302
+ :uuid="HEART_SOURCE_UUIDS.PRICE_TRACKER"
303
+ :last-label="$t('oipDrugs.heart.sources.lastPrice')"
304
+ :last-date="group?.last_price_update"
305
+ />
306
+ </div>
307
+ </article>
308
+ </template>
309
+
310
+ <style scoped>
311
+ .bg_card {
312
+ box-sizing: border-box;
313
+ width: 100%;
314
+ border: 1px solid var(--border-default);
315
+ border-radius: 12px;
316
+ background: var(--bg-surface);
317
+ overflow: hidden;
318
+ cursor: pointer;
319
+ transition: border-color 0.15s linear, box-shadow 0.15s linear;
320
+ }
321
+
322
+ .bg_card:hover {
323
+ border-color: color-mix(in srgb, var(--color-primary) 35%, var(--border-default));
324
+ box-shadow: var(--shadow-md);
325
+ }
326
+
327
+ /* `border-box` obligatoire : le reset de l'app remet `box-sizing` a
328
+ `content-box`, et `width: 100%` + 32px de gouttiere poussait la colonne de
329
+ droite (nombre de presentations, date) hors de la carte, ou le
330
+ `overflow: hidden` la coupait. */
331
+ .bg_head {
332
+ display: flex;
333
+ align-items: center;
334
+ gap: 12px;
335
+ box-sizing: border-box;
336
+ width: 100%;
337
+ padding: 14px 16px;
338
+ }
339
+
340
+ .bg_identity {
341
+ display: flex;
342
+ flex-direction: column;
343
+ gap: 2px;
344
+ min-width: 0;
345
+ flex: 1 1 auto;
346
+ }
347
+
348
+ .bg_brand {
349
+ color: var(--text-primary);
350
+ font-size: 16px;
351
+ font-weight: 800;
352
+ }
353
+
354
+ .bg_meta {
355
+ color: var(--text-muted);
356
+ font-size: 13px;
357
+ overflow: hidden;
358
+ text-overflow: ellipsis;
359
+ white-space: nowrap;
360
+ }
361
+
362
+ .bg_facts {
363
+ display: flex;
364
+ flex: 0 0 auto;
365
+ flex-direction: column;
366
+ align-items: flex-end;
367
+ gap: 2px;
368
+ }
369
+
370
+ .bg_count {
371
+ padding: 2px 10px;
372
+ border-radius: 999px;
373
+ background: color-mix(in srgb, var(--color-primary) 8%, transparent);
374
+ color: var(--color-primary);
375
+ font-size: 12px;
376
+ font-weight: 700;
377
+ }
378
+
379
+ .bg_date {
380
+ color: var(--text-muted);
381
+ font-size: 12px;
382
+ }
383
+
384
+ /* Le tableau va d'un bord a l'autre : ses lignes zebrees s'arretaient a
385
+ 16px des cotes, ce qui dessinait une seconde carte a l'interieur de la
386
+ carte. Le retrait revient sur ce qui n'est pas le tableau, et `flat` ote
387
+ le cadre que RmTable pose de lui-meme. */
388
+ .bg_body {
389
+ display: flex;
390
+ flex-direction: column;
391
+ gap: 8px;
392
+ box-sizing: border-box;
393
+ width: 100%;
394
+ padding: 0 0 16px;
395
+ border-top: 1px solid var(--border-default);
396
+ }
397
+
398
+ .pres {
399
+ display: flex;
400
+ flex-direction: column;
401
+ align-items: flex-start;
402
+ gap: 4px;
403
+ }
404
+
405
+ .pres_label {
406
+ font-weight: 600;
407
+ }
408
+
409
+ .pc_change {
410
+ font-size: 12px;
411
+ font-weight: 700;
412
+ }
413
+
414
+ /* Bleu pour la hausse, violet pour la baisse : deux teintes froides, aucune
415
+ ne dit si le mouvement est une bonne nouvelle. */
416
+ .pc_change.is_up {
417
+ color: var(--color-info);
418
+ }
419
+
420
+ .pc_change.is_down {
421
+ color: var(--color-secondary);
422
+ }
423
+
424
+ .pc_change.is_flat {
425
+ color: var(--text-muted);
426
+ }
427
+
428
+ .pc_none {
429
+ color: var(--text-muted);
430
+ }
431
+
432
+ .pc_date {
433
+ white-space: nowrap;
434
+ }
435
+
436
+ .bg_note {
437
+ padding: 0 16px;
438
+ color: var(--text-muted);
439
+ font-size: 12px;
440
+ }
441
+
442
+ .bg_footer {
443
+ box-sizing: border-box;
444
+ width: 100%;
445
+ padding: 12px 16px;
446
+ border-top: 1px solid var(--border-default);
447
+ background: var(--bg-surface-2);
448
+ font-size: 13px;
449
+ }
450
+ </style>
@@ -0,0 +1,199 @@
1
+ <!--
2
+ Carte d'un medicament d'interet therapeutique majeur (MITM).
3
+
4
+ Meme modele que les cartes de marque et LES (RmResultCard) : un produit
5
+ (CIS), ses presentations, son dernier SMR/ASMR. Le detail vit dans la fiche,
6
+ ouverte au clic sur la carte entiere.
7
+ -->
8
+ <template>
9
+ <RmResultCard
10
+ class="oip-drugs"
11
+ :clickable="!inDetails"
12
+ :count="presentationCount"
13
+ :count-label="countLabel"
14
+ :more-label="inDetails ? '' : locale === 'fr' ? 'Voir le détail' : 'See details'"
15
+ :lang="dateLang"
16
+ :link="detailsHref"
17
+ @click="!inDetails && openDrugsCard(nav, $event, detailsPath)"
18
+ >
19
+ <template #title>
20
+ <OipDrugsHeartTextHighlight :text="result.brand || result.cis_label" :queries="highlight" />
21
+ </template>
22
+ <template v-if="subtitle" #subtitle>
23
+ <OipDrugsHeartTextHighlight :text="subtitle" :queries="highlight" />
24
+ </template>
25
+
26
+ <template #actions>
27
+ <!-- Emplacement libre pour un appelant (la fiche projet y pose son
28
+ bouton « Commenter ») avant le bouton projet : vide partout ailleurs. -->
29
+ <slot name="hubr-actions" />
30
+ <component
31
+ :is="projectButton"
32
+ v-if="projectButton"
33
+ :resource-id="result._id"
34
+ type="FrMitm"
35
+ :label="result.brand || result.cis_label"
36
+ compact
37
+ />
38
+ </template>
39
+
40
+ <template #meta>
41
+ <span v-if="lastSmr" class="rm-rcard__label">
42
+ {{ locale === "fr" ? "Dernier SMR" : "Last SMR" }} ·
43
+ <RmDate :date="formatHasDate(lastSmr.has_date)" :lang="dateLang" />
44
+ </span>
45
+ <span class="rm-rcard__ref">CIS {{ result.cis_code }}</span>
46
+ <RmTag variant="violet">
47
+ {{ locale === "fr" ? "Sur la liste ANSM" : "On the ANSM list" }}
48
+ </RmTag>
49
+ </template>
50
+
51
+ <p class="rm-rcard__lead" :class="{ 'rm-rcard__clamp': !inDetails }">
52
+ <OipDrugsHeartTextHighlight :text="result.cis_label" :queries="highlight" />
53
+ </p>
54
+
55
+ <p v-if="lastSmr || lastAsmr" class="rm-rcard__text">
56
+ <template v-if="lastSmr">SMR {{ lastSmr.value }}</template>
57
+ <template v-if="lastSmr && lastAsmr"> · </template>
58
+ <template v-if="lastAsmr">ASMR {{ lastAsmr.value }}</template>
59
+ </p>
60
+
61
+ <!-- Rangees choisies dans « Personnaliser ». -->
62
+ <OipDrugsHeartResultsPrefRows scope="mitm" :values="prefValues" />
63
+
64
+ <!-- Ce que le moteur a reellement matche sur cette ligne. -->
65
+ <OipDrugsHeartResultsMatchExplain :hit="{ highlight: result.highlight }" />
66
+
67
+ <template #footer>
68
+ <OipDrugsHeartElasticSourceContainerElastic
69
+ :uuid="HEART_SOURCE_UUIDS.MITM"
70
+ :last-label="$t('oipDrugs.heart.sources.lastOpinion')"
71
+ :last-date="lastOpinionDate"
72
+ />
73
+ </template>
74
+ </RmResultCard>
75
+ </template>
76
+
77
+ <script setup lang="ts">
78
+ import type { PropType } from "vue";
79
+ import { HEART_SOURCE_UUIDS } from "../../../../oip-drugs/utils/sourceUuids";
80
+ import { drugsBrandPath } from "../../../../oip-drugs/kinds";
81
+ import { openDrugsCard } from "../../../../oip-drugs/utils/s-openCard";
82
+
83
+ const props = defineProps({
84
+ result: {
85
+ required: true,
86
+ type: Object,
87
+ },
88
+ inDetails: {
89
+ type: Boolean,
90
+ default: false,
91
+ },
92
+ /** Termes de la recherche, surlignes dans la carte (ex-filterStore). */
93
+ highlight: {
94
+ type: Array as PropType<string[]>,
95
+ default: () => [],
96
+ },
97
+ });
98
+
99
+ const { locale } = useI18n();
100
+ const nav = useOipResourceNav();
101
+ const { projectButton } = useOipResourceHooks();
102
+
103
+ const dateLang = computed(() => (locale.value === "en" ? "en" : "fr"));
104
+
105
+ const detailsPath = computed(() => {
106
+ const path = drugsBrandPath(props.result.brand);
107
+ return path ? `${path}?tab=mitm&cis=${encodeURIComponent(props.result.cis_code ?? "")}` : "";
108
+ });
109
+ const detailsHref = computed(() => (detailsPath.value ? nav.appHref("fr_drugs_app", detailsPath.value) : undefined));
110
+
111
+ /** « MOLECULE (ATC5) · FABRICANT », comme le sous-titre de la carte de marque. */
112
+ const subtitle = computed(() =>
113
+ [
114
+ locale.value === "fr"
115
+ ? props.result.atc5_french_code_label
116
+ : props.result.atc5_english_code_label,
117
+ props.result.owner_regroup,
118
+ ]
119
+ .filter(Boolean)
120
+ .join(" · ")
121
+ );
122
+
123
+ /** Les presentations (CIP) rattachees au CIS. */
124
+ const presentationCount = computed(() =>
125
+ Array.isArray(props.result.price) ? props.result.price.length : 0
126
+ );
127
+
128
+ const countLabel = computed(() => {
129
+ const n = presentationCount.value;
130
+ if (locale.value === "en") return n > 1 ? "presentations" : "presentation";
131
+ return n > 1 ? "présentations" : "présentation";
132
+ });
133
+
134
+ /** Premier SMR / ASMR renseigne : les tableaux portent souvent une ligne vide. */
135
+ const lastSmr = computed(
136
+ () => (props.result.last_smr ?? []).find((s: any) => s?.value) ?? null
137
+ );
138
+ const lastAsmr = computed(
139
+ () => (props.result.last_asmr ?? []).find((s: any) => s?.value) ?? null
140
+ );
141
+
142
+ /**
143
+ * Ce que la carte peut montrer en plus, si « Personnaliser » le demande.
144
+ *
145
+ * `molecule` ne porte pas les molecules du produit mais les autres CIS de la
146
+ * meme molecule : la rangee annonce donc des marques, pas des principes actifs.
147
+ */
148
+ const prefValues = computed(() => {
149
+ const en = locale.value === "en";
150
+ const smrEntries = (props.result.smr ?? []).filter((s: any) => s?.value);
151
+ const asmrEntries = (props.result.asmr ?? []).filter((s: any) => s?.value);
152
+ const prices = (props.result.price ?? []).filter((p: any) => p?.public_price);
153
+ const rates = [...new Set(prices.map((p: any) => p.reimbursement_rate).filter((r: any) => r != null))] as number[];
154
+ const amounts = prices.map((p: any) => p.public_price as number).sort((a: number, b: number) => a - b);
155
+ const money = (value: number) =>
156
+ value.toLocaleString(en ? "en-US" : "fr-FR", { style: "currency", currency: "EUR" });
157
+
158
+ return {
159
+ hasReason: [
160
+ ...new Set([props.result.last_smr?.[0]?.has_reason, props.result.last_asmr?.[0]?.has_reason].filter(Boolean)),
161
+ ] as string[],
162
+ hasCode: [
163
+ ...new Set([props.result.last_smr?.[0]?.has_code, props.result.last_asmr?.[0]?.has_code].filter(Boolean)),
164
+ ] as string[],
165
+ opinionCount: [
166
+ smrEntries.length ? `${smrEntries.length} SMR` : null,
167
+ asmrEntries.length ? `${asmrEntries.length} ASMR` : null,
168
+ ].filter(Boolean) as string[],
169
+ atc4: [en ? props.result.atc4_english_code_label : props.result.atc4_french_code_label].filter(Boolean) as string[],
170
+ sameMolecule: [
171
+ ...new Set(
172
+ (props.result.molecule ?? [])
173
+ .map((m: any) => m?.brand)
174
+ .filter((brand: string) => brand && brand !== props.result.brand)
175
+ ),
176
+ ] as string[],
177
+ prices: [
178
+ amounts.length
179
+ ? `${en ? "Public price" : "Prix public"} ${money(amounts[0])}${amounts.length > 1 && amounts[amounts.length - 1] !== amounts[0] ? ` ${en ? "to" : "à"} ${money(amounts[amounts.length - 1])}` : ""}`
180
+ : null,
181
+ ...rates
182
+ .sort((a, b) => b - a)
183
+ .map((rate) => `${en ? "Reimbursed" : "Remboursement"} ${Math.round(rate * 100)} %`),
184
+ ].filter(Boolean) as string[],
185
+ };
186
+ });
187
+
188
+ /** Le dernier avis HAS porte par la carte (SMR ou ASMR), en date ISO. */
189
+ const lastOpinionDate = computed(() =>
190
+ [lastSmr.value?.has_date, lastAsmr.value?.has_date]
191
+ .filter(Boolean)
192
+ .map((value: string) => formatHasDate(value)),
193
+ );
194
+
195
+ function formatHasDate(dateStr: string) {
196
+ if (!dateStr || dateStr.length !== 8) return dateStr;
197
+ return `${dateStr.substring(0, 4)}-${dateStr.substring(4, 6)}-${dateStr.substring(6, 8)}`;
198
+ }
199
+ </script>
@@ -0,0 +1,23 @@
1
+ <!--
2
+ Pied de source des blocs MITM (portage de heart/mitm/source de fr_drugs_app) :
3
+ la fiche Directus de la liste MITM, avec la date du dernier mouvement de ce
4
+ que l'appelant affiche quand il la connait.
5
+ -->
6
+ <template>
7
+ <OipDrugsHeartElasticSourceContainerElastic
8
+ class="oip-drugs"
9
+ :uuid="HEART_SOURCE_UUIDS.MITM"
10
+ tone="primary"
11
+ :last-label="lastLabel"
12
+ :last-date="lastDate"
13
+ />
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import { HEART_SOURCE_UUIDS } from "../../../../oip-drugs/utils/sourceUuids";
18
+
19
+ defineProps<{
20
+ lastLabel?: string;
21
+ lastDate?: string | number | null | Array<string | number | null | undefined>;
22
+ }>();
23
+ </script>