diff --git a/packages/common-ui/lib/api-client/useQuery.tsx b/packages/common-ui/lib/api-client/useQuery.tsx index 907496189..f0ae23dc9 100644 --- a/packages/common-ui/lib/api-client/useQuery.tsx +++ b/packages/common-ui/lib/api-client/useQuery.tsx @@ -93,8 +93,6 @@ export function useQuery( ); } - await onSuccess?.(response); - if (joinSpecs) { const { data } = response; const resources = isArray(data) ? data : [data]; @@ -104,6 +102,8 @@ export function useQuery( } } + await onSuccess?.(response); + return response; } diff --git a/packages/dina-ui/components/seqdb/metagenomics-workflow/MetagenomicsIndexAssignmentStep.tsx b/packages/dina-ui/components/seqdb/metagenomics-workflow/MetagenomicsIndexAssignmentStep.tsx index bf179620c..3721e491b 100644 --- a/packages/dina-ui/components/seqdb/metagenomics-workflow/MetagenomicsIndexAssignmentStep.tsx +++ b/packages/dina-ui/components/seqdb/metagenomics-workflow/MetagenomicsIndexAssignmentStep.tsx @@ -1,3 +1,100 @@ -export function MetagenomicsIndexAssignmentStep() { - return <>; +import { PersistedResource } from "kitsu"; +import { PcrBatch } from "packages/dina-ui/types/seqdb-api"; +import { Dispatch, SetStateAction } from "react"; +import Col from "react-bootstrap/Col"; +import Nav from "react-bootstrap/Nav"; +import Row from "react-bootstrap/Row"; +import Tab from "react-bootstrap/Tab"; +import { useSeqdbIntl } from "packages/dina-ui/intl/seqdb-intl"; +import { useLocalStorage } from "@rehooks/local-storage"; +import { MetagenomicsBatch } from "packages/dina-ui/types/seqdb-api/resources/metagenomics/MetagenomicsBatch"; +import { useMetagenomicsIndexAssignmentAPI } from "../ngs-workflow/useMetagenomicsIndexAssignmentAPI"; +import { MetagenomicsIndexGrid } from "../ngs-workflow/index-grid/MetagenomicsIndexGrid"; +import { MetagenomicsIndexAssignmentTable } from "../ngs-workflow/MetagenomicsIndexAssignmentTable"; + +export interface MetagenomicsIndexAssignmentStepProps { + pcrBatchId: string; + pcrBatch: PcrBatch; + metagenomicsBatchId: string; + metagenomicsBatch: MetagenomicsBatch; + onSaved: ( + nextStep: number, + batchSaved?: PersistedResource + ) => Promise; + editMode: boolean; + setEditMode: Dispatch>; + performSave: boolean; + setPerformSave: (newValue: boolean) => void; +} + +export function MetagenomicsIndexAssignmentStep( + props: MetagenomicsIndexAssignmentStepProps +) { + const { formatMessage } = useSeqdbIntl(); + // Get the last active tab from local storage (defaults to "assignByGrid") + const [activeKey, setActiveKey] = useLocalStorage( + "metagenomicsIndexAssignmentStep_activeTab", + "assignByGrid" + ); + + // Data required for both options is pretty much the same so share the data between both. + const metagenomicsIndexAssignmentApiProps = + useMetagenomicsIndexAssignmentAPI(props); + + const handleSelect = (eventKey) => { + // Do not switch modes if in edit mode. This is used to prevent data from being mixed up. + if (props.editMode) { + return; + } + + setActiveKey(eventKey); + }; + return ( + + + + + + + + + + + + + + + + + + ); } diff --git a/packages/dina-ui/components/seqdb/ngs-workflow/MetagenomicsIndexAssignmentTable.tsx b/packages/dina-ui/components/seqdb/ngs-workflow/MetagenomicsIndexAssignmentTable.tsx new file mode 100644 index 000000000..63822b5a0 --- /dev/null +++ b/packages/dina-ui/components/seqdb/ngs-workflow/MetagenomicsIndexAssignmentTable.tsx @@ -0,0 +1,213 @@ +import { + DinaForm, + LoadingSpinner, + ReactTable, + SelectField, + SelectOption, + SubmitButton, + useStringComparator +} from "packages/common-ui/lib"; +import { IndexAssignmentStepProps } from "./IndexAssignmentStep"; +import { useMemo } from "react"; +import { PcrBatchItem } from "packages/dina-ui/types/seqdb-api"; +import { ColumnDef } from "@tanstack/react-table"; +import { MaterialSampleSummary } from "packages/dina-ui/types/collection-api"; +import { UseIndexAssignmentReturn } from "./useIndexAssignmentAPI"; +import { useSeqdbIntl } from "packages/dina-ui/intl/seqdb-intl"; +import { MetagenomicsBatchItem } from "packages/dina-ui/types/seqdb-api/resources/metagenomics/MetagenomicsBatchItem"; +import { MetagenomicsIndexAssignmentStepProps } from "../metagenomics-workflow/MetagenomicsIndexAssignmentStep"; +import { + MetagenomicsIndexAssignmentResource, + UseMetagenomicsIndexAssignmentReturn +} from "./useMetagenomicsIndexAssignmentAPI"; + +interface MetagenomicsIndexAssignmentRow { + materialSample?: MaterialSampleSummary; + metagenomicsIndexAssignmentResource?: MetagenomicsIndexAssignmentResource; + pcrBatchItem?: PcrBatchItem; +} + +interface MetagenomicsIndexAssignmentTableProps + extends MetagenomicsIndexAssignmentStepProps, + UseMetagenomicsIndexAssignmentReturn {} + +export function MetagenomicsIndexAssignmentTable( + props: MetagenomicsIndexAssignmentTableProps +) { + const { + editMode, + metagenomicsBatch, + performSave, + setPerformSave, + loading, + metagenomicsIndexAssignmentResources, + materialSamples, + ngsIndexes, + onSubmitTable + } = props; + + const { compareByStringAndNumber } = useStringComparator(); + const { formatMessage } = useSeqdbIntl(); + + // Hidden button bar is used to submit the page from the button bar in a parent component. + const hiddenButtonBar = ( + + ); + + const COLUMNS: ColumnDef[] = [ + { + cell: ({ row: { original: sr } }) => { + const { metagenomicsIndexAssignmentResource } = + sr as MetagenomicsIndexAssignmentRow; + if ( + !metagenomicsIndexAssignmentResource || + !metagenomicsIndexAssignmentResource.storageUnitUsage + ) { + return ""; + } + + const { wellRow, wellColumn } = + metagenomicsIndexAssignmentResource.storageUnitUsage; + const wellCoordinates = + wellColumn === null || !wellRow ? "" : `${wellRow}${wellColumn}`; + + return wellCoordinates; + }, + header: "Well Coordinates", + accessorKey: "wellCoordinates", + sortingFn: (a: any, b: any): number => { + const aStorageUnit = + a.original?.metagenomicsIndexAssignmentResource?.storageUnitUsage; + const bStorageUnit = + b.original?.metagenomicsIndexAssignmentResource?.storageUnitUsage; + + const aString = + !aStorageUnit || + aStorageUnit?.wellRow === null || + aStorageUnit?.wellColumn === null + ? "" + : `${aStorageUnit?.wellRow}${aStorageUnit?.wellColumn}`; + const bString = + !bStorageUnit || + bStorageUnit?.wellRow === null || + bStorageUnit?.wellColumn === null + ? "" + : `${bStorageUnit?.wellRow}${bStorageUnit?.wellColumn}`; + return compareByStringAndNumber(aString, bString); + }, + enableSorting: true, + size: 150 + }, + { + header: "Material Sample Name", + accessorKey: "materialSample.materialSampleName" + }, + { + header: "Index i5", + cell: ({ row: { index } }) => + metagenomicsBatch.indexSet && ( + ngsIndex.direction === "I5") + ?.map>((ngsIndex) => ({ + label: ngsIndex.name, + value: ngsIndex.id + }))} + selectProps={{ + isClearable: true + }} + /> + ) + }, + { + header: "Index i7", + cell: ({ row: { index } }) => + metagenomicsBatch.indexSet && ( + ngsIndex.direction === "I7") + ?.map>((ngsIndex) => ({ + label: ngsIndex.name, + value: ngsIndex.id + }))} + selectProps={{ + isClearable: true + }} + /> + ) + } + ]; + + const tableData = useMemo( + () => + metagenomicsIndexAssignmentResources && + metagenomicsIndexAssignmentResources.length !== 0 + ? metagenomicsIndexAssignmentResources.map( + (prep) => ({ + metagenomicsIndexAssignmentResource: prep, + materialSample: materialSamples?.find( + (samp) => samp.id === prep?.materialSampleSummary?.id + ) + }) + ) + : [], + [metagenomicsIndexAssignmentResources, materialSamples] + ); + + const initialValues = useMemo(() => { + if ( + !metagenomicsIndexAssignmentResources || + metagenomicsIndexAssignmentResources.length === 0 + ) { + return {}; + } + + return { + indexAssignment: metagenomicsIndexAssignmentResources.map((resource) => ({ + ...(resource.indexI5 ? { indexI5: resource?.indexI5?.id } : {}), + ...(resource.indexI7 ? { indexI7: resource?.indexI7?.id } : {}) + })) + }; + }, [metagenomicsIndexAssignmentResources]); + + if (loading) { + return ; + } + + if (!metagenomicsBatch?.indexSet?.id) { + return ( +
+ {formatMessage("missingIndexForAssignment")} +
+ ); + } + + return ( + + {hiddenButtonBar} + + className="-striped react-table-overflow" + columns={COLUMNS} + data={tableData} + loading={loading} + manualPagination={true} + showPagination={false} + pageSize={tableData.length} + sort={[{ id: "wellCoordinates", desc: false }]} + /> + + ); +} diff --git a/packages/dina-ui/components/seqdb/ngs-workflow/index-grid/MetagenomicsIndexGrid.tsx b/packages/dina-ui/components/seqdb/ngs-workflow/index-grid/MetagenomicsIndexGrid.tsx new file mode 100644 index 000000000..d0066198c --- /dev/null +++ b/packages/dina-ui/components/seqdb/ngs-workflow/index-grid/MetagenomicsIndexGrid.tsx @@ -0,0 +1,217 @@ +import { + LoadingSpinner, + ReactTable, + DinaForm, + SelectField, + SelectOption, + SubmitButton +} from "common-ui"; +import { LibraryPrep } from "../../../../types/seqdb-api"; +import { ColumnDef } from "@tanstack/react-table"; +import { useSeqdbIntl } from "packages/dina-ui/intl/seqdb-intl"; +import { MetagenomicsIndexAssignmentStepProps } from "../../metagenomics-workflow/MetagenomicsIndexAssignmentStep"; +import { UseMetagenomicsIndexAssignmentReturn } from "../useMetagenomicsIndexAssignmentAPI"; + +interface CellData { + row: number; +} + +interface MetagenomicsIndexGridProps + extends MetagenomicsIndexAssignmentStepProps, + UseMetagenomicsIndexAssignmentReturn {} + +export function MetagenomicsIndexGrid(props: MetagenomicsIndexGridProps) { + const { + pcrBatch, + editMode, + performSave, + setPerformSave, + loading: libraryPrepsLoading, + metagenomicsIndexAssignmentResources: libraryPreps, + materialSamples, + ngsIndexes, + storageUnitType, + onSubmitGrid + } = props; + + const { formatMessage } = useSeqdbIntl(); + const { indexSet } = pcrBatch; + + // Hidden button bar is used to submit the page from the button bar in a parent component. + const hiddenButtonBar = ( + + ); + + if (libraryPrepsLoading) { + return ; + } + + if (!storageUnitType || !indexSet) { + return ( +
+ {formatMessage("missingContainerAndIndexForAssignment")} +
+ ); + } + + if (libraryPreps) { + const libraryPrepsWithCoords = libraryPreps.filter( + (prep) => + prep.storageUnitUsage?.wellRow && prep.storageUnitUsage?.wellColumn + ); + + // Display an error if no coordinates have been selected yet, nothing to edit. + if (libraryPrepsWithCoords.length === 0) { + return ( +
+ {formatMessage("missingSelectedCoordinatesForAssignment")} +
+ ); + } + + const cellGrid: { [key: string]: LibraryPrep } = {}; + for (const prep of libraryPrepsWithCoords) { + cellGrid[ + `${prep.storageUnitUsage?.wellRow}_${prep.storageUnitUsage?.wellColumn}` + ] = prep; + } + + const columns: ColumnDef[] = []; + + // Add the primer column: + columns.push({ + cell: ({ row: { original } }) => { + const rowLetter = String.fromCharCode(original.row + 65); + + return ( + indexSet && ( +
+ index.direction === "I5") + ?.map>((index) => ({ + label: index.name, + value: index.id + }))} + selectProps={{ + isClearable: true, + placeholder: formatMessage("selectI5") + }} + removeBottomMargin={true} + /> +
+ ) + ); + }, + meta: { + style: { + background: "white", + boxShadow: "7px 0px 9px 0px rgba(0,0,0,0.1)" + } + }, + id: "rowNumber", + accessorKey: "", + enableResizing: false, + enableSorting: false, + size: 300 + }); + + // Generate the columns + for ( + let col = 0; + col < (storageUnitType?.gridLayoutDefinition?.numberOfColumns ?? 0); + col++ + ) { + const columnLabel = String(col + 1); + + columns.push({ + cell: ({ row: { original } }) => { + const rowLabel = String.fromCharCode(original.row + 65); + const coords = `${rowLabel}_${columnLabel}`; + const prep = cellGrid[coords]; + + return prep ? ( +
+
+ {materialSamples?.find( + (sample) => sample.id === prep?.materialSample?.id + )?.materialSampleName ?? ""} +
+
+ {prep.indexI5 && ( +
+ i5: + {prep.indexI5.name} +
+ )} + {prep.indexI7 && ( +
+ i7: + {prep.indexI7.name} +
+ )} +
+
+ ) : null; + }, + header: () => + indexSet && ( + index.direction === "I7") + ?.map>((index) => ({ + label: index.name, + value: index.id + }))} + selectProps={{ + isClearable: true, + placeholder: formatMessage("selectI7") + }} + removeBottomMargin={true} + className={"w-100"} + /> + ), + id: `${columnLabel}`, + accessorKey: `${columnLabel}`, + enableResizing: false, + enableSorting: false, + size: 300 + }); + } + + // Populate the table's rows using the number of rows. + const tableData: CellData[] = []; + const numberOfRows = + storageUnitType?.gridLayoutDefinition?.numberOfRows ?? 0; + for (let i = 0; i < numberOfRows; i++) { + tableData.push({ row: i }); + } + + return ( + + {hiddenButtonBar} + + className="-striped react-table-overflow" + columns={columns} + data={tableData} + showPagination={false} + manualPagination={true} + /> + + ); + } + + return null; +} diff --git a/packages/dina-ui/components/seqdb/ngs-workflow/useMetagenomicsIndexAssignmentAPI.ts b/packages/dina-ui/components/seqdb/ngs-workflow/useMetagenomicsIndexAssignmentAPI.ts new file mode 100644 index 000000000..3a799a1c3 --- /dev/null +++ b/packages/dina-ui/components/seqdb/ngs-workflow/useMetagenomicsIndexAssignmentAPI.ts @@ -0,0 +1,364 @@ +import { + ApiClientContext, + DinaFormSubmitParams, + filterBy, + SaveArgs, + useQuery +} from "common-ui"; +import { Dictionary, toPairs } from "lodash"; +import { useContext, useState, useEffect } from "react"; +import { + MaterialSample, + MaterialSampleSummary, + Protocol, + StorageUnit, + StorageUnitType +} from "packages/dina-ui/types/collection-api"; +import { StorageUnitUsage } from "packages/dina-ui/types/collection-api/resources/StorageUnitUsage"; +import { + LibraryPrep, + NgsIndex, + PcrBatchItem +} from "packages/dina-ui/types/seqdb-api"; +import { isEqual } from "lodash"; +import { MetagenomicsIndexAssignmentStepProps } from "../metagenomics-workflow/MetagenomicsIndexAssignmentStep"; +import { MetagenomicsBatchItem } from "packages/dina-ui/types/seqdb-api/resources/metagenomics/MetagenomicsBatchItem"; + +export interface UseMetagenomicsIndexAssignmentReturn { + loading: boolean; + metagenomicsIndexAssignmentResources?: MetagenomicsIndexAssignmentResource[]; + materialSamples?: MaterialSampleSummary[]; + ngsIndexes?: NgsIndex[]; + storageUnitType?: StorageUnitType; + protocol?: Protocol; + onSubmitGrid: ({ + submittedValues + }: DinaFormSubmitParams) => Promise; + onSubmitTable: ({ + submittedValues + }: DinaFormSubmitParams) => Promise; +} + +// UI-side only type that combines MetagenomicsBatchItem and other fields necessary for Index Assignment step +export type MetagenomicsIndexAssignmentResource = MetagenomicsBatchItem & { + materialSampleSummary?: MaterialSampleSummary; + storageUnitUsage?: StorageUnitUsage; +}; + +export function useMetagenomicsIndexAssignmentAPI({ + pcrBatch, + metagenomicsBatch, + editMode, + setEditMode, + setPerformSave +}: Partial): UseMetagenomicsIndexAssignmentReturn { + const { save, apiClient, bulkGet } = useContext(ApiClientContext); + + const [lastSave, setLastSave] = useState(); + + const [storageUnitType, setStorageUnitType] = useState(); + const [loading, setLoading] = useState(true); + const [ + metagenomicsIndexAssignmentResources, + setMetagenomicsIndexAssignmentResources + ] = useState([]); + const [materialSamples, setMaterialSamples] = + useState(); + const [ngsIndexes, setNgsIndexes] = useState(); + const [protocol, setProtocol] = useState(); + + useQuery( + { + include: "indexI5,indexI7,pcrBatchItem", + page: { limit: 1000 }, + filter: filterBy([], { + extraFilters: [ + { + selector: "metagenomicsBatch.uuid", + comparison: "==", + arguments: metagenomicsBatch?.id ?? "" + } + ] + })(""), + path: `seqdb-api/metagenomics-batch-item` + }, + { + deps: [lastSave], + joinSpecs: [ + { + apiBaseUrl: "/seqdb-api", + idField: "pcrBatchItem.id", + joinField: "pcrBatchItem", + path: (metagenomicsBatchItem: MetagenomicsBatchItem) => + `pcr-batch-item/${metagenomicsBatchItem.pcrBatchItem?.id}?include=materialSample,storageUnitUsage` + } + ], + async onSuccess(response) { + /** + * Fetch Storage Unit Usage linked to each Library Prep along with the material sample. + * @returns + */ + async function fetchStorageUnitUsage( + metagenomicsBatchItems: MetagenomicsBatchItem[] + ): Promise { + const paths = metagenomicsBatchItems + .filter((item) => item.pcrBatchItem?.storageUnitUsage?.id) + .map( + (item) => + "/storage-unit-usage/" + item.pcrBatchItem?.storageUnitUsage?.id + ); + const storageUnitUsageQuery = await bulkGet(paths, { + apiBaseUrl: "/collection-api" + }); + + return metagenomicsBatchItems.map((metagenomicsBatchItem) => { + const queryStorageUnitUsage = storageUnitUsageQuery.find( + (storageUnitUsage) => + storageUnitUsage?.id === + metagenomicsBatchItem.pcrBatchItem?.storageUnitUsage?.id + ); + return { + ...metagenomicsBatchItem, + storageUnitUsage: queryStorageUnitUsage as StorageUnitUsage + }; + }); + } + + async function fetchMaterialSamples( + metagenomicsBatchItems: MetagenomicsIndexAssignmentResource[] + ): Promise { + const materialSampleQuery = await bulkGet( + metagenomicsBatchItems + .filter((item) => item?.pcrBatchItem?.materialSample?.id) + .map( + (item) => + "/material-sample-summary/" + + item?.pcrBatchItem?.materialSample?.id + ), + { + apiBaseUrl: "/collection-api" + } + ); + return materialSampleQuery as MaterialSampleSummary[]; + } + let fetchedMetagenomicsIndexAssignmentResources = + await fetchStorageUnitUsage(response.data); + const materialSampleItems = await fetchMaterialSamples( + fetchedMetagenomicsIndexAssignmentResources + ); + + // Add materialSampleSummary to each resource + fetchedMetagenomicsIndexAssignmentResources = + fetchedMetagenomicsIndexAssignmentResources.map( + (metagenomicsBatchItem) => { + const materialSampleSummary = materialSampleItems.find( + (msSummary) => + msSummary.id === + metagenomicsBatchItem.pcrBatchItem?.materialSample?.id + ); + return { + ...metagenomicsBatchItem, + materialSampleSummary + }; + } + ); + setMetagenomicsIndexAssignmentResources( + fetchedMetagenomicsIndexAssignmentResources + ); + setMaterialSamples(materialSampleItems); + setLoading(false); + } + } + ); + + useQuery( + { + page: { limit: 1000 }, + filter: filterBy([], { + extraFilters: [ + { + selector: "indexSet.uuid", + comparison: "==", + arguments: metagenomicsBatch?.indexSet?.id ?? "" + } + ] + })(""), + path: `seqdb-api/ngs-index` + }, + { + deps: [lastSave], + async onSuccess(response) { + setNgsIndexes(response.data as NgsIndex[]); + }, + disabled: !metagenomicsBatch?.indexSet?.id + } + ); + + useQuery( + { + page: { limit: 1 }, + path: `collection-api/protocol/${metagenomicsBatch?.protocol?.id}` + }, + { + async onSuccess(response) { + setProtocol(response.data as Protocol); + }, + disabled: !metagenomicsBatch?.protocol?.id + } + ); + + useEffect(() => { + if (!pcrBatch || !pcrBatch.storageUnit) return; + + async function fetchStorageUnitTypeLayout() { + const storageUnitReponse = await apiClient.get( + `/collection-api/storage-unit/${pcrBatch?.storageUnit?.id}`, + { include: "storageUnitType" } + ); + if (storageUnitReponse?.data.storageUnitType?.gridLayoutDefinition) { + setStorageUnitType(storageUnitReponse?.data.storageUnitType); + } + } + fetchStorageUnitTypeLayout(); + }, [metagenomicsIndexAssignmentResources]); + + /** + * Index Grid Form Submit + * + * Columns can set the i7 for each cell in that column and rows can set the i5 index for each + * cell in that row. + * + * @param submittedValues Formik form data - Indicates the row/column and the index to set. + */ + async function onSubmitGrid({ submittedValues }: DinaFormSubmitParams) { + // Do not perform a submit if not in edit mode. + if (!editMode) { + return; + } + + const resourcesToSave = metagenomicsIndexAssignmentResources + ? metagenomicsIndexAssignmentResources + : []; + const { indexI5s, indexI7s } = submittedValues; + + const edits: Dictionary> = {}; + + // Get the new i7 values: + const colIndexes = toPairs(indexI7s); + for (const [col, index] of colIndexes) { + const colResources = resourcesToSave.filter( + (it) => String(it?.storageUnitUsage?.wellColumn) === col + ); + for (const metagenicsIndexAssignmentResource of colResources) { + if (metagenicsIndexAssignmentResource.id) { + const edit = edits[metagenicsIndexAssignmentResource.id] || {}; + edit.indexI7 = { id: index, type: "ngs-index" } as NgsIndex; + edits[metagenicsIndexAssignmentResource.id] = edit; + } + } + } + + // Get the new i5 values: + const rowIndexes = toPairs(indexI5s); + for (const [row, index] of rowIndexes) { + const rowPreps = resourcesToSave.filter( + (it) => it?.storageUnitUsage?.wellRow === row + ); + for (const prep of rowPreps) { + if (prep.id) { + const edit = edits[prep.id] || {}; + edit.indexI5 = { id: index, type: "ngs-index" } as NgsIndex; + edits[prep.id] = edit; + } + } + } + + const saveOps: SaveArgs[] = toPairs(edits).map(([id, prepEdit]) => ({ + resource: { id, type: "library-prep", ...prepEdit }, + type: "library-prep" + })); + + await save(saveOps, { apiBaseUrl: "/seqdb-api" }); + setLastSave(Date.now()); + setPerformSave?.(false); + setEditMode?.(false); + } + + /** + * Table index assignment submit. This form lets you set the i5/i7 indexes for each library + * prep individually. + * + * @param submittedValues Formik form data + */ + async function onSubmitTable({ submittedValues }: DinaFormSubmitParams) { + // Do not perform a submit if not in edit mode. + if (!editMode) { + return; + } + + // Library preps must be loaded in. + if ( + !metagenomicsIndexAssignmentResources || + metagenomicsIndexAssignmentResources.length === 0 || + !submittedValues.indexAssignment || + submittedValues.indexAssignment.length === 0 + ) { + return; + } + + const indexAssignmentUpdates = (submittedValues?.indexAssignment as any[]) + ?.map( + (submittedValue: any, index: number) => + ({ + type: "metagenomics-batch-item", + id: metagenomicsIndexAssignmentResources[index].id, + ...(!isEqual( + metagenomicsIndexAssignmentResources[index]?.indexI5?.id, + submittedValue.indexI5 + ) && { + indexI5: { + type: "ngs-index", + id: submittedValue.indexI5 ? submittedValue.indexI5 : null + } + }), + ...(!isEqual( + metagenomicsIndexAssignmentResources[index]?.indexI7?.id, + submittedValue.indexI7 + ) && { + indexI7: { + type: "ngs-index", + id: submittedValue.indexI7 ? submittedValue.indexI7 : null + } + }) + } as MetagenomicsBatchItem) + ) + ?.filter( + (update: any) => + update.indexI5 !== undefined || update.indexI7 !== undefined + ); + + if (indexAssignmentUpdates.length !== 0) { + const saveArgs = indexAssignmentUpdates.map((resource) => ({ + resource, + type: "metagenomics-batch-item" + })); + + await save(saveArgs, { apiBaseUrl: "/seqdb-api" }); + setLastSave(Date.now()); + } + + setPerformSave?.(false); + setEditMode?.(false); + } + + return { + loading, + metagenomicsIndexAssignmentResources, + materialSamples, + ngsIndexes, + storageUnitType, + protocol, + onSubmitGrid, + onSubmitTable + }; +} diff --git a/packages/dina-ui/pages/seqdb/metagenomics-workflow/run.tsx b/packages/dina-ui/pages/seqdb/metagenomics-workflow/run.tsx index f1a411a3f..0ead48926 100644 --- a/packages/dina-ui/pages/seqdb/metagenomics-workflow/run.tsx +++ b/packages/dina-ui/pages/seqdb/metagenomics-workflow/run.tsx @@ -293,17 +293,22 @@ export default function MetagenomicWorkflowRunPage() { )} - {metagenomicsBatchQuery.response?.data && metagenomicsBatchId && ( - - )} + {metagenomicsBatchQuery.response?.data && + metagenomicsBatchId && + pcrBatchQuery?.response?.data && + pcrBatchId && ( + + )} {metagenomicsBatchQuery.response?.data && metagenomicsBatchId && (