@morscherlab/mint-sdk 1.0.0-rc.4 → 1.0.0-rc.6

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 (324) hide show
  1. package/dist/__tests__/components/AppTopBar.navigation.test.d.ts +1 -0
  2. package/dist/__tests__/components/DoseCalculatorVolumeField.test.d.ts +1 -0
  3. package/dist/__tests__/components/PlateMapEditorToolbarInternal.test.d.ts +1 -0
  4. package/dist/__tests__/components/PluginWorkspaceView.controls.test.d.ts +1 -0
  5. package/dist/__tests__/components/PluginWorkspaceView.navigation.test.d.ts +1 -0
  6. package/dist/__tests__/components/PluginWorkspaceView.shell.test.d.ts +1 -0
  7. package/dist/__tests__/components/ProtocolStep.presentation.test.d.ts +1 -0
  8. package/dist/__tests__/components/ProtocolStepEditor.state.test.d.ts +1 -0
  9. package/dist/__tests__/components/ProtocolStepParameterField.test.d.ts +1 -0
  10. package/dist/__tests__/components/ReagentList.presentation.test.d.ts +1 -0
  11. package/dist/__tests__/components/SampleSelector.colors.test.d.ts +1 -0
  12. package/dist/__tests__/components/SampleSelector.drag.test.d.ts +1 -0
  13. package/dist/__tests__/components/SampleSelector.groups.test.d.ts +1 -0
  14. package/dist/__tests__/components/SampleSelector.selection.test.d.ts +1 -0
  15. package/dist/__tests__/components/SampleSelectorSampleRow.test.d.ts +1 -0
  16. package/dist/__tests__/components/ScheduleCalendar.test.d.ts +1 -0
  17. package/dist/__tests__/components/SettingsModal.schema.test.d.ts +1 -0
  18. package/dist/__tests__/components/WellPlate.colors.test.d.ts +1 -0
  19. package/dist/__tests__/components/WellPlate.conditions.test.d.ts +1 -0
  20. package/dist/__tests__/components/WellPlate.geometry.test.d.ts +1 -0
  21. package/dist/__tests__/components/WellPlate.interaction.test.d.ts +1 -0
  22. package/dist/__tests__/components/WellPlate.legend.test.d.ts +1 -0
  23. package/dist/__tests__/components/WellPlate.rendering.test.d.ts +1 -0
  24. package/dist/__tests__/components/WellPlate.sampleDrop.test.d.ts +1 -0
  25. package/dist/__tests__/composables/autoGroup/classify.test.d.ts +1 -0
  26. package/dist/__tests__/composables/autoGroup/columns.test.d.ts +1 -0
  27. package/dist/__tests__/composables/autoGroup/compose.test.d.ts +1 -0
  28. package/dist/__tests__/composables/autoGroup/cooccurrence.test.d.ts +1 -0
  29. package/dist/__tests__/composables/autoGroup/fingerprint.test.d.ts +1 -0
  30. package/dist/__tests__/composables/autoGroup/integration.test.d.ts +1 -0
  31. package/dist/__tests__/composables/autoGroup/template.test.d.ts +1 -0
  32. package/dist/__tests__/composables/autoGroup/tokenize.test.d.ts +1 -0
  33. package/dist/__tests__/composables/useAutoGroupInputSources.test.d.ts +1 -0
  34. package/dist/__tests__/composables/useScheduleCalendarLayout.test.d.ts +1 -0
  35. package/dist/__tests__/docs/extractDocsComponents.test.d.ts +1 -0
  36. package/dist/__tests__/docs/extractDocsExports.test.d.ts +1 -0
  37. package/dist/__tests__/docs/extractDocsParsing.test.d.ts +1 -0
  38. package/dist/__tests__/docs/extractDocsTemplates.test.d.ts +1 -0
  39. package/dist/__tests__/docs/extractDocsTheme.test.d.ts +1 -0
  40. package/dist/components/AppSidebar.vue.d.ts +9 -6
  41. package/dist/components/AppTopBar.navigation.d.ts +11 -0
  42. package/dist/components/BaseButton.vue.d.ts +1 -1
  43. package/dist/components/BaseCheckbox.vue.d.ts +7 -2
  44. package/dist/components/BaseInput.vue.d.ts +2 -2
  45. package/dist/components/BasePill.vue.d.ts +2 -2
  46. package/dist/components/BaseRadioGroup.vue.d.ts +2 -2
  47. package/dist/components/BaseSelect.vue.d.ts +1 -1
  48. package/dist/components/BaseSlider.vue.d.ts +2 -2
  49. package/dist/components/BaseTextarea.vue.d.ts +2 -2
  50. package/dist/components/BaseToggle.vue.d.ts +7 -2
  51. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
  52. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -1
  53. package/dist/components/CollapsibleCard.vue.d.ts +9 -0
  54. package/dist/components/ColorSlider.vue.d.ts +2 -2
  55. package/dist/components/ConcentrationInput.vue.d.ts +2 -2
  56. package/dist/components/ControlWorkspaceView.vue.d.ts +4 -4
  57. package/dist/components/DatePicker.vue.d.ts +1 -1
  58. package/dist/components/DateTimePicker.vue.d.ts +2 -2
  59. package/dist/components/DoseCalculatorVolumeField.vue.d.ts +15 -0
  60. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +2 -2
  61. package/dist/components/DropdownButton.vue.d.ts +1 -1
  62. package/dist/components/FileUploader.vue.d.ts +2 -2
  63. package/dist/components/FormulaInput.vue.d.ts +2 -2
  64. package/dist/components/IconButton.vue.d.ts +1 -1
  65. package/dist/components/LoadingSpinner.vue.d.ts +1 -1
  66. package/dist/components/MoleculeInput.vue.d.ts +2 -2
  67. package/dist/components/MultiSelect.vue.d.ts +1 -1
  68. package/dist/components/NumberInput.vue.d.ts +1 -1
  69. package/dist/components/PlateMapEditor.vue.d.ts +6 -6
  70. package/dist/components/PluginWorkspaceView.controls.d.ts +28 -0
  71. package/dist/components/PluginWorkspaceView.navigation.d.ts +29 -0
  72. package/dist/components/PluginWorkspaceView.props.d.ts +151 -0
  73. package/dist/components/PluginWorkspaceView.shell.d.ts +19 -0
  74. package/dist/components/PluginWorkspaceView.vue.d.ts +51 -196
  75. package/dist/components/ProgressBar.vue.d.ts +1 -1
  76. package/dist/components/ProtocolStep.presentation.d.ts +4 -0
  77. package/dist/components/ProtocolStepEditor.state.d.ts +18 -0
  78. package/dist/components/ProtocolStepParameterField.vue.d.ts +12 -0
  79. package/dist/components/ReagentList.presentation.d.ts +16 -0
  80. package/dist/components/ResourceCard.vue.d.ts +1 -1
  81. package/dist/components/SampleSelector.colors.d.ts +13 -0
  82. package/dist/components/SampleSelector.drag.d.ts +24 -0
  83. package/dist/components/SampleSelector.groups.d.ts +15 -0
  84. package/dist/components/SampleSelector.selection.d.ts +26 -0
  85. package/dist/components/SampleSelector.vue.d.ts +4 -1
  86. package/dist/components/SampleSelectorSampleRow.vue.d.ts +21 -0
  87. package/dist/components/SegmentedControl.vue.d.ts +1 -1
  88. package/dist/components/SequenceInput.vue.d.ts +2 -2
  89. package/dist/components/SequenceProgressBar.vue.d.ts +1 -1
  90. package/dist/components/SettingsModal.schema.d.ts +9 -0
  91. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  92. package/dist/components/TagsInput.vue.d.ts +2 -2
  93. package/dist/components/TimePicker.vue.d.ts +2 -2
  94. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  95. package/dist/components/UnitInput.vue.d.ts +2 -2
  96. package/dist/components/WellPlate.colors.d.ts +9 -0
  97. package/dist/components/WellPlate.conditions.d.ts +26 -0
  98. package/dist/components/WellPlate.geometry.d.ts +23 -0
  99. package/dist/components/WellPlate.interaction.d.ts +71 -0
  100. package/dist/components/WellPlate.legend.d.ts +2 -0
  101. package/dist/components/WellPlate.rendering.d.ts +24 -0
  102. package/dist/components/WellPlate.sampleDrop.d.ts +8 -0
  103. package/dist/components/WellPlate.vue.d.ts +1 -1
  104. package/dist/components/index.js +2 -2
  105. package/dist/components/internal/ActionItemInternal.vue.d.ts +1 -1
  106. package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +28 -0
  107. package/dist/{components-DafPc4rM.js → components-Blx4MG--.js} +4073 -4222
  108. package/dist/components-Blx4MG--.js.map +1 -0
  109. package/dist/composables/autoGroup/classKey.d.ts +4 -0
  110. package/dist/composables/autoGroup/classify.d.ts +28 -0
  111. package/dist/composables/autoGroup/colors.d.ts +2 -0
  112. package/dist/composables/autoGroup/columns.d.ts +10 -0
  113. package/dist/composables/autoGroup/compose.d.ts +8 -0
  114. package/dist/composables/autoGroup/cooccurrence.d.ts +2 -0
  115. package/dist/composables/autoGroup/csv-shim.d.ts +2 -0
  116. package/dist/composables/autoGroup/fingerprint.d.ts +3 -0
  117. package/dist/composables/autoGroup/index.d.ts +16 -0
  118. package/dist/composables/autoGroup/replicatePreGroup.d.ts +38 -0
  119. package/dist/composables/autoGroup/template.d.ts +15 -0
  120. package/dist/composables/autoGroup/tokenize.d.ts +8 -0
  121. package/dist/composables/autoGroupConstants.d.ts +1 -0
  122. package/dist/composables/autoGroupGrouping.d.ts +3 -0
  123. package/dist/composables/controlComponentBindings.d.ts +1 -1
  124. package/dist/composables/controlSchemaAdapters.d.ts +1 -1
  125. package/dist/composables/controlSchemaDoseDesign.d.ts +11 -0
  126. package/dist/composables/controlSchemaLayout.d.ts +1 -1
  127. package/dist/composables/controlSchemaModel.d.ts +5 -0
  128. package/dist/composables/controlSchemaNormalize.d.ts +1 -1
  129. package/dist/composables/controlSchemaTypes.d.ts +311 -0
  130. package/dist/composables/controlWorkspaceOptions.d.ts +1 -1
  131. package/dist/composables/formBuilderSchema.d.ts +18 -0
  132. package/dist/composables/index.js +3 -3
  133. package/dist/composables/pluginEndpointBuilder.d.ts +13 -0
  134. package/dist/composables/protocolTemplateCatalog.d.ts +26 -0
  135. package/dist/composables/useAutoGroup.d.ts +61 -74
  136. package/dist/composables/useAutoGroupInputSources.d.ts +32 -0
  137. package/dist/composables/useBioTemplateControls.d.ts +1 -1
  138. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +1 -1
  139. package/dist/composables/useBioTemplateWorkspace.d.ts +1 -1
  140. package/dist/composables/useControlSchema.d.ts +4 -316
  141. package/dist/composables/useControlWorkspace.d.ts +1 -1
  142. package/dist/composables/useForm.d.ts +2 -33
  143. package/dist/composables/useFormBuilder.d.ts +2 -9
  144. package/dist/composables/useFormValidation.d.ts +34 -0
  145. package/dist/composables/usePluginClient.d.ts +1 -4
  146. package/dist/composables/useProtocolTemplates.d.ts +2 -24
  147. package/dist/composables/useScheduleCalendarLayout.d.ts +49 -0
  148. package/dist/{composables-BMkPQhVK.js → composables-CHDjDIQT.js} +33 -31
  149. package/dist/composables-CHDjDIQT.js.map +1 -0
  150. package/dist/index.js +4 -4
  151. package/dist/install.js +2 -2
  152. package/dist/styles.css +1432 -660
  153. package/dist/templates/controlSchemaTypes.d.ts +1 -1
  154. package/dist/templates/index.js +1 -1
  155. package/dist/templates/templateAdapterTypes.d.ts +48 -0
  156. package/dist/templates/templateCreateOptions.d.ts +165 -0
  157. package/dist/templates/templateQpcrTypes.d.ts +42 -0
  158. package/dist/templates/types.d.ts +5 -250
  159. package/dist/{templates-bUAWMn5L.js → templates-DSbHJC4v.js} +1536 -297
  160. package/dist/templates-DSbHJC4v.js.map +1 -0
  161. package/dist/types/auto-group.d.ts +79 -9
  162. package/dist/types/componentLabTypes.d.ts +161 -0
  163. package/dist/types/componentWorkflowTypes.d.ts +150 -0
  164. package/dist/types/components.d.ts +14 -311
  165. package/dist/types/form-builder.d.ts +3 -0
  166. package/dist/types/index.d.ts +1 -1
  167. package/dist/{useProtocolTemplates-QZtHFFH2.js → useProtocolTemplates-BbPOYPzO.js} +1220 -454
  168. package/dist/useProtocolTemplates-BbPOYPzO.js.map +1 -0
  169. package/package.json +1 -1
  170. package/src/__tests__/components/AppSidebar.test.ts +67 -0
  171. package/src/__tests__/components/AppTopBar.navigation.test.ts +70 -0
  172. package/src/__tests__/components/CollapsibleCard.test.ts +47 -0
  173. package/src/__tests__/components/DoseCalculatorVolumeField.test.ts +53 -0
  174. package/src/__tests__/components/FormBuilder.test.ts +57 -0
  175. package/src/__tests__/components/PlateMapEditorToolbarInternal.test.ts +54 -0
  176. package/src/__tests__/components/PluginWorkspaceView.controls.test.ts +156 -0
  177. package/src/__tests__/components/PluginWorkspaceView.navigation.test.ts +102 -0
  178. package/src/__tests__/components/PluginWorkspaceView.shell.test.ts +43 -0
  179. package/src/__tests__/components/ProtocolStep.presentation.test.ts +31 -0
  180. package/src/__tests__/components/ProtocolStepEditor.state.test.ts +165 -0
  181. package/src/__tests__/components/ProtocolStepParameterField.test.ts +44 -0
  182. package/src/__tests__/components/ReagentList.presentation.test.ts +68 -0
  183. package/src/__tests__/components/SampleSelector.colors.test.ts +49 -0
  184. package/src/__tests__/components/SampleSelector.drag.test.ts +100 -0
  185. package/src/__tests__/components/SampleSelector.groups.test.ts +81 -0
  186. package/src/__tests__/components/SampleSelector.selection.test.ts +70 -0
  187. package/src/__tests__/components/SampleSelector.test.ts +32 -0
  188. package/src/__tests__/components/SampleSelectorSampleRow.test.ts +37 -0
  189. package/src/__tests__/components/ScheduleCalendar.test.ts +44 -0
  190. package/src/__tests__/components/SettingsModal.schema.test.ts +97 -0
  191. package/src/__tests__/components/WellPlate.colors.test.ts +28 -0
  192. package/src/__tests__/components/WellPlate.conditions.test.ts +68 -0
  193. package/src/__tests__/components/WellPlate.geometry.test.ts +54 -0
  194. package/src/__tests__/components/WellPlate.interaction.test.ts +171 -0
  195. package/src/__tests__/components/WellPlate.legend.test.ts +13 -0
  196. package/src/__tests__/components/WellPlate.rendering.test.ts +122 -0
  197. package/src/__tests__/components/WellPlate.sampleDrop.test.ts +70 -0
  198. package/src/__tests__/composables/autoGroup/classify.test.ts +107 -0
  199. package/src/__tests__/composables/autoGroup/columns.test.ts +135 -0
  200. package/src/__tests__/composables/autoGroup/compose.test.ts +227 -0
  201. package/src/__tests__/composables/autoGroup/cooccurrence.test.ts +91 -0
  202. package/src/__tests__/composables/autoGroup/fingerprint.test.ts +50 -0
  203. package/src/__tests__/composables/autoGroup/integration.test.ts +79 -0
  204. package/src/__tests__/composables/autoGroup/template.test.ts +70 -0
  205. package/src/__tests__/composables/autoGroup/tokenize.test.ts +33 -0
  206. package/src/__tests__/composables/useAutoGroup.test.ts +129 -625
  207. package/src/__tests__/composables/useAutoGroupInputSources.test.ts +107 -0
  208. package/src/__tests__/composables/useControlSchema.test.ts +23 -0
  209. package/src/__tests__/composables/useScheduleCalendarLayout.test.ts +89 -0
  210. package/src/__tests__/docs/extractDocsComponents.test.ts +142 -0
  211. package/src/__tests__/docs/extractDocsExports.test.ts +77 -0
  212. package/src/__tests__/docs/extractDocsParsing.test.ts +69 -0
  213. package/src/__tests__/docs/extractDocsTemplates.test.ts +54 -0
  214. package/src/__tests__/docs/extractDocsTheme.test.ts +89 -0
  215. package/src/__tests__/docs/frontendDocsCatalog.test.ts +1 -1
  216. package/src/__tests__/fixtures/auto-group/mixed-lc-ms-batch.txt +187 -0
  217. package/src/components/AppSidebar.story.vue +79 -6
  218. package/src/components/AppSidebar.vue +74 -6
  219. package/src/components/AppTopBar.navigation.ts +62 -0
  220. package/src/components/AppTopBar.vue +17 -44
  221. package/src/components/AutoGroupModal.story.vue +50 -0
  222. package/src/components/AutoGroupModal.vue +441 -158
  223. package/src/components/BaseCheckbox.story.vue +27 -0
  224. package/src/components/BaseCheckbox.vue +63 -1
  225. package/src/components/BaseToggle.story.vue +27 -0
  226. package/src/components/BaseToggle.vue +66 -1
  227. package/src/components/CollapsibleCard.vue +123 -45
  228. package/src/components/ControlWorkspaceView.vue +2 -6
  229. package/src/components/DoseCalculator.vue +13 -73
  230. package/src/components/DoseCalculatorVolumeField.vue +61 -0
  231. package/src/components/ExperimentTimeline.vue +6 -31
  232. package/src/components/FormBuilder.story.vue +13 -0
  233. package/src/components/FormBuilder.vue +2 -7
  234. package/src/components/PlateMapEditor.vue +32 -106
  235. package/src/components/PluginWorkspaceView.controls.ts +182 -0
  236. package/src/components/PluginWorkspaceView.navigation.ts +106 -0
  237. package/src/components/PluginWorkspaceView.props.ts +174 -0
  238. package/src/components/PluginWorkspaceView.shell.ts +67 -0
  239. package/src/components/PluginWorkspaceView.vue +88 -404
  240. package/src/components/ProtocolStep.presentation.ts +31 -0
  241. package/src/components/ProtocolStepEditor.state.ts +104 -0
  242. package/src/components/ProtocolStepEditor.vue +48 -179
  243. package/src/components/ProtocolStepParameterField.vue +134 -0
  244. package/src/components/ReagentList.presentation.ts +105 -0
  245. package/src/components/ReagentList.vue +16 -79
  246. package/src/components/SampleSelector.colors.ts +43 -0
  247. package/src/components/SampleSelector.drag.ts +164 -0
  248. package/src/components/SampleSelector.groups.ts +109 -0
  249. package/src/components/SampleSelector.selection.ts +103 -0
  250. package/src/components/SampleSelector.vue +82 -349
  251. package/src/components/SampleSelectorSampleRow.vue +64 -0
  252. package/src/components/ScheduleCalendar.vue +44 -199
  253. package/src/components/SettingsModal.schema.ts +71 -0
  254. package/src/components/SettingsModal.vue +16 -46
  255. package/src/components/WellPlate.colors.ts +56 -0
  256. package/src/components/WellPlate.conditions.ts +100 -0
  257. package/src/components/WellPlate.geometry.ts +91 -0
  258. package/src/components/WellPlate.interaction.ts +272 -0
  259. package/src/components/WellPlate.legend.ts +8 -0
  260. package/src/components/WellPlate.rendering.ts +105 -0
  261. package/src/components/WellPlate.sampleDrop.ts +73 -0
  262. package/src/components/WellPlate.vue +102 -550
  263. package/src/components/internal/FormFieldRendererInternal.vue +23 -5
  264. package/src/components/internal/PlateMapEditorToolbarInternal.vue +128 -0
  265. package/src/composables/autoGroup/classKey.ts +5 -0
  266. package/src/composables/autoGroup/classify.ts +205 -0
  267. package/src/composables/autoGroup/colors.ts +6 -0
  268. package/src/composables/autoGroup/columns.ts +226 -0
  269. package/src/composables/autoGroup/compose.ts +156 -0
  270. package/src/composables/autoGroup/cooccurrence.ts +46 -0
  271. package/src/composables/autoGroup/csv-shim.ts +44 -0
  272. package/src/composables/autoGroup/fingerprint.ts +49 -0
  273. package/src/composables/autoGroup/index.ts +20 -0
  274. package/src/composables/autoGroup/replicatePreGroup.ts +90 -0
  275. package/src/composables/autoGroup/template.ts +126 -0
  276. package/src/composables/autoGroup/tokenize.ts +41 -0
  277. package/src/composables/autoGroup/vocab.json +67 -0
  278. package/src/composables/autoGroupConstants.ts +4 -0
  279. package/src/composables/autoGroupGrouping.ts +148 -0
  280. package/src/composables/controlComponentBindings.ts +1 -1
  281. package/src/composables/controlSchemaAdapters.ts +4 -1
  282. package/src/composables/controlSchemaDoseDesign.ts +215 -0
  283. package/src/composables/controlSchemaFormFields.ts +4 -1
  284. package/src/composables/controlSchemaLayout.ts +1 -1
  285. package/src/composables/controlSchemaModel.ts +163 -0
  286. package/src/composables/controlSchemaNormalize.ts +1 -1
  287. package/src/composables/controlSchemaTypes.ts +372 -0
  288. package/src/composables/controlWorkspaceOptions.ts +1 -1
  289. package/src/composables/formBuilderSchema.ts +153 -0
  290. package/src/composables/pluginEndpointBuilder.ts +203 -0
  291. package/src/composables/protocolTemplateCatalog.ts +325 -0
  292. package/src/composables/useAutoGroup.ts +395 -549
  293. package/src/composables/useAutoGroupInputSources.ts +147 -0
  294. package/src/composables/useBioTemplateControls.ts +1 -1
  295. package/src/composables/useBioTemplatePresetWorkspace.ts +1 -1
  296. package/src/composables/useBioTemplateWorkspace.ts +1 -1
  297. package/src/composables/useControlSchema.ts +21 -692
  298. package/src/composables/useControlWorkspace.ts +7 -13
  299. package/src/composables/useForm.ts +5 -187
  300. package/src/composables/useFormBuilder.ts +11 -153
  301. package/src/composables/useFormValidation.ts +154 -0
  302. package/src/composables/usePluginClient.ts +10 -193
  303. package/src/composables/useProtocolTemplates.ts +10 -328
  304. package/src/composables/useScheduleCalendarLayout.ts +287 -0
  305. package/src/styles/components/app-sidebar.css +134 -6
  306. package/src/styles/components/auto-group-modal.css +248 -310
  307. package/src/styles/components/checkbox.css +87 -0
  308. package/src/styles/components/collapsible-card.css +154 -14
  309. package/src/styles/components/toggle.css +80 -0
  310. package/src/templates/controlSchemaTypes.ts +1 -1
  311. package/src/templates/templateAdapterTypes.ts +58 -0
  312. package/src/templates/templateCreateOptions.ts +208 -0
  313. package/src/templates/templateQpcrTypes.ts +48 -0
  314. package/src/templates/types.ts +79 -275
  315. package/src/types/auto-group.ts +107 -9
  316. package/src/types/componentLabTypes.ts +235 -0
  317. package/src/types/componentWorkflowTypes.ts +190 -0
  318. package/src/types/components.ts +95 -424
  319. package/src/types/form-builder.ts +3 -0
  320. package/src/types/index.ts +2 -0
  321. package/dist/components-DafPc4rM.js.map +0 -1
  322. package/dist/composables-BMkPQhVK.js.map +0 -1
  323. package/dist/templates-bUAWMn5L.js.map +0 -1
  324. package/dist/useProtocolTemplates-QZtHFFH2.js.map +0 -1
