From 90b6019d704f52d73e5fde5f13586d77846f6a18 Mon Sep 17 00:00:00 2001 From: Craig Yu Date: Tue, 13 Jun 2023 14:36:20 -0700 Subject: [PATCH] Feat/Parent tree step foundation (#182) * fix: add prevent duplicate logic to orcahrd step * fix: remove prevent duplicate orchard id logic * fix: modularize things * fix: add root to dockerfile dev * feat: add DescriptionBox component * feat: adjust description box styling * fix: update carbon version * fix: test link in notification box * fix: remove !import for grid and refactor related code * feat: add padding to page bottom * fix: change where padding bottom is added * feat: add btn text and icon customization feat to submit modal * fix: update snapshot * fix: add end of file lines * fix: fix collection start date col size * feat: add proper style to tab list * feat: complete notification box * feat: add placeholder component for other 2 tabs * feat: dynamically switching description & notification * feat: add merge orchards logic * feat: add error notification table * feat: fetch data for parent tree genetic quality * feat: adding constants * feat: add dynamic table header * feat: implement table row rendering * fix: update data storing logic * feat: link state to parent component * fix: update css * feat: clean table when orchard changes * feat: add tooltip and input to tables * feat: add table col toggling * fix: lint update * fix: update node package * Revert "fix: update node package" This reverts commit 0191a6114568bf8061cc9843034d00b7d2678d2d. * fix: change h1 to h2 for description box * fix: upgrade webpack and ts-checked-plugin * fix: specify text type for description-box * fix: fix typo renderTableCell * fix: get rid of silly text --------- Co-authored-by: Derek Roberts Co-authored-by: Ricardo Campos --- frontend/src/api-service/ApiConfig.ts | 6 +- frontend/src/api-service/orchardAPI.ts | 12 +- .../src/components/DescriptionBox/index.tsx | 7 +- .../src/components/DescriptionBox/styles.scss | 6 +- frontend/src/components/FormReview/index.tsx | 6 +- .../OrchardStep/index.tsx | 16 +- .../Tabs/ConeAndPollen/constants.tsx | 20 - .../Tabs/ConeAndPollen/index.tsx | 47 -- .../ParentTreeStep/Tabs/styles.scss | 20 - .../UploadFileModal/constants.ts | 21 + .../ParentTreeStep/UploadFileModal/index.tsx | 28 +- .../ParentTreeStep/constants.tsx | 545 +++++++++--------- .../ParentTreeStep/definitions.ts | 80 ++- .../ParentTreeStep/index.tsx | 438 ++++++++++++-- .../ParentTreeStep/styles.scss | 88 +++ .../ParentTreeStep/utils.ts | 38 ++ frontend/src/theme/components-overrides.scss | 21 +- .../src/types/ParentTreeGeneticQualityType.ts | 34 ++ .../SeedlotRegistrationForm/definitions.ts | 8 +- .../Seedlot/SeedlotRegistrationForm/index.tsx | 13 +- .../Seedlot/SeedlotRegistrationForm/utils.ts | 14 +- 21 files changed, 1010 insertions(+), 458 deletions(-) delete mode 100644 frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/Tabs/ConeAndPollen/constants.tsx delete mode 100644 frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/Tabs/ConeAndPollen/index.tsx delete mode 100644 frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/Tabs/styles.scss create mode 100644 frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/UploadFileModal/constants.ts create mode 100644 frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/utils.ts create mode 100644 frontend/src/types/ParentTreeGeneticQualityType.ts diff --git a/frontend/src/api-service/ApiConfig.ts b/frontend/src/api-service/ApiConfig.ts index e25913c6d..13a369212 100644 --- a/frontend/src/api-service/ApiConfig.ts +++ b/frontend/src/api-service/ApiConfig.ts @@ -33,6 +33,8 @@ const ApiConfig = { paymentMethod: `${serverHost}/api/payment-methods`, + orchardSeedPlan: `${serverHost}/api/orchards`, + /** * ORACLE API */ @@ -40,7 +42,9 @@ const ApiConfig = { fundingSource: `${oracleServerHost}/api/funding-sources`, - orchard: `${oracleServerHost}/api/orchards` + orchard: `${oracleServerHost}/api/orchards`, + + parentTreeGeneticQuality: `${oracleServerHost}/api/orchards/parent-tree-genetic-quality` }; export default ApiConfig; diff --git a/frontend/src/api-service/orchardAPI.ts b/frontend/src/api-service/orchardAPI.ts index 69df2b07b..a0020e533 100644 --- a/frontend/src/api-service/orchardAPI.ts +++ b/frontend/src/api-service/orchardAPI.ts @@ -1,9 +1,17 @@ import ApiConfig from './ApiConfig'; import api from './api'; -const getOrchardByID = (orchardID: string) => { +export const getOrchardByID = (orchardID: string) => { const url = `${ApiConfig.orchard}/${orchardID}`; return api.get(url).then((res) => res.data); }; -export default getOrchardByID; +export const getSeedPlanUnits = (orchardID: string) => { + const url = `${ApiConfig.orchardSeedPlan}/${orchardID}/seed-plan-units?active=true`; + return api.get(url).then((res) => res.data); +}; + +export const getParentTreeGeneQuali = (orchardID: string, spu: string) => { + const url = `${ApiConfig.parentTreeGeneticQuality}/${orchardID}/${spu}`; + return api.get(url).then((res) => res.data); +}; diff --git a/frontend/src/components/DescriptionBox/index.tsx b/frontend/src/components/DescriptionBox/index.tsx index 9da541ce5..bf2d3afd2 100644 --- a/frontend/src/components/DescriptionBox/index.tsx +++ b/frontend/src/components/DescriptionBox/index.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Column } from '@carbon/react'; import './styles.scss'; type DescriptionBoxProps = { @@ -13,10 +12,10 @@ const DescriptionBox = ( description }: DescriptionBoxProps ) => ( - -

{header}

+
+

{header}

{description}
- +
); export default DescriptionBox; diff --git a/frontend/src/components/DescriptionBox/styles.scss b/frontend/src/components/DescriptionBox/styles.scss index 3cbe38971..814147bc0 100644 --- a/frontend/src/components/DescriptionBox/styles.scss +++ b/frontend/src/components/DescriptionBox/styles.scss @@ -1,15 +1,17 @@ +@use '@carbon/type'; @use '../../theme/variables.scss' as vars; .description-box-container{ + width: 100%; .description-box-header{ + @include type.type-style('heading-03'); color: var(--#{vars.$bcgov-prefix}-text-primary); - font-size: 1.25rem; } .description-box-description{ + @include type.type-style('body-01'); color: var(--#{vars.$bcgov-prefix}-text-secondary); - font-size: 0.875rem; margin-top: 0.5rem; a{ diff --git a/frontend/src/components/FormReview/index.tsx b/frontend/src/components/FormReview/index.tsx index 46d49add1..902532d2c 100644 --- a/frontend/src/components/FormReview/index.tsx +++ b/frontend/src/components/FormReview/index.tsx @@ -148,7 +148,10 @@ const FormReview = () => { orchardStep: orchardMock, collectionStep: collectionMock, extractionStorageStep: extractionMock, - parentTreeStep: {} + parentTreeStep: { + tableRowData: {}, + notifCtrl: {} + } }); return ( @@ -267,6 +270,7 @@ const FormReview = () => { state={allStepData.orchardStep} setStepData={() => { }} readOnly + cleanParentTables={() => { }} /> + + + { + /** + * Check if it's fetching parent tree data + */ + orchardsData.length > 0 && Object.values(state.tableRowData).length === 0 + ? ( + + ) + : ( + + + + { + headerConfig.map((header) => ( + (header.availableInTabs.includes(currentTab) && header.enabled) + ? ( + + + {header.name} + + + ) + : null + )) + } + + + + { + // Since we cannot sort an Object + // we will have to sort the array here + sortRowItem(Object.values(state.tableRowData)).map((rowData) => ( + + { + headerConfig.map((header) => ( + renderTableCell(rowData, header) + )) + } + + )) + } + +
+ ) + } + + +
+ + + + + + ); }; diff --git a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/styles.scss b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/styles.scss index 9d7ad957b..e191e527f 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/styles.scss +++ b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/styles.scss @@ -1,5 +1,93 @@ +@use '@carbon/type'; @use '../../../theme/variables.scss' as vars; +.parent-tree-step-container { + padding-left: 0rem; + padding-right: 0rem; + + .parent-tree-step-tab-list { + margin-left: -1rem; + border-bottom: 2px solid var(--#{vars.$bcgov-prefix}-border-subtle-01); + width: calc(100% + 1rem); + + .bcgov--tab--list { + // Without this the tab list will be pushed up by 2px due to the border bottom above + margin-bottom: -2px; + } + } +} + .parent-tree-tab-container { + padding: 2.5rem 0; + + .description-box-container { + padding-left: 0; + } + + .notification-row { + margin-top: 2.5rem; + + .#{vars.$bcgov-prefix}--actionable-notification__content { + display: flex; + flex-direction: column; + } + + .#{vars.$bcgov-prefix}--actionable-notification__title { + @include type.type-style('heading-compact-01'); + } + + .#{vars.$bcgov-prefix}--actionable-notification { + width: 100%; + max-width: none; + } + + .#{vars.$bcgov-prefix}--actionable-notification__action-button { + display: none; + } + + .notification-subtitle { + @include type.type-style('body-compact-01'); + } + + .notification-link { + color: var(--#{vars.$bcgov-prefix}-link-primary); + text-decoration: underline; + } + } +} + +.parent-tree-step-table-container { + margin-top: 2rem; + + .#{vars.$bcgov-prefix}--data-table { + .#{vars.$bcgov-prefix}--text-input { + background: transparent; + border-bottom: none; + border-radius: 0; + padding: 0; + } + + .#{vars.$bcgov-prefix}--text-input:focus { + background: white; + } + + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + input[type=number] { + -moz-appearance: textfield; + } + } + + .#{vars.$bcgov-prefix}--data-table-content{ + overflow: visible; + } + .#{vars.$bcgov-prefix}--definition-term{ + border-bottom: none; + @include type.type-style('heading-compact-01'); + } } diff --git a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/utils.ts b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/utils.ts new file mode 100644 index 000000000..e888a80dd --- /dev/null +++ b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/utils.ts @@ -0,0 +1,38 @@ +import { OrchardObj } from '../OrchardStep/definitions'; +import { RowItem } from './definitions'; + +export const getTabString = (selectedIndex: number) => { + switch (selectedIndex) { + case 0: + return 'coneTab'; + case 1: + return 'successTab'; + case 2: + return 'mixTab'; + default: + return 'coneTab'; + } +}; + +// Returns a merged array of orchards, duplicated orchards are merged as one +export const processOrchards = (orchards: Array): Array => { + const obj = {}; + + orchards.forEach((orchard) => { + if ( + !Object.prototype.hasOwnProperty.call(obj, orchard.orchardId) + && orchard.orchardId !== '' + && orchard.orchardLabel !== '' + ) { + Object.assign(obj, { + [orchard.orchardId]: orchard + }); + } + }); + + return Object.values(obj); +}; + +export const sortRowItem = (rows: Array) => ( + rows.sort((a: RowItem, b: RowItem) => Number(a.clone_number) - Number(b.clone_number)) +); diff --git a/frontend/src/theme/components-overrides.scss b/frontend/src/theme/components-overrides.scss index 672941265..fe3e01b56 100644 --- a/frontend/src/theme/components-overrides.scss +++ b/frontend/src/theme/components-overrides.scss @@ -147,20 +147,7 @@ nav.#{variables.$bcgov-prefix}--side-nav { font-weight: bold; } -.#{variables.$bcgov-prefix}--data-table { - padding: 0 !important; -} - -.#{variables.$bcgov-prefix}--data-table tr th{ - background-color: var(--#{variables.$bcgov-prefix}-layer-01); -} - -.#{variables.$bcgov-prefix}--data-table tr td{ - background-color: var(--#{variables.$bcgov-prefix}-layer-02); -} - -.#{variables.$bcgov-prefix}--table-header-label, -.#{variables.$bcgov-prefix}--data-table.#{variables.$bcgov-prefix}--skeleton th { +.#{variables.$bcgov-prefix}--table-header-label { font-weight: bold; } @@ -241,9 +228,3 @@ li.#{variables.$bcgov-prefix}--overflow-menu-options__option:first-child{ li.#{variables.$bcgov-prefix}--overflow-menu-options__option:last-child{ border-radius: 0 0 4px 4px; } - -table.#{variables.$bcgov-prefix}--data-table th, -table.#{variables.$bcgov-prefix}--data-table td, -table.#{variables.$bcgov-prefix}--data-table th button { - padding-left: 2rem; -} \ No newline at end of file diff --git a/frontend/src/types/ParentTreeGeneticQualityType.ts b/frontend/src/types/ParentTreeGeneticQualityType.ts new file mode 100644 index 000000000..6bcd2c433 --- /dev/null +++ b/frontend/src/types/ParentTreeGeneticQualityType.ts @@ -0,0 +1,34 @@ +export enum GenWorthCodeEnum{ + AD = 'ad', + DFS = 'dfs', + DFU = 'dfu', + DFW = 'dfw', + DSB = 'dsb', + DSC = 'dsc', + DSG = 'dsg', + GVO = 'gvo', + IWS = 'iws', + WDU = 'wdu', + WWD = 'wwd' +} + +type SingleParentTreeGeneticObj = { + geneticTypeCode: string, + geneticWorthCode: keyof typeof GenWorthCodeEnum, + geneticQualityValue: number, +}; + +type ParentTreeType = { + [key: string]: any, + parentTreeId: number, + parentTreeNumber: string, + parentTreeGeneticQualities: Array +} + +export type ParentTreeGeneticQualityType = { + [key: string]: any, + orchardId:string, + vegetationCode: string, + seedPlanningUnitId: number, + parentTrees: Array +} diff --git a/frontend/src/views/Seedlot/SeedlotRegistrationForm/definitions.ts b/frontend/src/views/Seedlot/SeedlotRegistrationForm/definitions.ts index 31f12ba0b..87830998c 100644 --- a/frontend/src/views/Seedlot/SeedlotRegistrationForm/definitions.ts +++ b/frontend/src/views/Seedlot/SeedlotRegistrationForm/definitions.ts @@ -3,13 +3,19 @@ import InterimForm from '../../../components/SeedlotRegistrationSteps/InterimSte import { SingleOwnerForm } from '../../../components/SeedlotRegistrationSteps/OwnershipStep/definitions'; import ExtractionStorage from '../../../types/SeedlotTypes/ExtractionStorage'; import { OrchardForm } from '../../../components/SeedlotRegistrationSteps/OrchardStep/definitions'; +import { RowDataDictType, NotifCtrlType } from '../../../components/SeedlotRegistrationSteps/ParentTreeStep/definitions'; + +export type ParentTreeStepDataObj = { + tableRowData: RowDataDictType, + notifCtrl: NotifCtrlType +} export type AllStepData = { collectionStep: CollectionForm, interimStep: InterimForm, ownershipStep: Array, orchardStep: OrchardForm, - parentTreeStep: any, + parentTreeStep: ParentTreeStepDataObj, extractionStorageStep: ExtractionStorage } diff --git a/frontend/src/views/Seedlot/SeedlotRegistrationForm/index.tsx b/frontend/src/views/Seedlot/SeedlotRegistrationForm/index.tsx index c0ee308d7..92d27b829 100644 --- a/frontend/src/views/Seedlot/SeedlotRegistrationForm/index.tsx +++ b/frontend/src/views/Seedlot/SeedlotRegistrationForm/index.tsx @@ -34,7 +34,8 @@ import { initOwnershipState, initExtractionStorageState, initInvalidationObj, - initOwnerShipInvalidState + initOwnerShipInvalidState, + initParentTreeState } from './utils'; import { getDropDownList } from '../../../utils/DropDownUtils'; import { CollectionForm } from '../../../components/SeedlotRegistrationSteps/CollectionStep/utils'; @@ -82,7 +83,7 @@ const SeedlotRegistrationForm = () => { interimStep: initInterimState(defaultAgency, defaultCode), ownershipStep: [initOwnershipState(defaultAgency, defaultCode)], orchardStep: initOrchardState(), - parentTreeStep: {}, + parentTreeStep: initParentTreeState(), extractionStorageStep: initExtractionStorageState(defaultExtStorAgency, defaultExtStorCode) }); @@ -119,6 +120,12 @@ const SeedlotRegistrationForm = () => { setFormStep(newStep); }; + const cleanParentTables = () => { + const clonedState = { ...allStepData }; + clonedState.parentTreeStep.tableRowData = {}; + setAllStepData(clonedState); + }; + const renderStep = () => { const seedlotSpecies = seedlotInfoQuery.data.seedlot?.lot_species ?? { code: '', @@ -170,6 +177,7 @@ const SeedlotRegistrationForm = () => { cleanParentTables()} setStepData={(data: OrchardForm) => setStepData('orchardStep', data)} /> ); @@ -179,6 +187,7 @@ const SeedlotRegistrationForm = () => { setStepData('parentTreeStep', data)} /> ); diff --git a/frontend/src/views/Seedlot/SeedlotRegistrationForm/utils.ts b/frontend/src/views/Seedlot/SeedlotRegistrationForm/utils.ts index ebb3078f3..73995ee34 100644 --- a/frontend/src/views/Seedlot/SeedlotRegistrationForm/utils.ts +++ b/frontend/src/views/Seedlot/SeedlotRegistrationForm/utils.ts @@ -3,7 +3,8 @@ import { ownerTemplate, validTemplate as ownerInvalidTemplate } from '../../../components/SeedlotRegistrationSteps/OwnershipStep/constants'; -import { FormInvalidationObj, OwnershipInvalidObj } from './definitions'; +import { notificationCtrlObj } from '../../../components/SeedlotRegistrationSteps/ParentTreeStep/constants'; +import { FormInvalidationObj, OwnershipInvalidObj, ParentTreeStepDataObj } from './definitions'; export const initCollectionState = ( defaultAgency: string, @@ -71,6 +72,17 @@ export const initOrchardState = (): OrchardForm => ( } ); +// { +// tableRowData: RowDataDictType, +// notifCtrl +// } +export const initParentTreeState = (): ParentTreeStepDataObj => ( + { + tableRowData: {}, + notifCtrl: structuredClone(notificationCtrlObj) + } +); + export const initExtractionStorageState = ( defaultAgency: string, defaultCode: string