From 9f8dc8f39262e9898a404eba4b3f47a4cd4dc248 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 12 Dec 2024 13:23:25 +0100 Subject: [PATCH 1/9] feat: redesigned form-tabs (#962) * feat: redesigned form-tabs * feat: restructured internal data representation & handling * fix: minor fix for submitting mutation payload * fix: add missing css file to portal --- packages/core/package.json | 3 +- .../utility/form-tab/DFormTabGroup.vue | 94 ++++++---- .../utility/form-tab/DFormTabGroups.vue | 174 +++++++++++------- .../src/components/utility/form-tab/index.ts | 2 + .../src/components/utility/form-tab/types.ts | 14 ++ .../components/core/MMutationTabGroup.vue | 63 ++++--- .../query-criteria/MQueryCriteriaModal.vue | 2 +- .../components/core/search/MSearchForm.vue | 60 +++--- .../mtb/src/runtime/domains/query/helper.ts | 24 +++ .../mtb/src/runtime/domains/query/index.ts | 1 + packages/portal/nuxt.config.ts | 1 + .../runtime/components/core/RSearchForm.vue | 31 +++- .../components/core/RVariantFormTabGroup.vue | 40 ++-- .../rd/src/runtime/domains/query/types.ts | 2 +- rollup.config.mjs | 26 +-- 15 files changed, 343 insertions(+), 194 deletions(-) create mode 100644 packages/core/src/components/utility/form-tab/types.ts create mode 100644 packages/mtb/src/runtime/domains/query/helper.ts diff --git a/packages/core/package.json b/packages/core/package.json index 6bbbea6b..4a5414b7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -8,7 +8,8 @@ "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.cjs" - } + }, + "./index.css": "./dist/index.css" }, "main": "./dist/index.cjs", "module": "./dist/index.mjs", diff --git a/packages/core/src/components/utility/form-tab/DFormTabGroup.vue b/packages/core/src/components/utility/form-tab/DFormTabGroup.vue index acbf2d46..151d91b6 100644 --- a/packages/core/src/components/utility/form-tab/DFormTabGroup.vue +++ b/packages/core/src/components/utility/form-tab/DFormTabGroup.vue @@ -1,68 +1,94 @@ + + +
+ - {{ text }} - - + + +
diff --git a/packages/core/src/components/utility/form-tab/DFormTabGroups.vue b/packages/core/src/components/utility/form-tab/DFormTabGroups.vue index 231a7bd1..d8313baf 100644 --- a/packages/core/src/components/utility/form-tab/DFormTabGroups.vue +++ b/packages/core/src/components/utility/form-tab/DFormTabGroups.vue @@ -3,20 +3,21 @@ import { type PropType, defineComponent, ref, toRef, watch, } from 'vue'; import DFormTabGroup from './DFormTabGroup.vue'; +import type { FormTab, FormTabInput } from './types'; export default defineComponent({ components: { DFormTabGroup }, props: { modelValue: { - type: Array as PropType[]>, + type: Array as PropType, }, - defaultLabel: { + label: { type: String, - default: 'Group', + default: 'Neuer Tab', }, minItems: { type: Number, - default: 0, + default: 1, }, maxItems: { type: Number, @@ -29,26 +30,35 @@ export default defineComponent({ type: String as PropType<'row' | 'col'>, default: 'row', }, - label: { - type: String, - default: undefined, - }, }, emits: ['update:modelValue'], setup(props, { emit }) { - const items = ref[]>([]); + const items = ref([]); const modelValue = toRef(props, 'modelValue'); const currentIndex = ref(-1); const add = () => { - items.value.push({}); + items.value.push({ + data: null, + label: props.label, + index: items.value.length, + }); + currentIndex.value = items.value.length - 1; }; const init = () => { if (!props.modelValue) return; - items.value = props.modelValue; + items.value = props.modelValue.map((el, index) => { + const item: FormTab = { + data: el.data ?? null, + index, + label: el.label ? el.label : props.label, + }; + + return item; + }); if ( items.value.length > 0 && @@ -77,46 +87,55 @@ export default defineComponent({ init(); }, { deep: true }); - const toggle = (index: number) => { - if (currentIndex.value === index) { - if (items.value.length <= props.minItems) { - return; + const pick = (index: number) => { + currentIndex.value = index; + }; + + const close = (index: number) => { + if (items.value.length === props.minItems) { + if (index === 0 && items.value.length === 1) { + items.value[index].data = null; + items.value[index].label = props.label; } - const nextIndex = index - 1; + emit('update:modelValue', items.value); - currentIndex.value = nextIndex < 0 ? 0 : nextIndex; + return; + } - if (items.value.length > 0) { - items.value.splice(index, 1); - } - } else { - currentIndex.value = index; + if (items.value.length > 0) { + items.value.splice(index, 1); } - }; - const toggleCurrent = () => { - toggle(currentIndex.value); + for (let i = 0; i < items.value.length; i++) { + items.value[i].index = i; + } + + const nextIndex = index - 1; + currentIndex.value = nextIndex < 0 ? 0 : nextIndex; + + emit('update:modelValue', items.value); }; - const handleUpdated = (data: Record) => { - if (currentIndex.value === -1) { - items.value.push({ ...data }); - currentIndex.value = 0; - } else { - items.value[currentIndex.value] = { ...data }; - } + const handleSaved = (data: FormTab) => { + currentIndex.value = data.index; + + items.value[data.index] = { + ...items.value[data.index], + ...data, + }; emit('update:modelValue', items.value); }; return { currentIndex, - handleUpdated, + handleSaved, items, + add, - toggle, - toggleCurrent, + close, + pick, }; }, }); @@ -129,9 +148,8 @@ export default defineComponent({ >
-
- +
+
+ diff --git a/packages/core/src/components/utility/form-tab/index.ts b/packages/core/src/components/utility/form-tab/index.ts index fa259ef4..852471e3 100644 --- a/packages/core/src/components/utility/form-tab/index.ts +++ b/packages/core/src/components/utility/form-tab/index.ts @@ -1 +1,3 @@ export { default as DFormTabGroups } from './DFormTabGroups.vue'; + +export * from './types'; diff --git a/packages/core/src/components/utility/form-tab/types.ts b/packages/core/src/components/utility/form-tab/types.ts new file mode 100644 index 00000000..faa4fd58 --- /dev/null +++ b/packages/core/src/components/utility/form-tab/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2024. + * Author Peter Placzek (tada5hi) + * For the full copyright and license information, + * view the LICENSE file that was distributed with this source code. + */ + +export type FormTab> = { + data: T | null, + label: string, + index: number +}; + +export type FormTabInput> = Partial>; diff --git a/packages/mtb/src/runtime/components/core/MMutationTabGroup.vue b/packages/mtb/src/runtime/components/core/MMutationTabGroup.vue index d3fc64da..5d15ddc0 100644 --- a/packages/mtb/src/runtime/components/core/MMutationTabGroup.vue +++ b/packages/mtb/src/runtime/components/core/MMutationTabGroup.vue @@ -2,9 +2,7 @@ import { type CodeSystemConcept, DCodeSystem, - DCollectionTransform, - toCoding, - transformConceptToFormSelectOption, + DCollectionTransform, type FormTabInput, } from '@dnpm-dip/core'; import { VCFormSelect, VCFormSelectSearch } from '@vuecs/form-controls'; import type { FormSelectOption } from '@vuecs/form-controls'; @@ -18,6 +16,7 @@ import { type QueryGeneAlterationCriteria, type QueryGeneAlterationVariantCriteria, QueryMutationType, + buildQueryGeneAlterationCriteriaLabel, } from '../../domains'; import MSearchCNVForm from './search/MSearchCNVForm.vue'; import MSearchFusionForm from './search/MSearchFusionForm.vue'; @@ -27,10 +26,11 @@ export default defineComponent({ components: { DCollectionTransform, VCFormSelectSearch, DCodeSystem, VCFormSelect, }, - emit: ['updated', 'toggle'], + emit: ['saved'], props: { entity: { - type: Object as PropType, + type: Object as PropType>, + required: true, }, }, setup(props, { emit }) { @@ -98,35 +98,41 @@ export default defineComponent({ changeMutationType((event.target as Record).value || null); }; - const isEditing = computed(() => !!props.entity && Object.keys(props.entity).length > 0); + const isEditing = computed(() => props.entity.data !== null); const init = () => { - if (!props.entity) { - return; - } - if ( - props.entity.variant && - props.entity.variant.type + props.entity.data && + props.entity.data.variant && + props.entity.data.variant.type ) { - changeMutationType(props.entity.variant.type); + changeMutationType(props.entity.data.variant.type); } else { changeMutationType(null); } - if (props.entity.gene) { - form.gene = `${props.entity.gene.code}+++${props.entity.gene.display}`; + if ( + props.entity.data && + props.entity.data.gene + ) { + form.gene = `${props.entity.data.gene.code}+++${props.entity.data.gene.display}`; } else { form.gene = ''; } - if (typeof props.entity.supporting !== 'undefined') { - form.supporting = props.entity.supporting; + if ( + props.entity.data && + typeof props.entity.data.supporting !== 'undefined' + ) { + form.supporting = props.entity.data.supporting; } else { form.supporting = false; } - if (typeof props.entity.negated !== 'undefined') { - form.negated = props.entity.negated; + if ( + props.entity.data && + typeof props.entity.data.negated !== 'undefined' + ) { + form.negated = props.entity.data.negated; } else { form.negated = false; } @@ -141,14 +147,21 @@ export default defineComponent({ const submit = () => { if (form.gene) { const [code, display] = form.gene.split('+++'); - emit('updated', { + + const data : QueryGeneAlterationCriteria = { gene: { code, display }, supporting: form.supporting, negated: form.negated, ...(mutationData.value ? { variant: mutationData.value } : {}), - } satisfies QueryGeneAlterationCriteria); + }; + + emit('saved', { + ...props.entity, + data, + label: buildQueryGeneAlterationCriteriaLabel(data), + }); } else { - emit('toggle'); + emit('saved', props.entity); } }; @@ -236,7 +249,7 @@ export default defineComponent({ @@ -262,9 +275,9 @@ export default defineComponent({ diff --git a/packages/mtb/src/runtime/components/core/query-criteria/MQueryCriteriaModal.vue b/packages/mtb/src/runtime/components/core/query-criteria/MQueryCriteriaModal.vue index be4cd10a..bc4fe3bb 100644 --- a/packages/mtb/src/runtime/components/core/query-criteria/MQueryCriteriaModal.vue +++ b/packages/mtb/src/runtime/components/core/query-criteria/MQueryCriteriaModal.vue @@ -62,7 +62,7 @@ export default defineComponent({ - - - - - - - - - +
+
+ + + + +
+
+ + + + +
+
From d89a33515b8b3cf291ae56174e01aaa94700dc55 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 9 Jan 2025 12:16:39 +0100 Subject: [PATCH 7/9] feat: implemented prepared queries for mtb & rd (#980) --- packages/core/src/components/core/index.ts | 1 + .../core/prepared-query/DPreparedQueries.ts} | 25 ++- .../core/prepared-query/DPreparedQuery.ts} | 23 ++- .../prepared-query/DPreparedQueryForm.vue} | 144 +++++++++------- .../components/core/prepared-query/index.ts | 12 ++ packages/core/src/domains/index.ts | 1 + .../core/src/domains/prepared-query/api.ts | 59 +++++++ .../src}/domains/prepared-query/index.ts | 0 .../core/src/domains/prepared-query/types.ts | 12 ++ .../components/core/search/MSearchForm.vue | 126 ++++++++------ .../src/runtime/core/http-client/module.ts | 5 +- packages/mtb/src/runtime/pages/index.vue | 20 ++- packages/portal/nuxt.config.ts | 7 + .../runtime/components/core/RSearchForm.vue | 159 +++++------------- .../rd/src/runtime/core/http-client/module.ts | 4 +- packages/rd/src/runtime/domains/index.ts | 1 - .../src/runtime/domains/prepared-query/api.ts | 34 ---- .../runtime/domains/prepared-query/types.ts | 14 -- packages/rd/src/runtime/pages/index.vue | 37 ++-- .../rd/src/runtime/pages/query/[id]/index.vue | 2 +- 20 files changed, 376 insertions(+), 310 deletions(-) rename packages/{rd/src/runtime/components/core/RPreparedQueries.ts => core/src/components/core/prepared-query/DPreparedQueries.ts} (52%) rename packages/{rd/src/runtime/components/core/RPreparedQuery.ts => core/src/components/core/prepared-query/DPreparedQuery.ts} (58%) rename packages/{rd/src/runtime/components/core/RPreparedQueryForm.vue => core/src/components/core/prepared-query/DPreparedQueryForm.vue} (66%) create mode 100644 packages/core/src/components/core/prepared-query/index.ts create mode 100644 packages/core/src/domains/prepared-query/api.ts rename packages/{rd/src/runtime => core/src}/domains/prepared-query/index.ts (100%) create mode 100644 packages/core/src/domains/prepared-query/types.ts delete mode 100644 packages/rd/src/runtime/domains/prepared-query/api.ts delete mode 100644 packages/rd/src/runtime/domains/prepared-query/types.ts diff --git a/packages/core/src/components/core/index.ts b/packages/core/src/components/core/index.ts index 8db12ec3..91ee3669 100644 --- a/packages/core/src/components/core/index.ts +++ b/packages/core/src/components/core/index.ts @@ -2,6 +2,7 @@ export * from './connection-peer'; export * from './code-system'; export * from './coding'; export * from './patient'; +export * from './prepared-query'; export * from './query'; export * from './site'; export * from './value-set'; diff --git a/packages/rd/src/runtime/components/core/RPreparedQueries.ts b/packages/core/src/components/core/prepared-query/DPreparedQueries.ts similarity index 52% rename from packages/rd/src/runtime/components/core/RPreparedQueries.ts rename to packages/core/src/components/core/prepared-query/DPreparedQueries.ts index 29059d50..3d1232f7 100644 --- a/packages/rd/src/runtime/components/core/RPreparedQueries.ts +++ b/packages/core/src/components/core/prepared-query/DPreparedQueries.ts @@ -1,19 +1,32 @@ -import type { ResourceCollectionSlots } from '@dnpm-dip/core'; -import { createResourceCollectionManager, defineResourceCollectionEvents } from '@dnpm-dip/core'; import type { SlotsType } from 'vue'; import { defineComponent } from 'vue'; -import { injectHTTPClient } from '../../core'; -import type { PreparedQuery } from '../../domains'; +import type { + ResourceCollectionSlots, +} from '../../../core'; +import { + createResourceCollectionManager, + defineResourceCollectionEvents, + injectHTTPClient, +} from '../../../core'; +import type { PreparedQuery } from '../../../domains'; +import { PreparedQueryAPI } from '../../../domains'; export default defineComponent({ slots: Object as SlotsType>, + props: { + useCase: { + type: String, + required: true, + }, + }, emits: defineResourceCollectionEvents(), setup(props, setup) { const api = injectHTTPClient(); + const preparedQueryAPI = new PreparedQueryAPI({ client: api, useCase: props.useCase }); const manager = createResourceCollectionManager({ - load: async (meta) => { - const response = await api.preparedQuery.getMany(); + load: async () => { + const response = await preparedQueryAPI.getMany(); return { data: response.entries, diff --git a/packages/rd/src/runtime/components/core/RPreparedQuery.ts b/packages/core/src/components/core/prepared-query/DPreparedQuery.ts similarity index 58% rename from packages/rd/src/runtime/components/core/RPreparedQuery.ts rename to packages/core/src/components/core/prepared-query/DPreparedQuery.ts index 9764ac82..854e82fe 100644 --- a/packages/rd/src/runtime/components/core/RPreparedQuery.ts +++ b/packages/core/src/components/core/prepared-query/DPreparedQuery.ts @@ -1,9 +1,15 @@ -import type { ResourceRecordSlots } from '@dnpm-dip/core'; -import { createResourceRecordManager, defineResourceRecordEvents } from '@dnpm-dip/core'; import type { PropType, SlotsType } from 'vue'; import { defineComponent, toRef } from 'vue'; -import { injectHTTPClient } from '../../core'; -import type { PreparedQuery } from '../../domains'; +import type { + ResourceRecordSlots, +} from '../../../core'; +import { + createResourceRecordManager, + defineResourceRecordEvents, + injectHTTPClient, +} from '../../../core'; +import type { PreparedQuery } from '../../../domains'; +import { PreparedQueryAPI } from '../../../domains'; export default defineComponent({ props: { @@ -13,17 +19,22 @@ export default defineComponent({ entityId: { type: String, }, + useCase: { + type: String, + required: true, + }, }, emits: defineResourceRecordEvents(), slots: Object as SlotsType>, async setup(props, setup) { const api = injectHTTPClient(); + const preparedQueryAPI = new PreparedQueryAPI({ client: api, useCase: props.useCase }); const id = toRef(props, 'entityId'); const data = toRef(props, 'entity'); const manager = createResourceRecordManager({ - load: async (id) => api.preparedQuery.getOne(id), - delete: async (id) => api.preparedQuery.delete(id), + load: async (id) => preparedQueryAPI.getOne(id), + delete: async (id) => preparedQueryAPI.delete(id), slots: setup.slots, expose: setup.expose, data, diff --git a/packages/rd/src/runtime/components/core/RPreparedQueryForm.vue b/packages/core/src/components/core/prepared-query/DPreparedQueryForm.vue similarity index 66% rename from packages/rd/src/runtime/components/core/RPreparedQueryForm.vue rename to packages/core/src/components/core/prepared-query/DPreparedQueryForm.vue index f35959a1..21509d85 100644 --- a/packages/rd/src/runtime/components/core/RPreparedQueryForm.vue +++ b/packages/core/src/components/core/prepared-query/DPreparedQueryForm.vue @@ -1,21 +1,31 @@