@@ -0,0 +1,104 @@
1
+ import type { ProtocolStep, ProtocolStepStatus } from '../types'
2
+ import type { ParameterDefinition, StepTemplate } from '../composables/useProtocolTemplates'
3
+
4
+ export interface ProtocolStepEditorFields {
5
+ name: string
6
+ description: string
7
+ duration?: number
8
+ parameters: Record<string, unknown>
9
+ }
10
+
11
+ export interface InitializedProtocolStepEditorState {
12
+ selectedTemplateId: string | null
13
+ fields: ProtocolStepEditorFields
14
+ }
15
+
16
+ export type ProtocolParameterFormatter = (
17
+ value: unknown,
18
+ parameter: ParameterDefinition
19
+ ) => string
20
+
21
+ export function buildProtocolTemplateDefaults(
22
+ template: StepTemplate
23
+ ): ProtocolStepEditorFields {
24
+ const parameters: Record<string, unknown> = {}
25
+ for (const parameter of template.parameters) {
26
+ if (parameter.default !== undefined) {
27
+ parameters[parameter.key] = parameter.default
28
+ }
29
+ }
30
+
31
+ return {
32
+ name: template.name,
33
+ description: template.description || '',
34
+ duration: template.defaultDuration,
35
+ parameters,
36
+ }
37
+ }
38
+
39
+ export function buildProtocolStepEditorState(
40
+ step: ProtocolStep,
41
+ getTemplateByType: (type: ProtocolStep['type']) => StepTemplate | undefined
42
+ ): InitializedProtocolStepEditorState {
43
+ const template = getTemplateByType(step.type)
44
+
45
+ return {
46
+ selectedTemplateId: template?.id || null,
47
+ fields: {
48
+ name: step.name,
49
+ description: step.description || '',
50
+ duration: step.duration,
51
+ parameters: { ...(step.parameters || {}) },
52
+ },
53
+ }
54
+ }
55
+
56
+ export function buildProtocolStepPreview(
57
+ template: StepTemplate,
58
+ fields: ProtocolStepEditorFields,
59
+ sourceStep?: ProtocolStep
60
+ ): ProtocolStep {
61
+ return {
62
+ id: sourceStep?.id || 'preview',
63
+ type: template.type,
64
+ name: fields.name || template.name,
65
+ description: fields.description,
66
+ duration: fields.duration ?? template.defaultDuration,
67
+ status: (sourceStep?.status || 'pending') as ProtocolStepStatus,
68
+ parameters: { ...fields.parameters },
69
+ order: sourceStep?.order || 0,
70
+ }
71
+ }
72
+
73
+ export function buildProtocolPreviewParams(
74
+ template: StepTemplate,
75
+ parameters: Record<string, unknown>,
76
+ formatParameterValue: ProtocolParameterFormatter
77
+ ): string {
78
+ return template.parameters
79
+ .filter((parameter) => (
80
+ parameters[parameter.key] !== undefined
81
+ && parameters[parameter.key] !== ''
82
+ ))
83
+ .map((parameter) => formatParameterValue(parameters[parameter.key], parameter))
84
+ .join(', ')
85
+ }
86
+
87
+ export function buildCustomProtocolTemplate(
88
+ template: StepTemplate,
89
+ fields: ProtocolStepEditorFields,
90
+ templateId = `custom-${Date.now()}`
91
+ ): StepTemplate {
92
+ return {
93
+ id: templateId,
94
+ type: template.type,
95
+ name: fields.name || template.name,
96
+ description: fields.description,
97
+ defaultDuration: fields.duration,
98
+ parameters: template.parameters.map((parameter) => ({
99
+ ...parameter,
100
+ default: fields.parameters[parameter.key],
101
+ })),
102
+ isBuiltIn: false,
103
+ }
104
+ }
@@ -1,14 +1,25 @@
1
1
  <script setup lang="ts">
