From 0675e3483821c02575a05025a40645f5a701724a Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Mon, 27 Nov 2023 11:41:44 +0000 Subject: [PATCH 01/23] prettier formatting --- frontend-v2/.prettierignore | 4 + frontend-v2/.prettierrc | 1 + frontend-v2/cypress.config.ts | 3 +- frontend-v2/cypress/e2e/pkmodel.cy.js | 115 +- frontend-v2/cypress/support/commands.ts | 18 +- frontend-v2/cypress/support/e2e.ts | 4 +- frontend-v2/openapi-config.json | 2 +- frontend-v2/openapi-config.ts | 16 +- frontend-v2/package.json | 1 + frontend-v2/public/index.html | 2 +- frontend-v2/src/App.test.tsx | 14 +- frontend-v2/src/App.tsx | 33 +- frontend-v2/src/app/api.ts | 188 +- frontend-v2/src/app/emptyApi.ts | 11 +- frontend-v2/src/app/hooks.ts | 17 +- frontend-v2/src/app/store.ts | 12 +- frontend-v2/src/components/Checkbox.tsx | 27 +- .../src/components/ConfirmationDialog.tsx | 20 +- frontend-v2/src/components/DropdownButton.tsx | 45 +- frontend-v2/src/components/DynamicTabs.tsx | 50 +- frontend-v2/src/components/FloatField.tsx | 45 +- frontend-v2/src/components/HelpButton.tsx | 10 +- frontend-v2/src/components/HelpDialog.tsx | 19 +- frontend-v2/src/components/IntegerField.tsx | 38 +- frontend-v2/src/components/SelectField.tsx | 49 +- frontend-v2/src/components/TextField.tsx | 52 +- frontend-v2/src/components/Title.tsx | 5 +- frontend-v2/src/components/UnitField.tsx | 55 +- frontend-v2/src/features/drug/Drug.tsx | 268 +- frontend-v2/src/features/help/Help.tsx | 78 +- frontend-v2/src/features/help/HelpTab.tsx | 52 +- frontend-v2/src/features/login/login.tsx | 78 +- frontend-v2/src/features/login/loginSlice.ts | 128 +- frontend-v2/src/features/main/MainContent.tsx | 34 +- frontend-v2/src/features/main/Sidebar.tsx | 189 +- frontend-v2/src/features/main/mainSlice.ts | 23 +- .../src/features/model/MapVariablesTab.tsx | 244 +- frontend-v2/src/features/model/Model.tsx | 215 +- .../src/features/model/PKPDModelTab.tsx | 373 +- .../src/features/model/ParameterRow.tsx | 77 +- .../src/features/model/ParametersTab.tsx | 183 +- .../src/features/model/TranslationTab.tsx | 26 +- .../src/features/model/VariableRow.tsx | 298 +- .../src/features/model/paramPriority.ts | 56 +- .../src/features/model/param_default.ts | 5210 ++++++++--------- .../features/model/resetToSpeciesDefaults.ts | 95 +- frontend-v2/src/features/projects/Project.tsx | 212 +- .../src/features/projects/Projects.tsx | 242 +- .../src/features/projects/UserAccess.tsx | 81 +- .../simulation/SimulationPlotForm.tsx | 450 +- .../simulation/SimulationPlotView.tsx | 371 +- .../simulation/SimulationSliderView.tsx | 120 +- .../src/features/simulation/Simulations.tsx | 511 +- frontend-v2/src/features/trial/Doses.tsx | 144 +- frontend-v2/src/features/trial/Protocols.tsx | 82 +- frontend-v2/src/hooks/useDirty.ts | 12 +- frontend-v2/src/hooks/usePrevious.ts | 12 +- frontend-v2/src/index.css | 6 +- frontend-v2/src/index.tsx | 18 +- frontend-v2/src/reportWebVitals.ts | 4 +- frontend-v2/src/setupProxy.js | 14 +- frontend-v2/src/setupTests.ts | 2 +- frontend-v2/tsconfig.json | 10 +- frontend-v2/yarn.lock | 5 + 64 files changed, 6234 insertions(+), 4545 deletions(-) create mode 100644 frontend-v2/.prettierignore create mode 100644 frontend-v2/.prettierrc diff --git a/frontend-v2/.prettierignore b/frontend-v2/.prettierignore new file mode 100644 index 00000000..3f2792cd --- /dev/null +++ b/frontend-v2/.prettierignore @@ -0,0 +1,4 @@ +# Ignore artifacts: +build +coverage +node_modules \ No newline at end of file diff --git a/frontend-v2/.prettierrc b/frontend-v2/.prettierrc new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/frontend-v2/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/frontend-v2/cypress.config.ts b/frontend-v2/cypress.config.ts index 067f51cd..52def34b 100644 --- a/frontend-v2/cypress.config.ts +++ b/frontend-v2/cypress.config.ts @@ -5,8 +5,7 @@ export default defineConfig({ setupNodeEvents(on, config) { // implement node event listeners here }, - baseUrl: 'http://localhost:3000', - + baseUrl: "http://localhost:3000", }, component: { diff --git a/frontend-v2/cypress/e2e/pkmodel.cy.js b/frontend-v2/cypress/e2e/pkmodel.cy.js index 4b69902f..1de9471e 100644 --- a/frontend-v2/cypress/e2e/pkmodel.cy.js +++ b/frontend-v2/cypress/e2e/pkmodel.cy.js @@ -1,101 +1,110 @@ -describe('landing page', () => { +describe("landing page", () => { beforeEach(() => { - const { username, password } = { username: 'demo', password: '12345'} - cy.login(username, password) - }) + const { username, password } = { username: "demo", password: "12345" }; + cy.login(username, password); + }); - it('can create combined pk and pd model and simulate from it', () => { + it("can create combined pk and pd model and simulate from it", () => { // create a new project - cy.intercept('POST', '/api/project/').as('createProject') - cy.get('[data-cy="create-project"]').click() - cy.get('[data-cy="create-project-option-Small Molecule"]').click() - cy.wait('@createProject').then((interception) => { - const { id } = interception.response.body - - // select the species - cy.get(`[data-cy="project-${id}"]`).find('[data-cy="select-project.species"]').click() - cy.get('[data-cy="select-option-project.species-Monkey"]').click() - - // select the project - cy.get(`[data-cy="project-${id}"]`).find('[type=radio]').click() + cy.intercept("POST", "/api/project/").as("createProject"); + cy.get('[data-cy="create-project"]').click(); + cy.get('[data-cy="create-project-option-Small Molecule"]').click(); + cy.wait("@createProject").then((interception) => { + const { id } = interception.response.body; + + // select the species + cy.get(`[data-cy="project-${id}"]`) + .find('[data-cy="select-project.species"]') + .click(); + cy.get('[data-cy="select-option-project.species-Monkey"]').click(); + + // select the project + cy.get(`[data-cy="project-${id}"]`).find("[type=radio]").click(); }); - cy.get('[data-cy="select-project.species"]').last().click() - cy.get('[data-cy="select-option-project.species-Monkey"]').last().click() - + cy.get('[data-cy="select-project.species"]').last().click(); + cy.get('[data-cy="select-option-project.species-Monkey"]').last().click(); // go to model tab - cy.get('li').contains('Model').click() + cy.get("li").contains("Model").click(); // select one compartment model - cy.get('[data-cy="select-pk_model"]').click() - cy.get('[data-cy="select-option-pk_model-one_compartment_preclinical"]').click() + cy.get('[data-cy="select-pk_model"]').click(); + cy.get( + '[data-cy="select-option-pk_model-one_compartment_preclinical"]', + ).click(); // select pd model - cy.get('[data-cy="select-pd_model"]').click() - cy.get('[data-cy="select-option-pd_model-indirect_effects_stimulation_elimination"]').click() + cy.get('[data-cy="select-pd_model"]').click(); + cy.get( + '[data-cy="select-option-pd_model-indirect_effects_stimulation_elimination"]', + ).click(); // go to map variables tab - cy.contains('button', 'Map Variables').click() + cy.contains("button", "Map Variables").click(); // dose into Aa compartment - cy.get('[data-cy="checkbox-dosing-Aa"]').click() + cy.get('[data-cy="checkbox-dosing-Aa"]').click(); // map C1 to pd effect - cy.get('[data-cy="checkbox-map-to-pd-C1"]').click() + cy.get('[data-cy="checkbox-map-to-pd-C1"]').click(); // go to parameters tab - cy.contains('button', 'Parameters').click() + cy.contains("button", "Parameters").click(); - cy.get('[data-cy="parameter-CL-value"]').find('input').then(($input) => { - const old_value = $input.val() + cy.get('[data-cy="parameter-CL-value"]') + .find("input") + .then(($input) => { + const old_value = $input.val(); - // reset to species defaults - cy.contains('button', 'Reset to Species Defaults').click() - cy.wait(1000) + // reset to species defaults + cy.contains("button", "Reset to Species Defaults").click(); + cy.wait(1000); - // check that the value has changed - cy.get('[data-cy="parameter-CL-value"]').find('input').then(($input) => { - expect($input.val()).not.to.eq(old_value) - }) - }); + // check that the value has changed + cy.get('[data-cy="parameter-CL-value"]') + .find("input") + .then(($input) => { + expect($input.val()).not.to.eq(old_value); + }); + }); // go to trial design tab - cy.get('li').contains('Trial Design').click() + cy.get("li").contains("Trial Design").click(); // set the dose - cy.get('input[name="doses.0.amount"]').clear().type('1') + cy.get('input[name="doses.0.amount"]').clear().type("1"); // set the number of doses - cy.get('input[name="doses.0.repeats"]').clear().type('4') + cy.get('input[name="doses.0.repeats"]').clear().type("4"); // set the duration - cy.get('input[name="doses.0.duration"]').clear().type('0.1') + cy.get('input[name="doses.0.duration"]').clear().type("0.1"); // set the interval - cy.get('input[name="doses.0.repeat_interval"]').clear().type('0.1') + cy.get('input[name="doses.0.repeat_interval"]').clear().type("0.1"); // go to simulation tab - cy.get('li').contains('Simulations').click() + cy.get("li").contains("Simulations").click(); // should be no svg with class "main-svg" - cy.get('svg.main-svg').should('not.exist') + cy.get("svg.main-svg").should("not.exist"); // add a plot of Aa - cy.get('[data-cy="add-plot"]').click() - cy.get('[data-cy^="add-plot-option-Aa"]').click() + cy.get('[data-cy="add-plot"]').click(); + cy.get('[data-cy^="add-plot-option-Aa"]').click(); // now there should be an svg with class "main-svg" - cy.get('svg.main-svg').should('exist') + cy.get("svg.main-svg").should("exist"); // should be no CL slider - cy.get('[data-cy="parameter-slider-CL"]').should('not.exist') + cy.get('[data-cy="parameter-slider-CL"]').should("not.exist"); // add a CL parameter slider - cy.get('[data-cy="add-parameter-slider"]').click() - cy.get('[data-cy="add-parameter-slider-option-CL"]').click() + cy.get('[data-cy="add-parameter-slider"]').click(); + cy.get('[data-cy="add-parameter-slider-option-CL"]').click(); // CL slider should exist - cy.get('[data-cy="parameter-slider-CL"]').should('exist') + cy.get('[data-cy="parameter-slider-CL"]').should("exist"); }); }); diff --git a/frontend-v2/cypress/support/commands.ts b/frontend-v2/cypress/support/commands.ts index 59301d8b..1a301cfa 100644 --- a/frontend-v2/cypress/support/commands.ts +++ b/frontend-v2/cypress/support/commands.ts @@ -36,20 +36,20 @@ // } // } -Cypress.Commands.add('login', (username, password) => { - cy.visit('/') +Cypress.Commands.add("login", (username, password) => { + cy.visit("/"); - cy.get('input[name=username]').type(username) + cy.get("input[name=username]").type(username); // {enter} causes the form to submit - cy.get('input[name=password]').type(`${password}{enter}`, { log: false }) + cy.get("input[name=password]").type(`${password}{enter}`, { log: false }); // we should be redirected to / - cy.url().should('not.contain', 'login') + cy.url().should("not.contain", "login"); // our auth cookies should be present - cy.getCookie('csrftoken').should('exist') - cy.getCookie('sessionid').should('exist') -}) + cy.getCookie("csrftoken").should("exist"); + cy.getCookie("sessionid").should("exist"); +}); -export {} \ No newline at end of file +export {}; diff --git a/frontend-v2/cypress/support/e2e.ts b/frontend-v2/cypress/support/e2e.ts index f80f74f8..6a173d6f 100644 --- a/frontend-v2/cypress/support/e2e.ts +++ b/frontend-v2/cypress/support/e2e.ts @@ -14,7 +14,7 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import "./commands"; // Alternatively you can use CommonJS syntax: -// require('./commands') \ No newline at end of file +// require('./commands') diff --git a/frontend-v2/openapi-config.json b/frontend-v2/openapi-config.json index 1b493e73..3674fac6 100644 --- a/frontend-v2/openapi-config.json +++ b/frontend-v2/openapi-config.json @@ -5,4 +5,4 @@ "outputFile": "./src/app/backendApi.ts", "exportName": "backendApi", "hooks": true -} \ No newline at end of file +} diff --git a/frontend-v2/openapi-config.ts b/frontend-v2/openapi-config.ts index c8c53aa4..33ae0f2a 100644 --- a/frontend-v2/openapi-config.ts +++ b/frontend-v2/openapi-config.ts @@ -1,12 +1,12 @@ -import type { ConfigFile } from '@rtk-query/codegen-openapi' +import type { ConfigFile } from "@rtk-query/codegen-openapi"; const config: ConfigFile = { - schemaFile: '../backend/schema.yml', - apiFile: './src/store/emptyApi.ts', - apiImport: 'emptySplitApi', - outputFile: './src/store/petApi.ts', - exportName: 'petApi', + schemaFile: "../backend/schema.yml", + apiFile: "./src/store/emptyApi.ts", + apiImport: "emptySplitApi", + outputFile: "./src/store/petApi.ts", + exportName: "petApi", hooks: true, -} +}; -export default config \ No newline at end of file +export default config; diff --git a/frontend-v2/package.json b/frontend-v2/package.json index 679f117d..a046b5b4 100644 --- a/frontend-v2/package.json +++ b/frontend-v2/package.json @@ -59,6 +59,7 @@ "@types/papaparse": "^5.3.10", "@types/react-plotly.js": "^2.6.0", "cypress": "^12.16.0", + "prettier": "3.1.0", "start-server-and-test": "^2.0.0" } } diff --git a/frontend-v2/public/index.html b/frontend-v2/public/index.html index 0eb2f946..1f791a49 100644 --- a/frontend-v2/public/index.html +++ b/frontend-v2/public/index.html @@ -1,4 +1,4 @@ - + diff --git a/frontend-v2/src/App.test.tsx b/frontend-v2/src/App.test.tsx index 659cc13d..6ebaf6ae 100644 --- a/frontend-v2/src/App.test.tsx +++ b/frontend-v2/src/App.test.tsx @@ -1,14 +1,14 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { Provider } from 'react-redux'; -import { store } from './app/store'; -import App from './App'; +import React from "react"; +import { render } from "@testing-library/react"; +import { Provider } from "react-redux"; +import { store } from "./app/store"; +import App from "./App"; -test('renders learn react link', () => { +test("renders learn react link", () => { const { getByText } = render( - + , ); expect(getByText(/learn/i)).toBeInTheDocument(); diff --git a/frontend-v2/src/App.tsx b/frontend-v2/src/App.tsx index 926152ee..a895697d 100644 --- a/frontend-v2/src/App.tsx +++ b/frontend-v2/src/App.tsx @@ -1,12 +1,16 @@ -import React, { useEffect } from 'react'; -import './App.css'; +import React, { useEffect } from "react"; +import "./App.css"; -import { fetchSession, isAuthenticated, login } from './features/login/loginSlice'; -import { useSelector } from 'react-redux'; -import Login from './features/login/login'; -import Sidebar from './features/main/Sidebar'; -import { useAppDispatch } from './app/hooks'; -import { RootState } from './app/store'; +import { + fetchSession, + isAuthenticated, + login, +} from "./features/login/loginSlice"; +import { useSelector } from "react-redux"; +import Login from "./features/login/login"; +import Sidebar from "./features/main/Sidebar"; +import { useAppDispatch } from "./app/hooks"; +import { RootState } from "./app/store"; function App() { const dispatch = useAppDispatch(); @@ -15,7 +19,7 @@ function App() { const onLogin = (username: string, password: string) => { dispatch(login({ username, password })); - } + }; useEffect(() => { dispatch(fetchSession()); @@ -23,14 +27,13 @@ function App() { return ( <> - { isAuth ? ( - - ): ( - - )} + {isAuth ? ( + + ) : ( + + )} ); } export default App; - diff --git a/frontend-v2/src/app/api.ts b/frontend-v2/src/app/api.ts index 853052d8..2cba1703 100644 --- a/frontend-v2/src/app/api.ts +++ b/frontend-v2/src/app/api.ts @@ -1,31 +1,40 @@ -import { backendApi } from './backendApi' +import { backendApi } from "./backendApi"; export const api = backendApi.enhanceEndpoints({ - addTagTypes: ['Project', 'Compound', 'Dataset', 'CombinedModel', 'Variable', 'Simulation', 'Protocol', 'Unit'], + addTagTypes: [ + "Project", + "Compound", + "Dataset", + "CombinedModel", + "Variable", + "Simulation", + "Protocol", + "Unit", + ], endpoints: { // Projects projectList: { providesTags: (result) => result ? [ - ...result.map(({ id }) => ({ type: 'Project' as const, id })), - { type: 'Project', id: 'LIST' }, - ] - : [{ type: 'Project', id: 'LIST' }], + ...result.map(({ id }) => ({ type: "Project" as const, id })), + { type: "Project", id: "LIST" }, + ] + : [{ type: "Project", id: "LIST" }], }, projectRetrieve: { - providesTags: (result, error, { id }) => [{ type: 'Project', id }], + providesTags: (result, error, { id }) => [{ type: "Project", id }], }, projectUpdate: { - invalidatesTags: (result, error, { id }) => [{ type: 'Project', id }], + invalidatesTags: (result, error, { id }) => [{ type: "Project", id }], }, projectCreate: { - invalidatesTags: [{ type: 'Project', id: 'LIST' }], + invalidatesTags: [{ type: "Project", id: "LIST" }], }, projectDestroy: { invalidatesTags: (result, error, { id }) => [ - { type: 'Project', id }, - { type: 'Project', id: 'LIST' } + { type: "Project", id }, + { type: "Project", id: "LIST" }, ], }, // Compounds @@ -33,24 +42,27 @@ export const api = backendApi.enhanceEndpoints({ providesTags: (result) => result ? [ - ...result.map(({ id }) => ({ type: 'Compound' as const, id })), - { type: 'Compound', id: 'LIST' }, - ] - : [{ type: 'Compound', id: 'LIST' }], + ...result.map(({ id }) => ({ type: "Compound" as const, id })), + { type: "Compound", id: "LIST" }, + ] + : [{ type: "Compound", id: "LIST" }], }, compoundRetrieve: { - providesTags: (result, error, { id }) => [{ type: 'Compound', id }], + providesTags: (result, error, { id }) => [{ type: "Compound", id }], }, compoundUpdate: { - invalidatesTags: (result, error, { id }) => [{ type: 'Compound', id }, { type: 'Unit', id: 'LIST' }], + invalidatesTags: (result, error, { id }) => [ + { type: "Compound", id }, + { type: "Unit", id: "LIST" }, + ], }, compoundCreate: { - invalidatesTags: [{ type: 'Compound', id: 'LIST' }], + invalidatesTags: [{ type: "Compound", id: "LIST" }], }, compoundDestroy: { invalidatesTags: (result, error, { id }) => [ - { type: 'Compound', id }, - { type: 'Compound', id: 'LIST' } + { type: "Compound", id }, + { type: "Compound", id: "LIST" }, ], }, // Datasets @@ -58,152 +70,166 @@ export const api = backendApi.enhanceEndpoints({ providesTags: (result) => result ? [ - ...result.map(({ id }) => ({ type: 'Dataset' as const, id })), - { type: 'Dataset', id: 'LIST' }, - ] - : [{ type: 'Dataset', id: 'LIST' }], + ...result.map(({ id }) => ({ type: "Dataset" as const, id })), + { type: "Dataset", id: "LIST" }, + ] + : [{ type: "Dataset", id: "LIST" }], }, datasetRetrieve: { - providesTags: (result, error, { id }) => [{ type: 'Dataset', id }], + providesTags: (result, error, { id }) => [{ type: "Dataset", id }], }, datasetUpdate: { - invalidatesTags: (result, error, { id }) => [{ type: 'Dataset', id }], + invalidatesTags: (result, error, { id }) => [{ type: "Dataset", id }], }, datasetCreate: { - invalidatesTags: [{ type: 'Dataset', id: 'LIST' }], + invalidatesTags: [{ type: "Dataset", id: "LIST" }], }, datasetDestroy: { invalidatesTags: (result, error, { id }) => [ - { type: 'Dataset', id }, - { type: 'Dataset', id: 'LIST' } + { type: "Dataset", id }, + { type: "Dataset", id: "LIST" }, ], }, // CombinedModel combinedModelSetParamsToDefaultsUpdate: { - invalidatesTags: (result, error, { id }) => [{ type: 'Variable', id: 'LIST' }], + invalidatesTags: (result, error, { id }) => [ + { type: "Variable", id: "LIST" }, + ], }, combinedModelList: { providesTags: (result) => result ? [ - ...result.map(({ id }) => ({ type: 'CombinedModel' as const, id })), - { type: 'CombinedModel', id: 'LIST' }, - ] - : [{ type: 'CombinedModel', id: 'LIST' }], + ...result.map(({ id }) => ({ + type: "CombinedModel" as const, + id, + })), + { type: "CombinedModel", id: "LIST" }, + ] + : [{ type: "CombinedModel", id: "LIST" }], }, combinedModelRetrieve: { - providesTags: (result, error, { id }) => [{ type: 'CombinedModel', id }], + providesTags: (result, error, { id }) => [{ type: "CombinedModel", id }], }, combinedModelUpdate: { - invalidatesTags: (result, error, { id }) => [{ type: 'CombinedModel', id }, { type: 'Variable', id: 'LIST' }, { type: 'Protocol', id: 'LIST' }], + invalidatesTags: (result, error, { id }) => [ + { type: "CombinedModel", id }, + { type: "Variable", id: "LIST" }, + { type: "Protocol", id: "LIST" }, + ], }, combinedModelCreate: { - invalidatesTags: [{ type: 'CombinedModel', id: 'LIST' }, { type: 'Variable', id: 'LIST' }], + invalidatesTags: [ + { type: "CombinedModel", id: "LIST" }, + { type: "Variable", id: "LIST" }, + ], }, combinedModelDestroy: { invalidatesTags: (result, error, { id }) => [ - { type: 'CombinedModel', id }, - { type: 'CombinedModel', id: 'LIST' }, - { type: 'Variable', id: 'LIST' } + { type: "CombinedModel", id }, + { type: "CombinedModel", id: "LIST" }, + { type: "Variable", id: "LIST" }, ], }, variableList: { providesTags: (result) => result ? [ - ...result.map(({ id }) => ({ type: 'Variable' as const, id })), - { type: 'Variable', id: 'LIST' }, - ] - : [{ type: 'Variable', id: 'LIST' }], - }, + ...result.map(({ id }) => ({ type: "Variable" as const, id })), + { type: "Variable", id: "LIST" }, + ] + : [{ type: "Variable", id: "LIST" }], + }, variableRetrieve: { - providesTags: (result, error, { id }) => [{ type: 'Variable', id }], + providesTags: (result, error, { id }) => [{ type: "Variable", id }], }, variableUpdate: { - invalidatesTags: (result, error, { id }) => [{ type: 'Variable', id }, { type: 'Protocol', id: 'LIST' }], + invalidatesTags: (result, error, { id }) => [ + { type: "Variable", id }, + { type: "Protocol", id: "LIST" }, + ], }, variableCreate: { - invalidatesTags: [{ type: 'Variable', id: 'LIST' }], + invalidatesTags: [{ type: "Variable", id: "LIST" }], }, variableDestroy: { invalidatesTags: (result, error, { id }) => [ - { type: 'Variable', id }, - { type: 'Variable', id: 'LIST' } + { type: "Variable", id }, + { type: "Variable", id: "LIST" }, ], }, simulationList: { providesTags: (result) => result ? [ - ...result.map(({ id }) => ({ type: 'Simulation' as const, id })), - { type: 'Simulation', id: 'LIST' }, - ] - : [{ type: 'Simulation', id: 'LIST' }], - }, + ...result.map(({ id }) => ({ type: "Simulation" as const, id })), + { type: "Simulation", id: "LIST" }, + ] + : [{ type: "Simulation", id: "LIST" }], + }, simulationRetrieve: { - providesTags: (result, error, { id }) => [{ type: 'Simulation', id }], + providesTags: (result, error, { id }) => [{ type: "Simulation", id }], }, simulationUpdate: { - invalidatesTags: (result, error, { id }) => [{ type: 'Simulation', id }], + invalidatesTags: (result, error, { id }) => [{ type: "Simulation", id }], }, simulationCreate: { - invalidatesTags: [{ type: 'Simulation', id: 'LIST' }], + invalidatesTags: [{ type: "Simulation", id: "LIST" }], }, simulationDestroy: { invalidatesTags: (result, error, { id }) => [ - { type: 'Simulation', id }, - { type: 'Simulation', id: 'LIST' } + { type: "Simulation", id }, + { type: "Simulation", id: "LIST" }, ], }, protocolList: { providesTags: (result) => result ? [ - ...result.map(({ id }) => ({ type: 'Protocol' as const, id })), - { type: 'Protocol', id: 'LIST' }, - ] - : [{ type: 'Protocol', id: 'LIST' }], - }, + ...result.map(({ id }) => ({ type: "Protocol" as const, id })), + { type: "Protocol", id: "LIST" }, + ] + : [{ type: "Protocol", id: "LIST" }], + }, protocolRetrieve: { - providesTags: (result, error, { id }) => [{ type: 'Protocol', id }], + providesTags: (result, error, { id }) => [{ type: "Protocol", id }], }, protocolUpdate: { - invalidatesTags: (result, error, { id }) => [{ type: 'Protocol', id }], + invalidatesTags: (result, error, { id }) => [{ type: "Protocol", id }], }, protocolCreate: { - invalidatesTags: [{ type: 'Protocol', id: 'LIST' }], - }, + invalidatesTags: [{ type: "Protocol", id: "LIST" }], + }, protocolDestroy: { invalidatesTags: (result, error, { id }) => [ - { type: 'Protocol', id }, - { type: 'Protocol', id: 'LIST' } + { type: "Protocol", id }, + { type: "Protocol", id: "LIST" }, ], }, unitList: { providesTags: (result) => result ? [ - ...result.map(({ id }) => ({ type: 'Unit' as const, id })), - { type: 'Unit', id: 'LIST' }, - ] - : [{ type: 'Unit', id: 'LIST' }], + ...result.map(({ id }) => ({ type: "Unit" as const, id })), + { type: "Unit", id: "LIST" }, + ] + : [{ type: "Unit", id: "LIST" }], }, unitRetrieve: { - providesTags: (result, error, { id }) => [{ type: 'Unit', id }], + providesTags: (result, error, { id }) => [{ type: "Unit", id }], }, unitUpdate: { - invalidatesTags: (result, error, { id }) => [{ type: 'Unit', id }], + invalidatesTags: (result, error, { id }) => [{ type: "Unit", id }], }, unitCreate: { - invalidatesTags: [{ type: 'Unit', id: 'LIST' }], + invalidatesTags: [{ type: "Unit", id: "LIST" }], }, unitDestroy: { invalidatesTags: (result, error, { id }) => [ - { type: 'Unit', id }, - { type: 'Unit', id: 'LIST' } + { type: "Unit", id }, + { type: "Unit", id: "LIST" }, ], }, - }, //addTagTypes: ['User'], //endpoints: { diff --git a/frontend-v2/src/app/emptyApi.ts b/frontend-v2/src/app/emptyApi.ts index b9b2bfde..de7d2b95 100644 --- a/frontend-v2/src/app/emptyApi.ts +++ b/frontend-v2/src/app/emptyApi.ts @@ -1,18 +1,18 @@ // Or from '@reduxjs/toolkit/query' if not using the auto-generated hooks -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' -import { selectCsrf } from '../features/login/loginSlice'; -import { RootState } from './store'; +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; +import { selectCsrf } from "../features/login/loginSlice"; +import { RootState } from "./store"; const baseQuery = fetchBaseQuery({ baseUrl: "/", credentials: "include", prepareHeaders: (headers, { getState }) => { - const csrf = selectCsrf(getState() as RootState) + const csrf = selectCsrf(getState() as RootState); if (csrf) { headers.set("X-CSRFToken", csrf); } return headers; - } + }, }); // initialize an empty api service that we'll inject endpoints into later as needed @@ -20,4 +20,3 @@ export const emptySplitApi = createApi({ baseQuery, endpoints: () => ({}), }); - diff --git a/frontend-v2/src/app/hooks.ts b/frontend-v2/src/app/hooks.ts index 93bb45e0..e5de056c 100644 --- a/frontend-v2/src/app/hooks.ts +++ b/frontend-v2/src/app/hooks.ts @@ -1,7 +1,7 @@ -import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; -import type { RootState, AppDispatch } from './store'; -import { Control, FieldPath, FieldValues, useFormState } from 'react-hook-form'; -import { useEffect, useState } from 'react'; +import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; +import type { RootState, AppDispatch } from "./store"; +import { Control, FieldPath, FieldValues, useFormState } from "react-hook-form"; +import { useEffect, useState } from "react"; // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch = () => useDispatch(); @@ -12,11 +12,14 @@ type Props = { control: Control; }; -export function useFieldState({ name, control}: Props) { +export function useFieldState({ + name, + control, +}: Props) { const { defaultValues } = useFormState({ control, name }); - const keys = name.split('.'); + const keys = name.split("."); const defaultValue = keys.reduce((acc, key) => { - if (acc && typeof acc === 'object') { + if (acc && typeof acc === "object") { return acc[key]; } return undefined; diff --git a/frontend-v2/src/app/store.ts b/frontend-v2/src/app/store.ts index f3a3a67a..498db8c1 100644 --- a/frontend-v2/src/app/store.ts +++ b/frontend-v2/src/app/store.ts @@ -1,8 +1,8 @@ -import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit'; -import { setupListeners } from '@reduxjs/toolkit/query' -import loginReducer from '../features/login/loginSlice'; -import mainReducer from '../features/main/mainSlice'; -import { api } from '../app/api' +import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"; +import { setupListeners } from "@reduxjs/toolkit/query"; +import loginReducer from "../features/login/loginSlice"; +import mainReducer from "../features/main/mainSlice"; +import { api } from "../app/api"; export const store = configureStore({ reducer: { @@ -18,7 +18,7 @@ export const store = configureStore({ // optional, but required for refetchOnFocus/refetchOnReconnect behaviors // see `setupListeners` docs - takes an optional callback as the 2nd arg for customization -setupListeners(store.dispatch) +setupListeners(store.dispatch); export type AppDispatch = typeof store.dispatch; export type RootState = ReturnType; diff --git a/frontend-v2/src/components/Checkbox.tsx b/frontend-v2/src/components/Checkbox.tsx index 6e3029d1..31e751a8 100644 --- a/frontend-v2/src/components/Checkbox.tsx +++ b/frontend-v2/src/components/Checkbox.tsx @@ -1,6 +1,6 @@ -import React from 'react'; -import { Control, Controller, FieldPath, FieldValues } from 'react-hook-form'; -import * as material from '@mui/material'; +import React from "react"; +import { Control, Controller, FieldPath, FieldValues } from "react-hook-form"; +import * as material from "@mui/material"; type Props = { label: string; @@ -10,20 +10,29 @@ type Props = { checkboxFieldProps?: material.CheckboxProps; }; -function Checkbox({ label, name, control, rules, checkboxFieldProps }: Props): React.ReactElement { +function Checkbox({ + label, + name, + control, + rules, + checkboxFieldProps, +}: Props): React.ReactElement { return ( { + render={({ + field: { onChange, onBlur, value }, + fieldState: { error }, + }) => { return ( ({ label, name, control, rules, checkbox } label={label} /> - ) + ); }} /> ); -}; +} -export default Checkbox; \ No newline at end of file +export default Checkbox; diff --git a/frontend-v2/src/components/ConfirmationDialog.tsx b/frontend-v2/src/components/ConfirmationDialog.tsx index d6c772e1..65ac0481 100644 --- a/frontend-v2/src/components/ConfirmationDialog.tsx +++ b/frontend-v2/src/components/ConfirmationDialog.tsx @@ -1,5 +1,11 @@ -import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material'; -import React from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, +} from "@mui/material"; +import React from "react"; interface ConfirmationDialogProps { open: boolean; @@ -9,7 +15,13 @@ interface ConfirmationDialogProps { onCancel: () => void; } -const ConfirmationDialog: React.FC = ({ open, title, message, onConfirm, onCancel }) => { +const ConfirmationDialog: React.FC = ({ + open, + title, + message, + onConfirm, + onCancel, +}) => { return ( {title} @@ -26,4 +38,4 @@ const ConfirmationDialog: React.FC = ({ open, title, me ); }; -export default ConfirmationDialog; \ No newline at end of file +export default ConfirmationDialog; diff --git a/frontend-v2/src/components/DropdownButton.tsx b/frontend-v2/src/components/DropdownButton.tsx index ccab023c..440534de 100644 --- a/frontend-v2/src/components/DropdownButton.tsx +++ b/frontend-v2/src/components/DropdownButton.tsx @@ -1,5 +1,12 @@ -import { Button, IconButton, ListItem, ListItemButton, ListItemText, Popover } from '@mui/material'; -import React, { useState } from 'react'; +import { + Button, + IconButton, + ListItem, + ListItemButton, + ListItemText, + Popover, +} from "@mui/material"; +import React, { useState } from "react"; type Option = { label: string; @@ -15,7 +22,14 @@ type Props = { useIcon?: boolean; }; -const DropdownButton: React.FC = ({ data_cy, options, onOptionSelected, children, disabled, useIcon }) => { +const DropdownButton: React.FC = ({ + data_cy, + options, + onOptionSelected, + children, + disabled, + useIcon, +}) => { const [anchorEl, setAnchorEl] = useState(null); if (useIcon === undefined) { @@ -41,11 +55,20 @@ const DropdownButton: React.FC = ({ data_cy, options, onOptionSelected, c return (
{useIcon ? ( - + {children} ) : ( - )} @@ -54,11 +77,15 @@ const DropdownButton: React.FC = ({ data_cy, options, onOptionSelected, c anchorEl={anchorEl} onClose={() => setAnchorEl(null)} data-cy={`dropdown-button-popover`} - anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} - transformOrigin={{ vertical: 'top', horizontal: 'left' }} + anchorOrigin={{ vertical: "bottom", horizontal: "left" }} + transformOrigin={{ vertical: "top", horizontal: "left" }} > {options.map((option, index) => ( - handleOptionSelected(option)} data-cy={`${data_cy}-option-${option.label}`}> + handleOptionSelected(option)} + data-cy={`${data_cy}-option-${option.label}`} + > ))} @@ -67,4 +94,4 @@ const DropdownButton: React.FC = ({ data_cy, options, onOptionSelected, c ); }; -export default DropdownButton; \ No newline at end of file +export default DropdownButton; diff --git a/frontend-v2/src/components/DynamicTabs.tsx b/frontend-v2/src/components/DynamicTabs.tsx index bc5750cd..738e135a 100644 --- a/frontend-v2/src/components/DynamicTabs.tsx +++ b/frontend-v2/src/components/DynamicTabs.tsx @@ -1,9 +1,9 @@ -import React, { PropsWithChildren, ReactElement, useState } from 'react'; -import Box from '@mui/material/Box'; -import Tabs from '@mui/material/Tabs'; -import Tab from '@mui/material/Tab'; -import ErrorIcon from '@mui/icons-material/Error'; -import { Tooltip } from '@mui/material'; +import React, { PropsWithChildren, ReactElement, useState } from "react"; +import Box from "@mui/material/Box"; +import Tabs from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; +import ErrorIcon from "@mui/icons-material/Error"; +import { Tooltip } from "@mui/material"; interface TabContextProps { currentTab: number; @@ -24,43 +24,59 @@ interface TabPanelProps { index?: number; } -export const TabPanel: React.FC> = ({ index, children }) => { +export const TabPanel: React.FC> = ({ + index, + children, +}) => { const { currentTab } = React.useContext(TabContext); return ; }; -export const DynamicTabs: React.FC> = ({ tabNames, tabErrors, children }) => { +export const DynamicTabs: React.FC> = ({ + tabNames, + tabErrors, + children, +}) => { const [currentTab, setCurrentTab] = useState(0); let errors: { [key: string]: ReactElement } = {}; for (const key in tabErrors) { errors[key] = ( - + - ) + ); } - const handleChange = (event: React.SyntheticEvent, newValue: number) => { setCurrentTab(newValue); }; - return ( - - - + + + {tabNames.map((name, index) => ( - + ))} {React.Children.map(children, (child, index) => { - return React.cloneElement(child as React.ReactElement, { index }); + return React.cloneElement(child as React.ReactElement, { + index, + }); })} diff --git a/frontend-v2/src/components/FloatField.tsx b/frontend-v2/src/components/FloatField.tsx index 3b33c259..73d012fb 100644 --- a/frontend-v2/src/components/FloatField.tsx +++ b/frontend-v2/src/components/FloatField.tsx @@ -1,7 +1,7 @@ -import React from 'react'; -import { Control, Controller, FieldPath, FieldValues } from 'react-hook-form'; -import * as material from '@mui/material'; -import { useFieldState } from '../app/hooks'; +import React from "react"; +import { Control, Controller, FieldPath, FieldValues } from "react-hook-form"; +import * as material from "@mui/material"; +import { useFieldState } from "../app/hooks"; type Props = { label?: string; @@ -13,8 +13,8 @@ type Props = { }; function convert(value: any) { - if (typeof value === 'string') { - if (value !== '') { + if (typeof value === "string") { + if (value !== "") { return parseFloat(value); } else { return null; @@ -24,17 +24,27 @@ function convert(value: any) { } } -function FloatField({ label, name, control, rules, textFieldProps, data_cy }: Props): React.ReactElement { +function FloatField({ + label, + name, + control, + rules, + textFieldProps, + data_cy, +}: Props): React.ReactElement { const [fieldValue, setFieldValue] = useFieldState({ name, control }); return ( { + render={({ + field: { onChange, onBlur, value }, + fieldState: { error, isDirty, isTouched }, + }) => { const handleBlur = (e: React.FocusEvent) => { const updatedValue = convert(e.target.value); - console.log('handleBlur', updatedValue, value) + console.log("handleBlur", updatedValue, value); if (updatedValue !== value) { e.target.value = updatedValue as any; onChange(e); @@ -43,27 +53,34 @@ function FloatField({ label, name, control, rules, textFi }; const handleChange = (e: React.ChangeEvent) => { - console.log('handleChage', convert(e.target.value)) + console.log("handleChage", convert(e.target.value)); setFieldValue(convert(e.target.value)); }; return ( ); }} /> ); -}; +} export default FloatField; diff --git a/frontend-v2/src/components/HelpButton.tsx b/frontend-v2/src/components/HelpButton.tsx index a7b7679d..5923574c 100644 --- a/frontend-v2/src/components/HelpButton.tsx +++ b/frontend-v2/src/components/HelpButton.tsx @@ -1,7 +1,7 @@ -import React, { useState } from 'react'; -import { HelpOutline } from '@mui/icons-material'; -import HelpDialog from './HelpDialog'; -import { IconButton } from '@mui/material'; +import React, { useState } from "react"; +import { HelpOutline } from "@mui/icons-material"; +import HelpDialog from "./HelpDialog"; +import { IconButton } from "@mui/material"; interface HelpButtonProps { title: string; @@ -31,4 +31,4 @@ const HelpButton: React.FC = ({ title, children }) => { ); }; -export default HelpButton; \ No newline at end of file +export default HelpButton; diff --git a/frontend-v2/src/components/HelpDialog.tsx b/frontend-v2/src/components/HelpDialog.tsx index 993c7c45..6b05f06b 100644 --- a/frontend-v2/src/components/HelpDialog.tsx +++ b/frontend-v2/src/components/HelpDialog.tsx @@ -1,5 +1,11 @@ -import React from 'react'; -import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material'; +import React from "react"; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, +} from "@mui/material"; interface HelpDialogProps { open: boolean; @@ -8,7 +14,12 @@ interface HelpDialogProps { children: React.ReactNode; } -const HelpDialog: React.FC = ({ open, title, onClose, children }) => { +const HelpDialog: React.FC = ({ + open, + title, + onClose, + children, +}) => { return ( {title} @@ -22,4 +33,4 @@ const HelpDialog: React.FC = ({ open, title, onClose, children ); }; -export default HelpDialog; \ No newline at end of file +export default HelpDialog; diff --git a/frontend-v2/src/components/IntegerField.tsx b/frontend-v2/src/components/IntegerField.tsx index 439b6c95..6d674920 100644 --- a/frontend-v2/src/components/IntegerField.tsx +++ b/frontend-v2/src/components/IntegerField.tsx @@ -1,7 +1,7 @@ -import React from 'react'; -import { Control, Controller, FieldPath, FieldValues } from 'react-hook-form'; -import * as material from '@mui/material'; -import { useFieldState } from '../app/hooks'; +import React from "react"; +import { Control, Controller, FieldPath, FieldValues } from "react-hook-form"; +import * as material from "@mui/material"; +import { useFieldState } from "../app/hooks"; type Props = { label?: string; @@ -12,21 +12,30 @@ type Props = { }; function convert(value: any) { - if (typeof value === 'string' && value !== '') { + if (typeof value === "string" && value !== "") { return parseInt(value); } else { return value; } } -function IntegerField({ label, name, control, rules, textFieldProps }: Props): React.ReactElement { +function IntegerField({ + label, + name, + control, + rules, + textFieldProps, +}: Props): React.ReactElement { const [fieldValue, setFieldValue] = useFieldState({ name, control }); return ( { + render={({ + field: { onChange, onBlur, value }, + fieldState: { error, isDirty, isTouched }, + }) => { const handleBlur = (e: React.FocusEvent) => { const updatedValue = convert(e.target.value); if (updatedValue !== value) { @@ -41,21 +50,28 @@ function IntegerField({ label, name, control, rules, text }; return ( ); }} /> ); -}; +} export default IntegerField; diff --git a/frontend-v2/src/components/SelectField.tsx b/frontend-v2/src/components/SelectField.tsx index ba8e931a..ad6ff504 100644 --- a/frontend-v2/src/components/SelectField.tsx +++ b/frontend-v2/src/components/SelectField.tsx @@ -1,6 +1,14 @@ -import React from 'react'; -import { Control, Controller, FieldPath, FieldValues } from 'react-hook-form'; -import { Select, SelectProps, MenuItem, InputLabel, FormControl, OutlinedInput, FormControlProps } from '@mui/material'; +import React from "react"; +import { Control, Controller, FieldPath, FieldValues } from "react-hook-form"; +import { + Select, + SelectProps, + MenuItem, + InputLabel, + FormControl, + OutlinedInput, + FormControlProps, +} from "@mui/material"; type Option = { value: any; @@ -17,23 +25,36 @@ type Props = { formControlProps?: FormControlProps; }; -function SelectField({ label, name, options, control, rules, selectProps, formControlProps }: Props): React.ReactElement { +function SelectField({ + label, + name, + options, + control, + rules, + selectProps, + formControlProps, +}: Props): React.ReactElement { const labelId = `${name}-label`; const displayEmpty = selectProps?.displayEmpty || true; - const labelWidth = (label ? label.length : 0) * 9 + const labelWidth = (label ? label.length : 0) * 9; return ( ( - - {label} + render={({ + field: { onChange, onBlur, value }, + fieldState: { error }, + }) => ( + + + {label} + addUser(e.target.value as number)}> + - {biomarkerTypes.map((bt, i) => { - return ( - - {bt.name} - - ); - })} - - - - {apiError && {apiError}} - {fitErrors && - fitErrors.map((fitError, i) => ( - - {fitError} - - ))} - - {auce && ( - - )} - - {auce && ( - - )} -
- ); -} diff --git a/frontend/src/features/dataAnalysis/NcaChart.js b/frontend/src/features/dataAnalysis/NcaChart.js deleted file mode 100644 index 326ccab7..00000000 --- a/frontend/src/features/dataAnalysis/NcaChart.js +++ /dev/null @@ -1,295 +0,0 @@ -import React from "react"; -import makeStyles from '@mui/styles/makeStyles'; - -import { Scatter } from "react-chartjs-2"; - -import { getColor, getColorBackground } from "../modelling/ShapesAndColors"; - -const useStyles = makeStyles((theme) => ({ - root: { - position: 'relative', - margin: 'auto', - height: '60vh', - }, -})); - -let options = { - animation: { - duration: 0, - }, - responsive: true, - maintainAspectRatio: false, - scales: { - x: { - type: "linear", - title: { - text: "Time ", - display: true, - }, - }, - y: { - position: "left", - title: { - text: "Data Variable / Model Output (units defined in detail panels)", - display: true, - }, - }, - }, - plugins: { - legend: { - labels: { - usePointStyle: true, - }, - }, - tooltip: { - mode: "interpolate", - callbacks: { - label: function (context) { - const tooltipText = context.dataset.tooltipText; - if (tooltipText) { - return tooltipText; - } - return `(${context.parsed.x}, ${context.parsed.y})`; - }, - }, - }, - }, -}; - -export function NcaChart({ nca, biomarker_type, subject, mode }) { - const classes = useStyles(); - const renderChart = true; - - const first_point = [nca.times[0], nca.concentrations[0]]; - const last_point = [ - nca.times[nca.times.length - 1], - nca.concentrations[nca.concentrations.length - 1], - ]; - - const max_point = [nca.t_max, nca.c_max]; - - let before = [first_point, [0.0, nca.c_0]]; - if (before[0][0] === 0.0) { - before = [before[1]]; - } - - const after = [ - last_point, - [last_point[0] + 0.5 * nca.t_half, last_point[1] * 0.75], - [last_point[0] + nca.t_half, last_point[1] * 0.5], - ]; - - let datasets = []; - - // main scatter plot - if (mode !== "aucm") { - datasets.push({ - borderColor: getColor(0), - backgroundColor: getColorBackground(0), - fill: mode === "auc", - label: - biomarker_type.name + " Concentration for ID " + subject.id_in_dataset, - data: nca.concentrations.map((y, i) => ({ - x: nca.times[i], - y: y, - })), - }); - } - - const rounding = 3; - const c0_text = "C_0 = " + nca.c_0.toPrecision(rounding); - const lambdaz_text = [ - "Lambda_z = " + nca.lambda_z.toPrecision(rounding), - "T_half = " + nca.t_half.toPrecision(rounding), - "Num_points = " + nca.num_points, - "R2 = " + nca.r2.toPrecision(rounding), - ]; - const t_half_text = "T_half = " + nca.t_half.toPrecision(rounding); - - if (mode === "extrapolation") { - datasets.push({ - borderColor: getColor(1), - backgroundColor: getColor(1), - label: "Extrapolation before", - tooltipText: c0_text, - data: before.map((pt, i) => ({ - x: pt[0], - y: pt[1], - })), - }); - - datasets.push({ - borderColor: getColor(1), - backgroundColor: getColor(1), - label: "Extrapolation after", - type: "line", - tooltipText: t_half_text, - lineTension: 0, - interpolate: true, - data: after.map((pt, i) => ({ - x: pt[0], - y: pt[1], - })), - }); - - datasets.push({ - borderColor: getColor(2), - backgroundColor: getColorBackground(2), - label: "Extrapolation", - tooltipText: lambdaz_text, - lineTension: 0, - interpolate: true, - data: [ - { x: after[0][0], y: after[0][1] }, - { x: after[2][0], y: after[2][1] }, - ], - }); - } - - if (mode === "aucm") { - const first_moment_y = nca.concentrations.map((c, i) => c * nca.times[i]); - datasets.push({ - borderColor: getColor(3), - backgroundColor: getColorBackground(3), - label: "First Moment", - fill: true, - lineTension: 0, - interpolate: true, - data: first_moment_y.map((fm, i) => ({ - x: nca.times[i], - y: fm, - })), - }); - - datasets.push({ - borderColor: getColor(4), - backgroundColor: getColorBackground(4), - label: "Extrapolation before", - tooltipText: c0_text, - lineTension: 0, - interpolate: true, - data: before.map((pt, i) => ({ - x: pt[0], - y: pt[1] * pt[0], - })), - }); - - datasets.push({ - borderColor: getColor(5), - backgroundColor: getColorBackground(5), - label: "Extrapolation after", - type: "line", - tooltipText: t_half_text, - lineTension: 0, - interpolate: true, - data: after.map((pt, i) => ({ - x: pt[0], - y: pt[1] * pt[0], - })), - }); - - const aumcHoverPoint = [ - 0.5 * (Math.max(...nca.times) + Math.min(...nca.times)), - 0.5 * Math.max(...first_moment_y), - ]; - const aumcText = "AUMC_0_last = " + nca.aumc_0_last.toPrecision(rounding); - - datasets.push({ - borderColor: getColor(6), - backgroundColor: getColorBackground(6), - label: "AUMC_0_last", - tooltipText: aumcText, - data: [ - { - x: aumcHoverPoint[0], - y: aumcHoverPoint[1], - }, - ], - }); - - const aumcInfHoverPoint = [ - last_point[0] * 1.05, - last_point[1] * last_point[0] * 0.5, - ]; - const aumcInfText = [ - "AUMC = " + nca.aumc.toPrecision(rounding), - "AUMC_extrap_percent = " + nca.aumc_extrap_percent.toPrecision(rounding), - ]; - - datasets.push({ - borderColor: getColor(8), - backgroundColor: getColorBackground(8), - label: "AUMC", - tooltipText: aumcInfText, - data: [ - { - x: aumcInfHoverPoint[0], - y: aumcInfHoverPoint[1], - }, - ], - }); - } - - if (mode === "auc") { - const aucHoverPoint = [max_point[0], 0.5 * max_point[1]]; - const aucText = "AUC_0_last = " + nca.auc_0_last.toPrecision(rounding); - - datasets.push({ - borderColor: getColor(6), - backgroundColor: getColorBackground(6), - label: "AUC_0_last", - tooltipText: aucText, - data: [ - { - x: aucHoverPoint[0], - y: aucHoverPoint[1], - }, - ], - }); - - const aucInfHoverPoint = [last_point[0] * 1.05, last_point[1] * 0.5]; - const aucInfText = [ - "AUC = " + nca.auc_infinity.toPrecision(rounding), - "AUC_infinity_dose = " + nca.auc_infinity_dose.toPrecision(rounding), - "AUC_extrap_percent = " + nca.auc_extrap_percent.toPrecision(rounding), - ]; - - datasets.push({ - borderColor: getColor(7), - backgroundColor: getColorBackground(7), - label: "AUC", - tooltipText: aucInfText, - data: [ - { - x: aucInfHoverPoint[0], - y: aucInfHoverPoint[1], - }, - ], - }); - } - - if (mode === "tmax") { - const tmax_text = "T_max = " + max_point[0]; - const cmax_text = "C_max = " + max_point[1]; - - datasets.push({ - borderColor: getColor(8), - backgroundColor: getColorBackground(8), - label: "T_max/C_max", - tooltipText: [tmax_text, cmax_text], - data: [ - { x: max_point[0], y: 0 }, - { x: max_point[0], y: max_point[1] }, - { x: 0, y: max_point[1] }, - ], - }); - } - - const data = { datasets }; - - return ( -
- {renderChart && } -
- ); -} diff --git a/frontend/src/features/dataAnalysis/NcaDetail.js b/frontend/src/features/dataAnalysis/NcaDetail.js deleted file mode 100644 index 48720351..00000000 --- a/frontend/src/features/dataAnalysis/NcaDetail.js +++ /dev/null @@ -1,167 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useSelector } from "react-redux"; -import makeStyles from '@mui/styles/makeStyles'; - -import Select from "@mui/material/Select"; -import InputLabel from "@mui/material/InputLabel"; -import MenuItem from "@mui/material/MenuItem"; -import FormControl from "@mui/material/FormControl"; - -import { NcaChart } from "./NcaChart"; -import { - selectProtocolById, -} from "../protocols/protocolsSlice"; -import { - selectDatasetById, -} from "../datasets/datasetsSlice"; -import { - selectBiomarkerTypesByDatasetId, - selectBiomarkerTypeById, -} from "../datasets/biomarkerTypesSlice"; - -import { selectSubjectById } from "../datasets/subjectsSlice"; - -import { api } from "../../Api"; - -const useStyles = makeStyles((theme) => ({ - root: { - marginTop: theme.spacing(2), - flexGrow: 1, - }, - formControl: { - margin: theme.spacing(1), - minWidth: 120, - }, -})); - -export default function NcaDetail({ project, dataset }) { - const classes = useStyles(); - const [nca, setNca] = useState(null); - const [subjectId, setSubjectId] = useState(null); - const [mode, setMode] = useState("data"); - const modeOptions = [ - { key: "Data", value: "data" }, - { key: "Maximum and Extrapolation", value: "extrapolation" }, - { key: "Area Under the Curve", value: "auc" }, - { key: "Area Under the First Moment", value: "aucm" }, - ]; - - const [biomarkerTypeId, setBiomarkerTypeId] = useState(null); - const biomarker_type = useSelector((state) => - biomarkerTypeId ? selectBiomarkerTypeById(state, biomarkerTypeId) : null - ); - - - const subject = useSelector((state) => - subjectId ? selectSubjectById(state, subjectId) : null - ); - - const biomarkerTypes = useSelector((state) => - selectBiomarkerTypesByDatasetId(state, dataset.id) - ); - - const subjectsEntities = useSelector((state) => state.subjects.entities); - - const subjectsInBiomarkerType = biomarkerTypeId - ? [... new Set(biomarker_type.data.subjects)] - : []; - - const subjectOptions = subjectsInBiomarkerType.filter(s_id => - (subjectsEntities[s_id].protocol) - ).map(s_id => ({ - key: subjectsEntities[s_id].id_in_dataset, value: s_id - })) - - useEffect(() => { - if (biomarkerTypeId != null && subjectId != null) { - api - .post(`/api/nca/`, { - biomarker_type_id: biomarkerTypeId, - subject_id: subjectId, - }) - .then(setNca); - } else { - setNca(null) - } - }, [biomarkerTypeId, subjectId]); - - const handleSubjectChange = (event) => { - setSubjectId(event.target.value); - }; - const handleBiomarkerTypeChange = (event) => { - setBiomarkerTypeId(event.target.value); - setSubjectId(null); - }; - const handleModeChange = (event) => { - setMode(event.target.value); - }; - - console.log("nca", nca); - - return ( -
- - Variable - - - - - Subject - - - - - {nca && subject && ( - - Show - - - )} - - {nca && subject && ( - - )} -
- ); -} diff --git a/frontend/src/features/datasets/BiomarkerTypeSubform.js b/frontend/src/features/datasets/BiomarkerTypeSubform.js deleted file mode 100644 index 01b08e4e..00000000 --- a/frontend/src/features/datasets/BiomarkerTypeSubform.js +++ /dev/null @@ -1,136 +0,0 @@ -import React, { useEffect } from "react"; -import { useSelector, useDispatch } from "react-redux"; -import IconButton from "@mui/material/IconButton"; -import { useForm, useFormState } from "react-hook-form"; -import SaveIcon from "@mui/icons-material/Save"; -import Stack from "@mui/material/Stack"; -import Grid from "@mui/material/Grid"; - -import { - FormCheckboxField, - FormTextField, - FormSelectField, -} from "../forms/FormComponents"; -import { selectUnitById } from "../projects/unitsSlice"; -import { - selectBiomarkerTypeById, - updateBiomarkerType, -} from "./biomarkerTypesSlice"; - -export default function BiomarkerTypeSubform({ biomarker_id, disableSave }) { - const dispatch = useDispatch(); - let biomarker_type = useSelector((state) => - selectBiomarkerTypeById(state, biomarker_id) - ); - if (!biomarker_type) { - biomarker_type = { - default_value: false, - axis: false, - }; - } - const unit_id = biomarker_type ? biomarker_type.display_unit : 1; - let unit = useSelector((state) => selectUnitById(state, unit_id)); - if (!unit) { - unit = { - symbol: "X", - compatible_units: [], - }; - } - const unitOptions = unit.compatible_units.map((u) => { - return { key: u.symbol, value: u.id }; - }); - - const time_unit_id = biomarker_type ? biomarker_type.display_time_unit : 1; - let time_unit = useSelector((state) => selectUnitById(state, time_unit_id)); - if (!time_unit) { - time_unit = { - symbol: "X", - compatible_units: [], - }; - } - const timeUnitOptions = time_unit.compatible_units.map((u) => { - return { key: u.symbol, value: u.id }; - }); - - const { control, handleSubmit, reset } = useForm({ - defaultValues: { - id: biomarker_type.id, - display: biomarker_type.display, - name: biomarker_type.name, - color: biomarker_type.color, - display_unit: biomarker_type.display_unit, - display_time_unit: biomarker_type.display_time_unit, - axis: biomarker_type.axis, - }, - }); - - const { isDirty } = useFormState({ control }); - - useEffect(() => { - reset(biomarker_type); - }, [reset, biomarker_type]); - - const onSubmit = (values) => { - console.log("submit biomarker_type", values); - dispatch(updateBiomarkerType(values)); - }; - - const axisOptions = [ - { value: false, key: "LHS" }, - { value: true, key: "RHS" }, - ]; - - return ( - - - - - - - - - - - - - - {isDirty && ( - - - - )} - - - ); -} diff --git a/frontend/src/features/datasets/DatasetDetail.js b/frontend/src/features/datasets/DatasetDetail.js deleted file mode 100644 index 8ecc46b4..00000000 --- a/frontend/src/features/datasets/DatasetDetail.js +++ /dev/null @@ -1,221 +0,0 @@ -import React, { useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import Button from "@mui/material/Button"; -import Grid from "@mui/material/Grid"; -import Box from "@mui/material/Box"; -import Alert from '@mui/material/Alert'; -import { useForm } from "react-hook-form"; -import makeStyles from '@mui/styles/makeStyles'; -import Typography from "@mui/material/Typography"; -import List from "@mui/material/List"; -import ListItem from "@mui/material/ListItem"; -import Dialog from "@mui/material/Dialog"; -import DialogTitle from "@mui/material/DialogTitle"; -import DialogActions from "@mui/material/DialogActions"; -import Tab from "@mui/material/Tab"; -import Tabs from "@mui/material/Tabs"; -import Stack from "@mui/material/Stack"; - -import Accordion from "@mui/material/Accordion"; -import AccordionSummary from "@mui/material/AccordionSummary"; -import Paper from "@mui/material/Paper"; -import AccordionDetails from "@mui/material/AccordionDetails"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; - -import Header from "../modelling/Header"; -import Footer from "../modelling/Footer"; -import SubjectsTable from "./SubjectsTable"; -import BiomarkerTypeSubform from "./BiomarkerTypeSubform"; - -import AuceDetail from "../dataAnalysis/AuceDetail" -import NcaDetail from "../dataAnalysis/NcaDetail" - -import { userHasReadOnlyAccess } from "../projects/projectsSlice"; - -import { - updateDataset, - uploadDatasetCsv, - deleteDataset, -} from "../datasets/datasetsSlice"; -import { FormTextField, FormDateTimeField } from "../forms/FormComponents"; - -const useStyles = makeStyles((theme) => ({ - root: { - width: "100%", - padding: theme.spacing(2), - maxHeight: '75vh', overflow: 'auto' - }, - rootTab: { - width: "100%", - padding: theme.spacing(2), - height: '75vh', overflow: 'auto' - }, - paper: { - padding: theme.spacing(2) - }, - tabs: { - backgroundColor: theme.palette.primary.main, - color: theme.palette.primary.contrastText, - borderBottom: 1, - borderColor: 'divider', - }, - dialogFooter: { - } -})); - -function TabPanel(props) { - const { children, value, index, ...other } = props; - - return ( - - ); -} - -function a11yProps(index: number) { - return { - id: `simple-tab-${index}`, - 'aria-controls': `simple-tabpanel-${index}`, - }; -} - -function DataAnalysisDialog({ project, onClose, dataset, open }) { - const classes = useStyles(); - const [value, setValue] = React.useState(0); - const handleClose = () => { - onClose(); - }; - - const handleChange = (event, newValue) => { - setValue(newValue); - }; - - return ( - -
- - - - -
- - - - - - - - - -
- ); -} - - -export default function DatasetDetail({ project, dataset }) { - const { control, handleSubmit, reset } = useForm(); - const dispatch = useDispatch(); - - const [openDataAnalysis, setOpenDataAnalysis] = React.useState(false); - const handleDataAnalysis = () => { - setOpenDataAnalysis(true); - }; - const handleCloseDataAnalysis = () => { - setOpenDataAnalysis(false); - }; - - useEffect(() => { - reset(dataset); - }, [reset, dataset]); - - - const handleDatasetDelete = () => { - dispatch(deleteDataset(dataset.id)); - }; - - const handleFileUpload = (event) => { - console.log(event) - const files = Array.from(event.target.files); - const [file] = files; - dispatch(uploadDatasetCsv({ id: dataset.id, csv: file })); - }; - - const onSubmit = (values) => { - dispatch(updateDataset(values)); - }; - - const disableSave = useSelector(state => userHasReadOnlyAccess(state, project)); - - return ( - -
-
- - - - - Variables - - {dataset.biomarker_types.map((biomarker_id, index) => { - return ( - - - - ); - })} - - - - {dataset.errors && - dataset.errors.map((error, index) => ( - - {error} - - ))} - -