2
2
  /** Form for creating or editing a single protocol step (incubation, wash, addition, centrifuge, etc.) with template picker, typed parameters, and duration. */
3
3
  import { ref, computed, watch, onMounted } from 'vue'
4
- import type { ProtocolStep, ProtocolStepType, ProtocolStepStatus } from '../types'
4
+ import type { ProtocolStep } from '../types'
5
5
  import { useDropdownState } from '../composables/useDropdownState'
6
6
  import {
7
7
  useProtocolTemplates,
8
8
  type StepTemplate,
9
9
  } from '../composables/useProtocolTemplates'
10
- import ConcentrationInput from './ConcentrationInput.vue'
11
- import type { ConcentrationValue } from '../composables/useConcentrationUnits'
10
+ import {
11
+ PROTOCOL_STEP_TYPE_ICONS as stepTypeIcons,
12
+ formatProtocolDuration as formatDuration,
13
+ } from './ProtocolStep.presentation'
14
+ import ProtocolStepParameterField from './ProtocolStepParameterField.vue'
15
+ import {
16
+ buildCustomProtocolTemplate,
17
+ buildProtocolPreviewParams,
18
+ buildProtocolStepEditorState,
19
+ buildProtocolStepPreview,
20
+ buildProtocolTemplateDefaults,
21
+ type ProtocolStepEditorFields,
22
+ } from './ProtocolStepEditor.state'
12
23
 
13
24
  interface Props {
14
25
  modelValue?: ProtocolStep
@@ -69,51 +80,41 @@ const selectedTemplate = computed(() => {
69
80
  return availableTemplates.value.find((t) => t.id === selectedTemplateId.value) || null
70
81
  })
71
82
 
72
- // Step type icons (same as ExperimentTimeline)
73
- const stepTypeIcons: Record<ProtocolStepType, string> = {
74
- incubation: 'M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z',
75
- wash: 'M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z',
76
- addition: 'M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z',
77
- measurement: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z',
78
- transfer: 'M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4',
79
- centrifuge: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15',
80
- mix: 'M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z',
81
- custom: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z',
82
- }
83
-
84
83
  // Preview computed
85
84
  const previewStep = computed<ProtocolStep | null>(() => {
86
85
  if (!selectedTemplate.value) return null
87
- return {
88
- id: props.modelValue?.id || 'preview',
89
- type: selectedTemplate.value.type,
90
- name: stepName.value || selectedTemplate.value.name,
91
- description: stepDescription.value,
92
- duration: stepDuration.value ?? selectedTemplate.value.defaultDuration,
93
- status: (props.modelValue?.status || 'pending') as ProtocolStepStatus,
94
- parameters: { ...parameters.value },
95
- order: props.modelValue?.order || 0,
96
- }
86
+ return buildProtocolStepPreview(selectedTemplate.value, editorFields(), props.modelValue)
97
87
  })
98
88
 
99
89
  const previewParams = computed(() => {
100
90
  if (!selectedTemplate.value || !previewStep.value) return ''
101
- return selectedTemplate.value.parameters
102
- .filter((p) => parameters.value[p.key] !== undefined && parameters.value[p.key] !== '')
103
- .map((p) => formatParameterValue(parameters.value[p.key], p))
104
- .join(', ')
91
+ return buildProtocolPreviewParams(
92
+ selectedTemplate.value,
93
+ parameters.value,
94
+ formatParameterValue
95
+ )
105
96
  })
106
97
 
107
- // Initialize from modelValue
108
- function initFromStep(step: ProtocolStep) {
109
- const template = getTemplateByType(step.type)
110
- if (template) {
111
- selectedTemplateId.value = template.id
98
+ function editorFields(): ProtocolStepEditorFields {
99
+ return {
100
+ name: stepName.value,
101
+ description: stepDescription.value,
102
+ duration: stepDuration.value,
103
+ parameters: parameters.value,
112
104
  }
113
- stepName.value = step.name
114
- stepDescription.value = step.description || ''
115
- stepDuration.value = step.duration
116
- parameters.value = { ...(step.parameters || {}) }
105
+ }
106
+
107
+ function applyEditorFields(fields: ProtocolStepEditorFields) {
108
+ stepName.value = fields.name
109
+ stepDescription.value = fields.description
110
+ stepDuration.value = fields.duration
111
+ parameters.value = { ...fields.parameters }
112
+ }
113
+
114
+ function initFromStep(step: ProtocolStep) {
115
+ const state = buildProtocolStepEditorState(step, getTemplateByType)
116
+ selectedTemplateId.value = state.selectedTemplateId
117
+ applyEditorFields(state.fields)
117
118
  }
118
119
 
119
120
  // Handle template selection
@@ -123,16 +124,7 @@ function selectTemplate(template: StepTemplate) {
123
124
 
124
125
  // Reset to template defaults if creating new
125
126
  if (props.mode === 'create') {
126
- stepName.value = template.name
127
- stepDescription.value = template.description || ''
128
- stepDuration.value = template.defaultDuration
129
-
130
- parameters.value = {}
131
- for (const param of template.parameters) {
132
- if (param.default !== undefined) {
133
- parameters.value[param.key] = param.default
134
- }
135
- }
127
+ applyEditorFields(buildProtocolTemplateDefaults(template))
136
128
  }
137
129
  }
138
130
 
@@ -145,14 +137,6 @@ function handleParamChange(key: string, value: unknown) {
145
137
  }
146
138
  }
147
139
 
148
- // Handle concentration change
149
- function handleConcentrationChange(key: string, value: ConcentrationValue | undefined) {
150
- parameters.value[key] = value
151
- if (validationErrors.value[key]) {
152
- delete validationErrors.value[key]
153
- }
154
- }
155
-
156
140
  // Validate and save
157
141
  function handleSave() {
158
142
  if (!selectedTemplate.value || !previewStep.value) return
@@ -169,21 +153,7 @@ function handleSave() {
169
153
  // Save as template
170
154
  function handleSaveTemplate() {
171
155
  if (!selectedTemplate.value) return
172
-
173
- const newTemplate: StepTemplate = {
174
- id: `custom-${Date.now()}`,
175
- type: selectedTemplate.value.type,
176
- name: stepName.value || selectedTemplate.value.name,
177
- description: stepDescription.value,
178
- defaultDuration: stepDuration.value,
179
- parameters: selectedTemplate.value.parameters.map((p) => ({
180
- ...p,
181
- default: parameters.value[p.key],
182
- })),
183
- isBuiltIn: false,
184
- }
185
-
186
- emit('save-template', newTemplate)
156
+ emit('save-template', buildCustomProtocolTemplate(selectedTemplate.value, editorFields()))
187
157
  }
188
158
 
189
159
  // Handle cancel
@@ -191,15 +161,6 @@ function handleCancel() {
191
161
  emit('cancel')
192
162
  }
193
163
 
194
- // Format duration
195
- function formatDuration(minutes: number | undefined): string {
196
- if (minutes === undefined) return ''
197
- if (minutes < 60) return `${minutes}m`
198
- const hours = Math.floor(minutes / 60)
199
- const mins = minutes % 60
200
- return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`
201
- }
202
-
203
164
  onMounted(() => {
204
165
  if (props.modelValue) {
205
166
  initFromStep(props.modelValue)
@@ -335,106 +296,14 @@ watch(
335
296
 
336
297
  <!-- Dynamic parameters -->
337
298
  <template v-if="selectedTemplate">
338
- <div
339
- v-for="(param, index) in selectedTemplate.parameters"
299
+ <ProtocolStepParameterField
300
+ v-for="param in selectedTemplate.parameters"
340
301
  :key="param.key"
341
- :class="[
342
- 'mint-protocol-editor__field',
343
- index % 2 === 0 && selectedTemplate.parameters[index + 1]
344
- ? ''
345
- : '',
346
- ]"
347
- >
348
- <label
349
- :class="[
350
- 'mint-protocol-editor__field-label',
351
- param.required ? 'mint-protocol-editor__field-label--required' : '',
352
- ]"
353
- >
354
- {{ param.label }}
355
- </label>
356
-
357
- <!-- Number input with unit -->
358
- <div
359
- v-if="param.type === 'number' || param.type === 'temperature' || param.type === 'duration'"
360
- class="mint-protocol-editor__input-unit"
361
- >
362
- <input
363
- type="number"
364
- :value="parameters[param.key]"
365
- :min="param.min"
366
- :max="param.max"
367
- :placeholder="param.placeholder || `Enter ${param.label.toLowerCase()}`"
368
- :class="[
369
- 'mint-protocol-editor__input',
370
- validationErrors[param.key] ? 'mint-protocol-editor__input--error' : '',
371
- ]"
372
- @input="handleParamChange(param.key, ($event.target as HTMLInputElement).valueAsNumber)"
373
- />
374
- <span v-if="param.unit" class="mint-protocol-editor__unit-suffix">
375
- {{ param.unit }}
376
- </span>
377
- </div>
378
-
379
- <!-- Text input -->
380
- <input
381
- v-else-if="param.type === 'text'"
382
- type="text"
383
- :value="parameters[param.key]"
384
- :placeholder="param.placeholder || `Enter ${param.label.toLowerCase()}`"
385
- :class="[
386
- 'mint-protocol-editor__input',
387
- validationErrors[param.key] ? 'mint-protocol-editor__input--error' : '',
388
- ]"
389
- @input="handleParamChange(param.key, ($event.target as HTMLInputElement).value)"
390
- />
391
-
392
- <!-- Select -->
393
- <select
394
- v-else-if="param.type === 'select'"
395
- :value="parameters[param.key]"
396
- :class="[
397
- 'mint-protocol-editor__select',
398
- validationErrors[param.key] ? 'mint-protocol-editor__input--error' : '',
399
- ]"
400
- @change="handleParamChange(param.key, ($event.target as HTMLSelectElement).value)"
401
- >
402
- <option value="" disabled>Select {{ param.label.toLowerCase() }}</option>
403
- <option
404
- v-for="option in param.options"
405
- :key="option.value"
406
- :value="option.value"
407
- >
408
- {{ option.label }}
409
- </option>
410
- </select>
411
-
412
- <!-- Concentration input -->
413
- <ConcentrationInput
414
- v-else-if="param.type === 'concentration'"
415
- :model-value="parameters[param.key] as ConcentrationValue | undefined"
416
- :error="!!validationErrors[param.key]"
417
- size="md"
418
- @update:model-value="handleConcentrationChange(param.key, $event)"
419
- />
420
-
421
- <!-- Reagent input (text for now) -->
422
- <input
423
- v-else-if="param.type === 'reagent'"
424
- type="text"
425
- :value="parameters[param.key]"
426
- :placeholder="param.placeholder || 'Enter reagent name'"
427
- :class="[
428
- 'mint-protocol-editor__input',
429
- validationErrors[param.key] ? 'mint-protocol-editor__input--error' : '',
430
- ]"
431
- @input="handleParamChange(param.key, ($event.target as HTMLInputElement).value)"
432
- />
433
-
434
- <span v-if="validationErrors[param.key]" class="mint-protocol-editor__field-error">
435
- {{ validationErrors[param.key] }}
436
- </span>
437
- </div>
302
+ :parameter="param"
303
+ :model-value="parameters[param.key]"
304
+ :error="validationErrors[param.key]"
305
+ @update:model-value="handleParamChange(param.key, $event)"
306
+ />
438
307
  </template>
439
308
 
440
309
  <!-- Description -->
@@ -0,0 +1,134 @@
1
+ <script setup lang="ts">
2
+ /** Renders one typed parameter control for ProtocolStepEditor. */
3
+ import { computed } from 'vue'
4
+ import type { ParameterDefinition } from '../composables/useProtocolTemplates'
5
+ import type { ConcentrationValue } from '../composables/useConcentrationUnits'
6
+ import ConcentrationInput from './ConcentrationInput.vue'
7
+
8
+ interface Props {
9
+ parameter: ParameterDefinition
10
+ modelValue?: unknown
11
+ error?: string
12
+ }
13
+
14
+ const props = defineProps<Props>()
15
+
16
+ const emit = defineEmits<{
17
+ 'update:modelValue': [value: unknown]
18
+ }>()
19
+
20
+ const scalarValue = computed(() => (
21
+ props.modelValue as string | number | undefined
22
+ ))
23
+
24
+ const concentrationValue = computed(() => (
25
+ props.modelValue as ConcentrationValue | undefined
26
+ ))
27
+
28
+ const hasError = computed(() => !!props.error)
29
+
30
+ function handleNumberInput(event: Event) {
31
+ emit('update:modelValue', (event.target as HTMLInputElement).valueAsNumber)
32
+ }
33
+
34
+ function handleTextInput(event: Event) {
35
+ emit('update:modelValue', (event.target as HTMLInputElement).value)
36
+ }
37
+
38
+ function handleSelectChange(event: Event) {
39
+ emit('update:modelValue', (event.target as HTMLSelectElement).value)
40
+ }
41
+
42
+ function handleConcentrationChange(value: ConcentrationValue | undefined) {
43
+ emit('update:modelValue', value)
44
+ }
45
+ </script>
46
+
47
+ <template>
48
+ <div class="mint-protocol-editor__field">
49
+ <label
50
+ :class="[
51
+ 'mint-protocol-editor__field-label',
52
+ parameter.required ? 'mint-protocol-editor__field-label--required' : '',
53
+ ]"
54
+ >
55
+ {{ parameter.label }}
56
+ </label>
57
+
58
+ <div
59
+ v-if="parameter.type === 'number' || parameter.type === 'temperature' || parameter.type === 'duration'"
60
+ class="mint-protocol-editor__input-unit"
61
+ >
62
+ <input
63
+ type="number"
64
+ :value="scalarValue"
65
+ :min="parameter.min"
66
+ :max="parameter.max"
67
+ :placeholder="parameter.placeholder || `Enter ${parameter.label.toLowerCase()}`"
68
+ :class="[
69
+ 'mint-protocol-editor__input',
70
+ hasError ? 'mint-protocol-editor__input--error' : '',
71
+ ]"
72
+ @input="handleNumberInput"
73
+ />
74
+ <span v-if="parameter.unit" class="mint-protocol-editor__unit-suffix">
75
+ {{ parameter.unit }}
76
+ </span>
77
+ </div>
78
+
79
+ <input
80
+ v-else-if="parameter.type === 'text'"
81
+ type="text"
82
+ :value="scalarValue"
83
+ :placeholder="parameter.placeholder || `Enter ${parameter.label.toLowerCase()}`"
84
+ :class="[
85
+ 'mint-protocol-editor__input',
86
+ hasError ? 'mint-protocol-editor__input--error' : '',
87
+ ]"
88
+ @input="handleTextInput"
89
+ />
90
+
91
+ <select
92
+ v-else-if="parameter.type === 'select'"
93
+ :value="scalarValue"
94
+ :class="[
95
+ 'mint-protocol-editor__select',
96
+ hasError ? 'mint-protocol-editor__input--error' : '',
97
+ ]"
98
+ @change="handleSelectChange"
99
+ >
100
+ <option value="" disabled>Select {{ parameter.label.toLowerCase() }}</option>
101
+ <option
102
+ v-for="option in parameter.options"
103
+ :key="option.value"
104
+ :value="option.value"
105
+ >
106
+ {{ option.label }}
107
+ </option>
108
+ </select>
109
+
110
+ <ConcentrationInput
111
+ v-else-if="parameter.type === 'concentration'"
112
+ :model-value="concentrationValue"
113
+ :error="hasError"
114
+ size="md"
115
+ @update:model-value="handleConcentrationChange"
116
+ />
117
+
118
+ <input
119
+ v-else-if="parameter.type === 'reagent'"
120
+ type="text"
121
+ :value="scalarValue"
122
+ :placeholder="parameter.placeholder || 'Enter reagent name'"
123
+ :class="[
124
+ 'mint-protocol-editor__input',
125
+ hasError ? 'mint-protocol-editor__input--error' : '',
126
+ ]"
127
+ @input="handleTextInput"
128
+ />
129
+
130
+ <span v-if="error" class="mint-protocol-editor__field-error">
131
+ {{ error }}
132
+ </span>
133
+ </div>
134
+ </template>
@@ -0,0 +1,105 @@
1
+ import type { Reagent, ReagentColumn } from '../types'
2
+
3
+ export const REAGENT_COLUMN_LABELS: Record<ReagentColumn, string> = {
4
+ name: 'Name',
5
+ catalog: 'Catalog #',
6
+ lot: 'Lot #',
7
+ expiry: 'Expiry',
8
+ storage: 'Storage',
9
+ location: 'Location',
10
+ stock: 'Stock',
11
+ supplier: 'Supplier',
12
+ }
13
+
14
+ export interface ReagentRowClassOptions {
15
+ lowStockThreshold: number
16
+ draggedId?: string | null
17
+ dragOverId?: string | null
18
+ now?: Date
19
+ }
20
+
21
+ export function getReagentColumnValue(reagent: Reagent, column: ReagentColumn): unknown {
22
+ switch (column) {
23
+ case 'name':
24
+ return reagent.name
25
+ case 'catalog':
26
+ return reagent.catalogNumber
27
+ case 'lot':
28
+ return reagent.lotNumber
29
+ case 'expiry':
30
+ return reagent.expiryDate ? new Date(reagent.expiryDate).getTime() : null
31
+ case 'storage':
32
+ return reagent.storageCondition
33
+ case 'location':
34
+ return reagent.location
35
+ case 'stock':
36
+ return reagent.stockLevel
37
+ case 'supplier':
38
+ return reagent.supplier
39
+ default:
40
+ return null
41
+ }
42
+ }
43
+
44
+ export function isReagentExpired(reagent: Reagent, now = new Date()): boolean {
45
+ if (!reagent.expiryDate) return false
46
+ const expiry = new Date(reagent.expiryDate)
47
+ return expiry < now
48
+ }
49
+
50
+ export function isReagentExpiringSoon(
51
+ reagent: Reagent,
52
+ daysThreshold = 30,
53
+ now = new Date()
54
+ ): boolean {
55
+ if (!reagent.expiryDate) return false
56
+ const expiry = new Date(reagent.expiryDate)
57
+ const daysUntilExpiry = (expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
58
+ return daysUntilExpiry > 0 && daysUntilExpiry <= daysThreshold
59
+ }
60
+
61
+ export function isReagentLowStock(reagent: Reagent, lowStockThreshold: number): boolean {
62
+ if (reagent.stockLevel === undefined) return false
63
+ return reagent.stockLevel <= lowStockThreshold
64
+ }
65
+
66
+ export function formatReagentExpiryDate(date: Date | string | undefined): string {
67
+ if (!date) return '-'
68
+ const parsed = new Date(date)
69
+ return parsed.toLocaleDateString('en-US', { year: 'numeric', month: 'short' })
70
+ }
71
+
72
+ export function getReagentStockLevel(reagent: Reagent): number {
73
+ return reagent.stockLevel ?? 0
74
+ }
75
+
76
+ export function getReagentStockFillClass(level: number): string {
77
+ if (level >= 50) return 'mint-reagent-list__stock-fill--good'
78
+ if (level >= 20) return 'mint-reagent-list__stock-fill--warning'
79
+ return 'mint-reagent-list__stock-fill--low'
80
+ }
81
+
82
+ export function getReagentRowClasses(
83
+ reagent: Reagent,
84
+ options: ReagentRowClassOptions
85
+ ): string[] {
86
+ const classes: string[] = ['mint-reagent-list__row']
87
+
88
+ if (isReagentExpired(reagent, options.now)) {
89
+ classes.push('mint-reagent-list__row--expired')
90
+ } else if (isReagentExpiringSoon(reagent, 30, options.now)) {
91
+ classes.push('mint-reagent-list__row--expiring-soon')
92
+ }
93
+
94
+ if (isReagentLowStock(reagent, options.lowStockThreshold)) {
95
+ classes.push('mint-reagent-list__row--low-stock')
96
+ }
97
+ if (options.draggedId === reagent.id) {
98
+ classes.push('mint-reagent-list__row--dragging')
99
+ }
100
+ if (options.dragOverId === reagent.id) {
101
+ classes.push('mint-reagent-list__row--drag-over')
102
+ }
103
+
104
+ return classes
105
+ }