From 8526d8480e2ddbee2d6f82400cbfd597ce7c8200 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Thu, 26 Sep 2024 20:13:49 -0700 Subject: [PATCH 001/172] panelopen -> store --- .../AdditionalStandAttributes.vue | 9 +++++---- .../model-param-selection-panes/ReportInfo.vue | 11 ++++++----- .../model-param-selection-panes/SiteInfo.vue | 9 +++++---- .../model-param-selection-panes/SpeciesInfo.vue | 11 +++++++---- .../model-param-selection-panes/StandDensity.vue | 9 +++++---- frontend/src/stores/modelParameterStore.ts | 11 +++++++++++ 6 files changed, 39 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue index 42c8c2d73..427945695 100644 --- a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue +++ b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue @@ -1,13 +1,15 @@ diff --git a/frontend/src/components/model-param-selection-panes/SiteInfo.vue b/frontend/src/components/model-param-selection-panes/SiteInfo.vue index dcc1a5e5b..429ac0c7e 100644 --- a/frontend/src/components/model-param-selection-panes/SiteInfo.vue +++ b/frontend/src/components/model-param-selection-panes/SiteInfo.vue @@ -18,222 +18,224 @@ -
- - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - *Ministry Default Curve for this Species - - - - - -
- - -
Site Species Values:
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + *Ministry Default Curve for this Species + + + + + +
+ + +
Site Species Values:
+
+ -
- - -
- - - Clear - Confirm - +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+ + + Clear + Confirm + +
@@ -258,6 +260,8 @@ import { FLOATING, } from '@/constants/constants' +const form = ref() + const modelParameterStore = useModelParameterStore() const { panelOpenStates, @@ -452,7 +456,11 @@ const handleBHA50SiteIndexInput = (event: Event) => { } } -const clear = () => {} +const clear = () => { + if (form.value) { + form.value.reset() + } +} const confirm = () => {} diff --git a/frontend/src/components/model-param-selection-panes/StandDensity.vue b/frontend/src/components/model-param-selection-panes/StandDensity.vue index 4dc670e2f..c5064ee6f 100644 --- a/frontend/src/components/model-param-selection-panes/StandDensity.vue +++ b/frontend/src/components/model-param-selection-panes/StandDensity.vue @@ -18,115 +18,117 @@ -
- - - - - - +
+ + + + + + + + - - - - - - - - - - - - - - - - Applying Default of 50% - - -
- - - Clear - Confirm - +
+ + + + + + + + + + + + + Applying Default of 50% + + +
+ + + Clear + Confirm + +
@@ -140,6 +142,8 @@ import { storeToRefs } from 'pinia' import { minimumDBHLimitsOptions } from '@/constants/options' import { DERIVED_BY, SITE_SPECIES_VALUES } from '@/constants/constants' +const form = ref() + const modelParameterStore = useModelParameterStore() const { panelOpenStates, @@ -237,7 +241,11 @@ const treesPerHectareError = computed(() => { return error === true ? [] : [error] }) -const clear = () => {} +const clear = () => { + if (form.value) { + form.value.reset() + } +} const confirm = () => {} From 7760ad2c3a4b89208d467605481d5db26634fc53 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Wed, 2 Oct 2024 11:36:00 -0700 Subject: [PATCH 010/172] Use Constants Default values --- frontend/src/constants/constants.ts | 25 ++++++++++ frontend/src/stores/modelParameterStore.ts | 56 ++++++++++------------ 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/frontend/src/constants/constants.ts b/frontend/src/constants/constants.ts index be4723502..c46662187 100644 --- a/frontend/src/constants/constants.ts +++ b/frontend/src/constants/constants.ts @@ -39,3 +39,28 @@ export const COMPUTED_VALUES = Object.freeze({ USE: 'Use Computed Values', MODIFY: 'Modify Computed Values', }) + +export const DEFAULT_VALUES = Object.freeze({ + BEC_ZONE: '8', + SITE_SPECIES_VALUES: SITE_SPECIES_VALUES.COMPUTED, + AGE_TYPE: AGE_TYPE.TOTAL, + AGE: 60, + HEIGHT: 17.0, + BHA50_SITE_INDEX: 16.3, + PERCENT_STOCKABLE_AREA: 55, + PERCENT_CROWN_CLOSURE: 0, + MINIMUM_DBH_LIMIT: '7.5 cm+', + LOREY_HEIGHT: 13.45, + WHOLE_STEM_VOLUME: 106.6, + BASAL_AREA_125CM: 17.0482, + WHOLE_STEM_VOLUME_125CM: 97.0, + CLOSE_UTIL_VOLUME: 84.1, + CLOSE_UTIL_NET_DECAY_VOLUME: 78.2, + CLOSE_UTIL_NET_DECAY_WASTE_VOLUME: 75.1, + STARTING_AGE: 0, + FINISHIN_GAGE: 250, + AGE_INCREMENT: 25, + SELECTED_VOLUME_REPORTED: ['Whole Stem'], + PROJECTION_TYPE: 'Volume', + REPORT_TITLE: 'A Sample Report Title', +}) diff --git a/frontend/src/stores/modelParameterStore.ts b/frontend/src/stores/modelParameterStore.ts index 3d233e187..b43354b65 100644 --- a/frontend/src/stores/modelParameterStore.ts +++ b/frontend/src/stores/modelParameterStore.ts @@ -1,11 +1,6 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' -import { - DERIVED_BY, - SITE_SPECIES_VALUES, - AGE_TYPE, - FLOATING, -} from '@/constants/constants' +import { DERIVED_BY, FLOATING, DEFAULT_VALUES } from '@/constants/constants' export const useModelParameterStore = defineStore('modelParameter', () => { // panel open @@ -80,7 +75,7 @@ export const useModelParameterStore = defineStore('modelParameter', () => { group: key, percent: groupMap[key], siteSpecies: key, - minimumDBHLimit: '7.5 cm+', + minimumDBHLimit: DEFAULT_VALUES.MINIMUM_DBH_LIMIT, })) speciesGroups.value.sort((a, b) => b.percent - a.percent) @@ -143,32 +138,33 @@ export const useModelParameterStore = defineStore('modelParameter', () => { speciesGroups.value = speciesGroups.value.map((group) => ({ ...group, - minimumDBHLimit: '7.5 cm+', + minimumDBHLimit: DEFAULT_VALUES.MINIMUM_DBH_LIMIT, })) - becZone.value = '8' - siteSpeciesValues.value = SITE_SPECIES_VALUES.COMPUTED - ageType.value = AGE_TYPE.TOTAL - age.value = 60 - height.value = 17.0 - bha50SiteIndex.value = 16.3 + becZone.value = DEFAULT_VALUES.BEC_ZONE + siteSpeciesValues.value = DEFAULT_VALUES.SITE_SPECIES_VALUES + ageType.value = DEFAULT_VALUES.AGE_TYPE + age.value = DEFAULT_VALUES.AGE + height.value = DEFAULT_VALUES.HEIGHT + bha50SiteIndex.value = DEFAULT_VALUES.BHA50_SITE_INDEX floating.value = FLOATING.SITEINDEX - percentStockableArea.value = 55 - percentCrownClosure.value = 0 - minimumDBHLimit.value = '7.5 cm+' - loreyHeight.value = 13.45 - wholeStemVolume75cm.value = 106.6 - basalArea125cm.value = 17.0482 - wholeStemVolume125cm.value = 97.0 - closeUtilVolume.value = 84.1 - closeUtilNetDecayVolume.value = 78.2 - closeUtilNetDecayWasteVolume.value = 75.1 - startingAge.value = 0 - finishingAge.value = 250 - ageIncrement.value = 25 - selectedVolumeReported.value = ['Whole Stem'] - projectionType.value = 'Volume' - reportTitle.value = 'A Sample Report Title' + percentStockableArea.value = DEFAULT_VALUES.PERCENT_STOCKABLE_AREA + percentCrownClosure.value = DEFAULT_VALUES.PERCENT_CROWN_CLOSURE + minimumDBHLimit.value = DEFAULT_VALUES.MINIMUM_DBH_LIMIT + loreyHeight.value = DEFAULT_VALUES.LOREY_HEIGHT + wholeStemVolume75cm.value = DEFAULT_VALUES.WHOLE_STEM_VOLUME + basalArea125cm.value = DEFAULT_VALUES.BASAL_AREA_125CM + wholeStemVolume125cm.value = DEFAULT_VALUES.WHOLE_STEM_VOLUME_125CM + closeUtilVolume.value = DEFAULT_VALUES.CLOSE_UTIL_VOLUME + closeUtilNetDecayVolume.value = DEFAULT_VALUES.CLOSE_UTIL_NET_DECAY_VOLUME + closeUtilNetDecayWasteVolume.value = + DEFAULT_VALUES.CLOSE_UTIL_NET_DECAY_WASTE_VOLUME + startingAge.value = DEFAULT_VALUES.STARTING_AGE + finishingAge.value = DEFAULT_VALUES.FINISHIN_GAGE + ageIncrement.value = DEFAULT_VALUES.AGE_INCREMENT + selectedVolumeReported.value = DEFAULT_VALUES.SELECTED_VOLUME_REPORTED + projectionType.value = DEFAULT_VALUES.PROJECTION_TYPE + reportTitle.value = DEFAULT_VALUES.REPORT_TITLE } return { From 92c8d996931d8100f9bdecb306c5285f211c8290 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Wed, 2 Oct 2024 12:04:33 -0700 Subject: [PATCH 011/172] Reset Site Information based on Sepecies Info selection and default values --- .../model-param-selection-panes/SiteInfo.vue | 24 ++++++++++++++++--- frontend/src/constants/constants.ts | 3 ++- frontend/src/stores/modelParameterStore.ts | 2 +- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/SiteInfo.vue b/frontend/src/components/model-param-selection-panes/SiteInfo.vue index 429ac0c7e..062b66720 100644 --- a/frontend/src/components/model-param-selection-panes/SiteInfo.vue +++ b/frontend/src/components/model-param-selection-panes/SiteInfo.vue @@ -30,7 +30,6 @@ v-model="becZone" item-title="label" item-value="value" - clearable hide-details="auto" persistent-placeholder placeholder="Select Bec Zone" @@ -258,6 +257,7 @@ import { DERIVED_BY, SITE_SPECIES_VALUES, FLOATING, + DEFAULT_VALUES, } from '@/constants/constants' const form = ref() @@ -359,8 +359,7 @@ const handleDerivedByChange = ( } } -// Update siteIndexCurve based on selectedSiteSpecies -watch(selectedSiteSpecies, (newSiteSpecies) => { +const updateSiteIndexCurve = (newSiteSpecies: string | null) => { if ( newSiteSpecies && siteIndexCurveMap[newSiteSpecies as keyof typeof siteIndexCurveMap] @@ -370,6 +369,10 @@ watch(selectedSiteSpecies, (newSiteSpecies) => { } else { siteIndexCurve.value = null // Clear if no mapping found } +} +// Update siteIndexCurve based on selectedSiteSpecies +watch(selectedSiteSpecies, (newSiteSpecies) => { + updateSiteIndexCurve(newSiteSpecies) }) watch( @@ -460,6 +463,21 @@ const clear = () => { if (form.value) { form.value.reset() } + + selectedSiteSpecies.value = highestPercentSpecies.value + updateSiteIndexCurve(selectedSiteSpecies.value) + + becZone.value = DEFAULT_VALUES.BEC_ZONE + siteSpeciesValues.value = DEFAULT_VALUES.SITE_SPECIES_VALUES + ageType.value = DEFAULT_VALUES.AGE_TYPE + floating.value = DEFAULT_VALUES.FLOATING + + handleDerivedByChange( + derivedBy.value, + selectedSiteSpecies.value, + siteSpeciesValues.value, + floating.value, + ) } const confirm = () => {} diff --git a/frontend/src/constants/constants.ts b/frontend/src/constants/constants.ts index c46662187..363d2720a 100644 --- a/frontend/src/constants/constants.ts +++ b/frontend/src/constants/constants.ts @@ -47,6 +47,7 @@ export const DEFAULT_VALUES = Object.freeze({ AGE: 60, HEIGHT: 17.0, BHA50_SITE_INDEX: 16.3, + FLOATING: FLOATING.SITEINDEX, PERCENT_STOCKABLE_AREA: 55, PERCENT_CROWN_CLOSURE: 0, MINIMUM_DBH_LIMIT: '7.5 cm+', @@ -58,7 +59,7 @@ export const DEFAULT_VALUES = Object.freeze({ CLOSE_UTIL_NET_DECAY_VOLUME: 78.2, CLOSE_UTIL_NET_DECAY_WASTE_VOLUME: 75.1, STARTING_AGE: 0, - FINISHIN_GAGE: 250, + FINISHING_AGE: 250, AGE_INCREMENT: 25, SELECTED_VOLUME_REPORTED: ['Whole Stem'], PROJECTION_TYPE: 'Volume', diff --git a/frontend/src/stores/modelParameterStore.ts b/frontend/src/stores/modelParameterStore.ts index b43354b65..9088055c9 100644 --- a/frontend/src/stores/modelParameterStore.ts +++ b/frontend/src/stores/modelParameterStore.ts @@ -160,7 +160,7 @@ export const useModelParameterStore = defineStore('modelParameter', () => { closeUtilNetDecayWasteVolume.value = DEFAULT_VALUES.CLOSE_UTIL_NET_DECAY_WASTE_VOLUME startingAge.value = DEFAULT_VALUES.STARTING_AGE - finishingAge.value = DEFAULT_VALUES.FINISHIN_GAGE + finishingAge.value = DEFAULT_VALUES.FINISHING_AGE ageIncrement.value = DEFAULT_VALUES.AGE_INCREMENT selectedVolumeReported.value = DEFAULT_VALUES.SELECTED_VOLUME_REPORTED projectionType.value = DEFAULT_VALUES.PROJECTION_TYPE From b2af54a477471378f7b0476de0721a51c1b01c69 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Wed, 2 Oct 2024 12:36:37 -0700 Subject: [PATCH 012/172] Reset Stand Density / add percent stockable area hint --- .../StandDensity.vue | 54 +++++++++++-------- frontend/src/constants/constants.ts | 1 + frontend/src/stores/modelParameterStore.ts | 6 +-- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/StandDensity.vue b/frontend/src/components/model-param-selection-panes/StandDensity.vue index c5064ee6f..fd94eea7e 100644 --- a/frontend/src/components/model-param-selection-panes/StandDensity.vue +++ b/frontend/src/components/model-param-selection-panes/StandDensity.vue @@ -29,14 +29,26 @@ max="100" min="0" step="5" - :rules="[validatePercentStockableArea]" + :rules="[validatePercent]" :error-messages="percentStockableAreaError" - placeholder="Select..." + placeholder="" persistent-placeholder + hide-details="auto" density="compact" dense - > + > + A default will be computed when the model is run. + Applying Default of 50%() @@ -191,17 +209,7 @@ watch( { immediate: true }, ) -const validatePercentStockableArea = (value: any) => { - if (value === null || value === '') { - return 'Percent Stockable Area is required' - } - if (value < 0 || value > 100) { - return 'Please enter a value between 0 and 100' - } - return true -} - -const validatePercentCrownClosure = (value: any) => { +const validatePercent = (value: any) => { if (value === null || value === '') { return true } @@ -222,12 +230,12 @@ const validateMinimum = (value: any) => { } const percentStockableAreaError = computed(() => { - const error = validatePercentStockableArea(percentStockableArea.value) + const error = validatePercent(percentStockableArea.value) return error === true ? [] : [error] }) const percentCrownClosureError = computed(() => { - const error = validatePercentCrownClosure(percentCrownClosure.value) + const error = validatePercent(percentCrownClosure.value) return error === true ? [] : [error] }) @@ -245,6 +253,8 @@ const clear = () => { if (form.value) { form.value.reset() } + minimumDBHLimit.value = DEFAULT_VALUES.MINIMUM_DBH_LIMIT + percentCrownClosure.value = DEFAULT_VALUES.PERCENT_CROWN_CLOSURE } const confirm = () => {} diff --git a/frontend/src/constants/constants.ts b/frontend/src/constants/constants.ts index 363d2720a..b93bed117 100644 --- a/frontend/src/constants/constants.ts +++ b/frontend/src/constants/constants.ts @@ -41,6 +41,7 @@ export const COMPUTED_VALUES = Object.freeze({ }) export const DEFAULT_VALUES = Object.freeze({ + DERIVED_BY: DERIVED_BY.VOLUME, BEC_ZONE: '8', SITE_SPECIES_VALUES: SITE_SPECIES_VALUES.COMPUTED, AGE_TYPE: AGE_TYPE.TOTAL, diff --git a/frontend/src/stores/modelParameterStore.ts b/frontend/src/stores/modelParameterStore.ts index 9088055c9..38fadb1b0 100644 --- a/frontend/src/stores/modelParameterStore.ts +++ b/frontend/src/stores/modelParameterStore.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' -import { DERIVED_BY, FLOATING, DEFAULT_VALUES } from '@/constants/constants' +import { FLOATING, DEFAULT_VALUES } from '@/constants/constants' export const useModelParameterStore = defineStore('modelParameter', () => { // panel open @@ -98,7 +98,7 @@ export const useModelParameterStore = defineStore('modelParameter', () => { const floating = ref(null) // stand density - const percentStockableArea = ref(0) + const percentStockableArea = ref(null) const basalArea = ref(null) const treesPerHectare = ref(null) const minimumDBHLimit = ref(null) @@ -124,7 +124,7 @@ export const useModelParameterStore = defineStore('modelParameter', () => { // set default values const setDefaultValues = () => { - derivedBy.value = DERIVED_BY.VOLUME + derivedBy.value = DEFAULT_VALUES.DERIVED_BY speciesList.value = [ { species: 'PL', percent: 30 }, { species: 'AC', percent: 30 }, From f0099a1d260211ed8d4225ee985472432c7674c3 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Wed, 2 Oct 2024 13:16:01 -0700 Subject: [PATCH 013/172] Reset Additional Stand Attributes and Report Info --- .../AdditionalStandAttributes.vue | 5 ++++ .../ReportInfo.vue | 24 +++++++++++++------ frontend/src/constants/constants.ts | 11 ++++++++- frontend/src/constants/options.ts | 12 ++++++---- frontend/src/stores/modelParameterStore.ts | 9 ++++--- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue index 9b1069ed9..d8c9f9e6c 100644 --- a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue +++ b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue @@ -204,6 +204,7 @@ import { DERIVED_BY, SITE_SPECIES_VALUES, COMPUTED_VALUES, + DEFAULT_VALUES, } from '@/constants/constants' const form = ref() @@ -316,6 +317,10 @@ const clear = () => { if (form.value) { form.value.reset() } + + computedValues.value = DEFAULT_VALUES.COMPUTED_VALUES + + // TODO - set text-field with calculated values based on seleciton in the previous screen } const confirm = () => {} diff --git a/frontend/src/components/model-param-selection-panes/ReportInfo.vue b/frontend/src/components/model-param-selection-panes/ReportInfo.vue index e1bf69aa2..44f4aada0 100644 --- a/frontend/src/components/model-param-selection-panes/ReportInfo.vue +++ b/frontend/src/components/model-param-selection-panes/ReportInfo.vue @@ -86,7 +86,7 @@ :style="{ 'max-width': index < 4 ? '20%' : 'auto' }" > () @@ -219,7 +219,8 @@ const { startingAge, finishingAge, ageIncrement, - selectedVolumeReported, + volumeReported, + includeInReport, projectionType, reportTitle, } = storeToRefs(modelParameterStore) @@ -264,9 +265,18 @@ const ageIncrementError = computed(() => { }) const clear = () => { - if (form.value) { - form.value.reset() - } + startingAge.value = null + finishingAge.value = null + ageIncrement.value = null + volumeReported.value = [] + includeInReport.value = [] + reportTitle.value = null + + projectionType.value = DEFAULT_VALUES.PROJECTION_TYPE + speciesGroups.value = speciesGroups.value.map((group) => ({ + ...group, + minimumDBHLimit: MINIMUM_DBH_LIMITS.CM4_0, + })) } const confirm = () => {} diff --git a/frontend/src/constants/constants.ts b/frontend/src/constants/constants.ts index b93bed117..a7113bc84 100644 --- a/frontend/src/constants/constants.ts +++ b/frontend/src/constants/constants.ts @@ -40,6 +40,14 @@ export const COMPUTED_VALUES = Object.freeze({ MODIFY: 'Modify Computed Values', }) +export const MINIMUM_DBH_LIMITS = Object.freeze({ + CM4_0: '4.0 cm+', + CM7_5: '7.5 cm+', + CM12_5: '12.5 cm+', + CM17_5: '17.5 cm+', + CM22_5: '22.5 cm+', +}) + export const DEFAULT_VALUES = Object.freeze({ DERIVED_BY: DERIVED_BY.VOLUME, BEC_ZONE: '8', @@ -51,7 +59,8 @@ export const DEFAULT_VALUES = Object.freeze({ FLOATING: FLOATING.SITEINDEX, PERCENT_STOCKABLE_AREA: 55, PERCENT_CROWN_CLOSURE: 0, - MINIMUM_DBH_LIMIT: '7.5 cm+', + MINIMUM_DBH_LIMIT: MINIMUM_DBH_LIMITS.CM7_5, + COMPUTED_VALUES: COMPUTED_VALUES.USE, LOREY_HEIGHT: 13.45, WHOLE_STEM_VOLUME: 106.6, BASAL_AREA_125CM: 17.0482, diff --git a/frontend/src/constants/options.ts b/frontend/src/constants/options.ts index 9c200e45d..f6e12f801 100644 --- a/frontend/src/constants/options.ts +++ b/frontend/src/constants/options.ts @@ -1,3 +1,5 @@ +import { MINIMUM_DBH_LIMITS } from '@/constants/constants' + export const derivedByOptions = [ { label: 'Volume', value: 'Volume' }, { label: 'Basal Area', value: 'Basal Area' }, @@ -116,11 +118,11 @@ export const floatingOptions = [ ] export const minimumDBHLimitsOptions = [ - { label: '4.0 cm+', value: '4.0 cm+' }, - { label: '7.5 cm+', value: '7.5 cm+' }, - { label: '12.5 cm+', value: '12.5 cm+' }, - { label: '17.5 cm+', value: '17.5 cm+' }, - { label: '22.5 cm+', value: '22.5 cm+' }, + { label: '4.0 cm+', value: MINIMUM_DBH_LIMITS.CM4_0 }, + { label: '7.5 cm+', value: MINIMUM_DBH_LIMITS.CM7_5 }, + { label: '12.5 cm+', value: MINIMUM_DBH_LIMITS.CM12_5 }, + { label: '17.5 cm+', value: MINIMUM_DBH_LIMITS.CM17_5 }, + { label: '22.5 cm+', value: MINIMUM_DBH_LIMITS.CM22_5 }, ] export const additionalStandAttributesOptions = [ diff --git a/frontend/src/stores/modelParameterStore.ts b/frontend/src/stores/modelParameterStore.ts index 38fadb1b0..e1657dc03 100644 --- a/frontend/src/stores/modelParameterStore.ts +++ b/frontend/src/stores/modelParameterStore.ts @@ -118,7 +118,8 @@ export const useModelParameterStore = defineStore('modelParameter', () => { const startingAge = ref(null) const finishingAge = ref(null) const ageIncrement = ref(null) - const selectedVolumeReported = ref([]) + const volumeReported = ref([]) + const includeInReport = ref([]) const projectionType = ref(null) const reportTitle = ref(null) @@ -151,6 +152,7 @@ export const useModelParameterStore = defineStore('modelParameter', () => { percentStockableArea.value = DEFAULT_VALUES.PERCENT_STOCKABLE_AREA percentCrownClosure.value = DEFAULT_VALUES.PERCENT_CROWN_CLOSURE minimumDBHLimit.value = DEFAULT_VALUES.MINIMUM_DBH_LIMIT + computedValues.value = DEFAULT_VALUES.COMPUTED_VALUES loreyHeight.value = DEFAULT_VALUES.LOREY_HEIGHT wholeStemVolume75cm.value = DEFAULT_VALUES.WHOLE_STEM_VOLUME basalArea125cm.value = DEFAULT_VALUES.BASAL_AREA_125CM @@ -162,7 +164,7 @@ export const useModelParameterStore = defineStore('modelParameter', () => { startingAge.value = DEFAULT_VALUES.STARTING_AGE finishingAge.value = DEFAULT_VALUES.FINISHING_AGE ageIncrement.value = DEFAULT_VALUES.AGE_INCREMENT - selectedVolumeReported.value = DEFAULT_VALUES.SELECTED_VOLUME_REPORTED + volumeReported.value = DEFAULT_VALUES.SELECTED_VOLUME_REPORTED projectionType.value = DEFAULT_VALUES.PROJECTION_TYPE reportTitle.value = DEFAULT_VALUES.REPORT_TITLE } @@ -210,7 +212,8 @@ export const useModelParameterStore = defineStore('modelParameter', () => { startingAge, finishingAge, ageIncrement, - selectedVolumeReported, + volumeReported, + includeInReport, projectionType, reportTitle, // set default values From d74773968a7fd7e6fa4bf168a70fe5d1deabd0c8 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Thu, 3 Oct 2024 12:33:15 -0700 Subject: [PATCH 014/172] Add AppMessageDialog / add validations in species information on confirm --- frontend/src/App.vue | 2 + .../components/common/AppMessageDialog.vue | 52 +++++++++ .../SpeciesInfo.vue | 110 +++++++++++++----- .../src/stores/common/messageDialogStore.ts | 56 +++++++++ frontend/src/styles/style.scss | 5 +- 5 files changed, 195 insertions(+), 30 deletions(-) create mode 100644 frontend/src/components/common/AppMessageDialog.vue create mode 100644 frontend/src/stores/common/messageDialogStore.ts diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 72925f98f..50c287f84 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -3,6 +3,7 @@ + @@ -16,6 +17,7 @@ + + diff --git a/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue b/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue index 3d373960a..91009860c 100644 --- a/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue +++ b/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue @@ -199,17 +199,7 @@ Clear - - Confirm - + Confirm @@ -220,12 +210,17 @@ diff --git a/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue b/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue index 91009860c..67df46c86 100644 --- a/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue +++ b/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue @@ -357,7 +357,7 @@ const validateTotalSpeciesPercent = (): boolean => { return true } -const validateDerivedBy = (): boolean => { +const validateRequiredFields = (): boolean => { if (!derivedBy.value) { messageDialogStore.openDialog( 'Missing Information', @@ -372,9 +372,9 @@ const validateDerivedBy = (): boolean => { const confirm = () => { const isDuplicateValid = validateDuplicateSpecies() const isTotalPercentValid = validateTotalSpeciesPercent() - const isDerivedByValid = validateDerivedBy() + const isRequiredFieldsValid = validateRequiredFields() - if (isDuplicateValid && isTotalPercentValid && isDerivedByValid) { + if (isDuplicateValid && isTotalPercentValid && isRequiredFieldsValid) { form.value?.validate() } } diff --git a/frontend/src/components/model-param-selection-panes/StandDensity.vue b/frontend/src/components/model-param-selection-panes/StandDensity.vue index fd94eea7e..68359bb36 100644 --- a/frontend/src/components/model-param-selection-panes/StandDensity.vue +++ b/frontend/src/components/model-param-selection-panes/StandDensity.vue @@ -38,13 +38,7 @@ dense > A default will be computed when the model is run. @@ -124,13 +118,7 @@ :disabled="isPercentCrownClosureDisabled" > Applying Default of 50% @@ -151,6 +139,7 @@ diff --git a/frontend/src/components/model-param-selection-panes/SiteInfo.vue b/frontend/src/components/model-param-selection-panes/SiteInfo.vue index b85530e7d..f3e1ff7e9 100644 --- a/frontend/src/components/model-param-selection-panes/SiteInfo.vue +++ b/frontend/src/components/model-param-selection-panes/SiteInfo.vue @@ -153,7 +153,6 @@ - agePlaceholder:{{ agePlaceholder }} { isAgeTypeDisabled.value = false isAgeDisabled.value = false @@ -346,9 +348,6 @@ const setFloatingState = (newFloating: string | null) => { } } -const agePlaceholder = ref('') -const heightPlaceholder = ref('') - const handleSiteSpeciesValuesState = ( newSiteSpeciesValues: string | null, newFloating: string | null, diff --git a/frontend/src/components/model-param-selection-panes/StandDensity.vue b/frontend/src/components/model-param-selection-panes/StandDensity.vue index 68359bb36..cb8be140e 100644 --- a/frontend/src/components/model-param-selection-panes/StandDensity.vue +++ b/frontend/src/components/model-param-selection-panes/StandDensity.vue @@ -23,7 +23,7 @@ + + Density Measurements cannot be supplied without an + Age. + + > + @@ -102,7 +109,7 @@ { const isBasalAreaEnabled = newDerivedBy === DERIVED_BY.BASAL_AREA && @@ -187,13 +199,22 @@ const updateBasalAreaAndTreesState = ( isBasalAreaDisabled.value = !isBasalAreaEnabled isTreesPerHectareDisabled.value = !isBasalAreaEnabled + + // handle by Age change + if (Util.isEmptyOrZero(newAge)) { + isBasalAreaDisabled.value = true + isTreesPerHectareDisabled.value = true + } + + basalAreaPlaceholder.value = isBasalAreaDisabled.value ? 'N/A' : '' + tphPlaceholder.value = isTreesPerHectareDisabled.value ? 'N/A' : '' } watch( - [derivedBy, siteSpeciesValues], - ([newDerivedBy, newSiteSpeciesValues]) => { + [derivedBy, siteSpeciesValues, age], + ([newDerivedBy, newSiteSpeciesValues, newAge]) => { updatePercentCrownClosureState(newDerivedBy, newSiteSpeciesValues) - updateBasalAreaAndTreesState(newDerivedBy, newSiteSpeciesValues) + updateBasalAreaAndTreesState(newDerivedBy, newSiteSpeciesValues, newAge) }, { immediate: true }, ) From 701551e170a2289e1600636ea08cfa52cb1d9a59 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Thu, 3 Oct 2024 16:01:07 -0700 Subject: [PATCH 018/172] additional crown closure hint conditional logic / handling for placeholders of tph, crown closure, basal area --- .../StandDensity.vue | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/StandDensity.vue b/frontend/src/components/model-param-selection-panes/StandDensity.vue index cb8be140e..ef076e419 100644 --- a/frontend/src/components/model-param-selection-panes/StandDensity.vue +++ b/frontend/src/components/model-param-selection-panes/StandDensity.vue @@ -96,10 +96,9 @@ v-model="minimumDBHLimit" item-title="label" item-value="value" - clearable hide-details persistent-placeholder - placeholder="Select..." + placeholder="" density="compact" dense disabled @@ -118,14 +117,17 @@ :rules="[validatePercent]" :error-messages="percentCrownClosureError" persistent-placeholder - placeholder="" + :placeholder="crownClosurePlaceholder" hide-details="auto" density="compact" dense :disabled="isPercentCrownClosureDisabled" > Applying Default of 50% @@ -177,6 +179,7 @@ const isTreesPerHectareDisabled = ref(false) const basalAreaPlaceholder = ref('') const tphPlaceholder = ref('') +const crownClosurePlaceholder = ref('') const updatePercentCrownClosureState = ( newDerivedBy: string | null, @@ -204,10 +207,29 @@ const updateBasalAreaAndTreesState = ( if (Util.isEmptyOrZero(newAge)) { isBasalAreaDisabled.value = true isTreesPerHectareDisabled.value = true + isPercentCrownClosureDisabled.value = true + } + + if (isBasalAreaDisabled.value) { + basalAreaPlaceholder.value = 'N/A' + basalArea.value = null + } else { + basalAreaPlaceholder.value = '' } - basalAreaPlaceholder.value = isBasalAreaDisabled.value ? 'N/A' : '' - tphPlaceholder.value = isTreesPerHectareDisabled.value ? 'N/A' : '' + if (isTreesPerHectareDisabled.value) { + tphPlaceholder.value = 'N/A' + treesPerHectare.value = null + } else { + tphPlaceholder.value = '' + } + + if (isPercentCrownClosureDisabled.value) { + crownClosurePlaceholder.value = 'N/A' + percentCrownClosure.value = null + } else { + crownClosurePlaceholder.value = '' + } } watch( From c3de4f61182a0fa5fd68425174d6a043444f0315 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Thu, 3 Oct 2024 16:25:09 -0700 Subject: [PATCH 019/172] Add Current Diameter field to Stand Density --- .../StandDensity.vue | 20 +++++++++++++++++++ frontend/src/constants/constants.ts | 3 ++- frontend/src/stores/modelParameterStore.ts | 5 ++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/StandDensity.vue b/frontend/src/components/model-param-selection-panes/StandDensity.vue index ef076e419..5ad9f2ede 100644 --- a/frontend/src/components/model-param-selection-panes/StandDensity.vue +++ b/frontend/src/components/model-param-selection-panes/StandDensity.vue @@ -132,6 +132,25 @@ >Applying Default of 50% + + + + @@ -170,6 +189,7 @@ const { basalArea, treesPerHectare, minimumDBHLimit, + currentDiameter, percentCrownClosure, } = storeToRefs(modelParameterStore) diff --git a/frontend/src/constants/constants.ts b/frontend/src/constants/constants.ts index a7113bc84..4be20c5ac 100644 --- a/frontend/src/constants/constants.ts +++ b/frontend/src/constants/constants.ts @@ -58,8 +58,9 @@ export const DEFAULT_VALUES = Object.freeze({ BHA50_SITE_INDEX: 16.3, FLOATING: FLOATING.SITEINDEX, PERCENT_STOCKABLE_AREA: 55, - PERCENT_CROWN_CLOSURE: 0, MINIMUM_DBH_LIMIT: MINIMUM_DBH_LIMITS.CM7_5, + PERCENT_CROWN_CLOSURE: 0, + CURRENT_DIAMETER: 11.3, COMPUTED_VALUES: COMPUTED_VALUES.USE, LOREY_HEIGHT: 13.45, WHOLE_STEM_VOLUME: 106.6, diff --git a/frontend/src/stores/modelParameterStore.ts b/frontend/src/stores/modelParameterStore.ts index e1657dc03..c776f28bb 100644 --- a/frontend/src/stores/modelParameterStore.ts +++ b/frontend/src/stores/modelParameterStore.ts @@ -103,6 +103,7 @@ export const useModelParameterStore = defineStore('modelParameter', () => { const treesPerHectare = ref(null) const minimumDBHLimit = ref(null) const percentCrownClosure = ref(null) + const currentDiameter = ref(null) // additional stand attributes const computedValues = ref(null) @@ -150,8 +151,9 @@ export const useModelParameterStore = defineStore('modelParameter', () => { bha50SiteIndex.value = DEFAULT_VALUES.BHA50_SITE_INDEX floating.value = FLOATING.SITEINDEX percentStockableArea.value = DEFAULT_VALUES.PERCENT_STOCKABLE_AREA - percentCrownClosure.value = DEFAULT_VALUES.PERCENT_CROWN_CLOSURE minimumDBHLimit.value = DEFAULT_VALUES.MINIMUM_DBH_LIMIT + currentDiameter.value = DEFAULT_VALUES.CURRENT_DIAMETER + percentCrownClosure.value = DEFAULT_VALUES.PERCENT_CROWN_CLOSURE computedValues.value = DEFAULT_VALUES.COMPUTED_VALUES loreyHeight.value = DEFAULT_VALUES.LOREY_HEIGHT wholeStemVolume75cm.value = DEFAULT_VALUES.WHOLE_STEM_VOLUME @@ -199,6 +201,7 @@ export const useModelParameterStore = defineStore('modelParameter', () => { treesPerHectare, minimumDBHLimit, percentCrownClosure, + currentDiameter, // additional stand attributes computedValues, loreyHeight, From 5deb75de39755fdb61a222294bce45e52e1cf558 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Fri, 4 Oct 2024 10:23:09 -0700 Subject: [PATCH 020/172] Fixed height of Age/Height/Site Index for horizontal alignment with 'Float' / Added default Age and Height values temporary --- .../AdditionalStandAttributes.vue | 2 +- .../model-param-selection-panes/SiteInfo.vue | 27 ++++++++++++------- .../StandDensity.vue | 6 ++--- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue index ce00ecedd..29ad50778 100644 --- a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue +++ b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue @@ -270,7 +270,7 @@ const updateFieldDisabledStates = (newComputedValues: string | null) => { } const updateFieldValueStates = (newAge: number | null) => { - // TO-DO - Make sure that all fields are changed to 'N/A' by Age. + // TODO - Make sure that all fields are changed to 'N/A' by Age. if (Util.isEmptyOrZero(newAge)) { loreyHeight.value = null wholeStemVolume75cm.value = null diff --git a/frontend/src/components/model-param-selection-panes/SiteInfo.vue b/frontend/src/components/model-param-selection-panes/SiteInfo.vue index f3e1ff7e9..21a0bc068 100644 --- a/frontend/src/components/model-param-selection-panes/SiteInfo.vue +++ b/frontend/src/components/model-param-selection-panes/SiteInfo.vue @@ -135,7 +135,7 @@ - + - + @@ -188,7 +188,7 @@ step="0.1" persistent-placeholder :placeholder="heightPlaceholder" - hide-details="auto" + hide-details density="compact" dense :disabled="isHeightDisabled" @@ -201,7 +201,7 @@ > - + @@ -214,7 +214,7 @@ step="0.1" persistent-placeholder placeholder="" - hide-details="auto" + hide-details density="compact" dense @input="handleBHA50SiteIndexInput($event)" @@ -233,6 +233,7 @@ @@ -356,6 +357,11 @@ const handleSiteSpeciesValuesState = ( isFloatingDisabled.value = false setFloatingState(newFloating) + // TODO - set values based on species, beczone, agetype + // if age or height float is selected, age, height or bha 50 site index should also be factored into the calculation for these values + age.value = 60 + height.value = 17.0 + agePlaceholder.value = '' heightPlaceholder.value = '' } else if (newSiteSpeciesValues === SITE_SPECIES_VALUES.SUPPLIED) { @@ -392,6 +398,7 @@ const handleDerivedByChange = ( } } +// Update siteIndexCurve based on selectedSiteSpecies const updateSiteIndexCurve = (newSiteSpecies: string | null) => { if ( newSiteSpecies && @@ -403,7 +410,7 @@ const updateSiteIndexCurve = (newSiteSpecies: string | null) => { siteIndexCurve.value = null // Clear if no mapping found } } -// Update siteIndexCurve based on selectedSiteSpecies + watch(selectedSiteSpecies, (newSiteSpecies) => { updateSiteIndexCurve(newSiteSpecies) }) diff --git a/frontend/src/components/model-param-selection-panes/StandDensity.vue b/frontend/src/components/model-param-selection-panes/StandDensity.vue index 5ad9f2ede..843e7307b 100644 --- a/frontend/src/components/model-param-selection-panes/StandDensity.vue +++ b/frontend/src/components/model-param-selection-panes/StandDensity.vue @@ -216,12 +216,10 @@ const updateBasalAreaAndTreesState = ( newSiteSpeciesValues: string | null, newAge: number | null, ) => { - const isBasalAreaEnabled = + isBasalAreaDisabled.value = isTreesPerHectareDisabled.value = !( newDerivedBy === DERIVED_BY.BASAL_AREA && newSiteSpeciesValues === SITE_SPECIES_VALUES.COMPUTED - - isBasalAreaDisabled.value = !isBasalAreaEnabled - isTreesPerHectareDisabled.value = !isBasalAreaEnabled + ) // handle by Age change if (Util.isEmptyOrZero(newAge)) { From ce8de04b8eb2d540db7a2d84ebd0eee605273d74 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Fri, 4 Oct 2024 13:14:39 -0700 Subject: [PATCH 021/172] Set basal area and tph default values / refactoring watch functions in stand density / add validation for confirm action in additional stand attributes --- .../components/common/AppMessageDialog.vue | 1 + .../AdditionalStandAttributes.vue | 178 ++++++++++++++++-- .../StandDensity.vue | 62 +++--- frontend/src/utils/util.ts | 15 +- 4 files changed, 206 insertions(+), 50 deletions(-) diff --git a/frontend/src/components/common/AppMessageDialog.vue b/frontend/src/components/common/AppMessageDialog.vue index a3a14a3ae..341d4b877 100644 --- a/frontend/src/components/common/AppMessageDialog.vue +++ b/frontend/src/components/common/AppMessageDialog.vue @@ -13,6 +13,7 @@ font-size: 14px; padding-left: 35px !important; padding-right: 35px !important; + white-space: pre-line; " > {{ message }} diff --git a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue index 29ad50778..628bf4c28 100644 --- a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue +++ b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue @@ -199,6 +199,7 @@ import { ref, computed, watch } from 'vue' import { Util } from '@/utils/util' import { useModelParameterStore } from '@/stores/modelParameterStore' +import { useMessageDialogStore } from '@/stores/common/messageDialogStore' import { storeToRefs } from 'pinia' import { additionalStandAttributesOptions } from '@/constants/options' import { @@ -211,6 +212,8 @@ import { const form = ref() const modelParameterStore = useModelParameterStore() +const messageDialogStore = useMessageDialogStore() + const { panelOpenStates, derivedBy, @@ -243,6 +246,14 @@ const closeUtilVolumePlaceholder = ref('') const closeUtilNetDecayVolumePlaceholder = ref('') const closeUtilNetDecayWasteVolumePlaceholder = ref('') +const loreyHeightOriginal = ref(null) +const wholeStemVolume75cmOriginal = ref(null) +const basalArea125cmOriginal = ref(null) +const wholeStemVolume125cmOriginal = ref(null) +const closeUtilVolumeOriginal = ref(null) +const closeUtilNetDecayVolumeOriginal = ref(null) +const closeUtilNetDecayWasteVolumeOriginal = ref(null) + const updateComputedValuesState = ( newDerivedBy: string | null, newSiteSpeciesValues: string | null, @@ -269,6 +280,27 @@ const updateFieldDisabledStates = (newComputedValues: string | null) => { isCloseUtilNetDecayWasteVolumeDisabled.value = isDisabled } +const updateFieldPlaceholderStates = (newAge: number | null) => { + // TODO - Make sure that all fields are changed to 'N/A' by Age. + if (Util.isEmptyOrZero(newAge)) { + loreyHeightPlaceholder.value = 'N/A' + wholeStemVolume75cmPlaceholder.value = 'N/A' + basalArea125cmPlaceholder.value = 'N/A' + wholeStemVolume125cmPlaceholder.value = 'N/A' + closeUtilVolumePlaceholder.value = 'N/A' + closeUtilNetDecayVolumePlaceholder.value = 'N/A' + closeUtilNetDecayWasteVolumePlaceholder.value = 'N/A' + } else { + loreyHeightPlaceholder.value = '' + wholeStemVolume75cmPlaceholder.value = '' + basalArea125cmPlaceholder.value = '' + wholeStemVolume125cmPlaceholder.value = '' + closeUtilVolumePlaceholder.value = '' + closeUtilNetDecayVolumePlaceholder.value = '' + closeUtilNetDecayWasteVolumePlaceholder.value = '' + } +} + const updateFieldValueStates = (newAge: number | null) => { // TODO - Make sure that all fields are changed to 'N/A' by Age. if (Util.isEmptyOrZero(newAge)) { @@ -279,23 +311,44 @@ const updateFieldValueStates = (newAge: number | null) => { closeUtilVolume.value = null closeUtilNetDecayVolume.value = null closeUtilNetDecayWasteVolume.value = null + } +} - loreyHeightPlaceholder.value = 'N/A' - wholeStemVolume75cmPlaceholder.value = 'N/A' - basalArea125cmPlaceholder.value = 'N/A' - wholeStemVolume125cmPlaceholder.value = 'N/A' - closeUtilVolumePlaceholder.value = 'N/A' - closeUtilNetDecayVolumePlaceholder.value = 'N/A' - closeUtilNetDecayWasteVolumePlaceholder.value = 'N/A' +const updateOriginalValues = () => { + if (computedValues.value === COMPUTED_VALUES.MODIFY) { + loreyHeightOriginal.value = loreyHeight.value + wholeStemVolume75cmOriginal.value = wholeStemVolume75cm.value + basalArea125cmOriginal.value = basalArea125cm.value + wholeStemVolume125cmOriginal.value = wholeStemVolume125cm.value + closeUtilVolumeOriginal.value = closeUtilVolume.value + closeUtilNetDecayVolumeOriginal.value = closeUtilNetDecayVolume.value + closeUtilNetDecayWasteVolumeOriginal.value = + closeUtilNetDecayWasteVolume.value } } +const handleStateChanges = ( + newDerivedBy: string | null, + newSiteSpeciesValues: string | null, + newComputedValues: string | null, + newAge: number | null, +) => { + updateComputedValuesState(newDerivedBy, newSiteSpeciesValues) + updateFieldDisabledStates(newComputedValues) + updateFieldValueStates(newAge) + updateFieldPlaceholderStates(newAge) + updateOriginalValues() +} + watch( [derivedBy, siteSpeciesValues, computedValues, age], ([newDerivedBy, newSiteSpeciesValues, newComputedValues, newAge]) => { - updateComputedValuesState(newDerivedBy, newSiteSpeciesValues) - updateFieldDisabledStates(newComputedValues) - updateFieldValueStates(newAge) + handleStateChanges( + newDerivedBy, + newSiteSpeciesValues, + newComputedValues, + newAge, + ) }, { immediate: true }, ) @@ -354,6 +407,109 @@ const clear = () => { // TODO - set all text-fields on this screen with calculated values based on seleciton in the previous screen } -const confirm = () => {} + +const validateFieldPresenceAndValue = ( + fieldValue: number | null, + fieldName: string, +): boolean => { + if (Util.isBlank(fieldValue)) { + messageDialogStore.openDialog( + 'Invalid Input!', + `${fieldName}: is not a valid number`, + { width: 400 }, + ) + return false + } + + if (Util.isZeroValue(fieldValue)) { + messageDialogStore.openDialog( + 'Invalid Input!', + `${fieldName}: must be greater than 0.0`, + { width: 450 }, + ) + return false + } + + return true +} + +const validateAllFields = (): boolean => { + const fieldsToValidate = [ + { value: loreyHeight.value, name: 'Lorey Height - 7.5cm+' }, + { value: wholeStemVolume75cm.value, name: 'Whole Stem Volume - 7.5cm+' }, + { value: basalArea125cm.value, name: 'Basal Area - 12.5cm+' }, + { value: wholeStemVolume125cm.value, name: 'Whole Stem Volume - 12.5cm+' }, + { + value: closeUtilVolume.value, + name: 'Close Utilization Volume - 12.5cm+', + }, + { + value: closeUtilNetDecayVolume.value, + name: 'Close Utilization Net Decay Volume - 12.5cm+', + }, + { + value: closeUtilNetDecayWasteVolume.value, + name: 'Close Utilization Net Decay Waste Volume - 12.5cm+', + }, + ] + + for (const field of fieldsToValidate) { + if (!validateFieldPresenceAndValue(field.value, field.name)) { + return false + } + } + + return true +} + +const validateComputedValuesModification = (): boolean => { + if (computedValues.value === COMPUTED_VALUES.MODIFY) { + const fields = [ + { original: loreyHeightOriginal, current: loreyHeight.value }, + { + original: wholeStemVolume75cmOriginal, + current: wholeStemVolume75cm.value, + }, + { original: basalArea125cmOriginal, current: basalArea125cm.value }, + { + original: wholeStemVolume125cmOriginal, + current: wholeStemVolume125cm.value, + }, + { original: closeUtilVolumeOriginal, current: closeUtilVolume.value }, + { + original: closeUtilNetDecayVolumeOriginal, + current: closeUtilNetDecayVolume.value, + }, + { + original: closeUtilNetDecayWasteVolumeOriginal, + current: closeUtilNetDecayWasteVolume.value, + }, + ] + + // Returns true if any element satisfies the condition, otherwise returns false. + const hasModification = fields.some((field) => { + return field.original.value !== field.current + }) + + if (!hasModification) { + messageDialogStore.openDialog( + 'No Modifications!', + "At least one of the starting values must have been modified from the original computed values.\n\n Please modify at least one starting value or switch to 'Computed Values' mode.", + { width: 400 }, + ) + return false + } + } + return true +} + +const confirm = () => { + const isAllFieldsValid = validateAllFields() + const isModificationValid = validateComputedValuesModification() + + if (isAllFieldsValid && isModificationValid) { + form.value?.validate() + } +} diff --git a/frontend/src/components/model-param-selection-panes/StandDensity.vue b/frontend/src/components/model-param-selection-panes/StandDensity.vue index 843e7307b..a2bcd4862 100644 --- a/frontend/src/components/model-param-selection-panes/StandDensity.vue +++ b/frontend/src/components/model-param-selection-panes/StandDensity.vue @@ -201,46 +201,37 @@ const basalAreaPlaceholder = ref('') const tphPlaceholder = ref('') const crownClosurePlaceholder = ref('') -const updatePercentCrownClosureState = ( - newDerivedBy: string | null, - newSiteSpeciesValues: string | null, -) => { - isPercentCrownClosureDisabled.value = !( - newDerivedBy === DERIVED_BY.VOLUME && - newSiteSpeciesValues === SITE_SPECIES_VALUES.COMPUTED - ) -} - -const updateBasalAreaAndTreesState = ( - newDerivedBy: string | null, - newSiteSpeciesValues: string | null, - newAge: number | null, -) => { - isBasalAreaDisabled.value = isTreesPerHectareDisabled.value = !( - newDerivedBy === DERIVED_BY.BASAL_AREA && - newSiteSpeciesValues === SITE_SPECIES_VALUES.COMPUTED - ) - - // handle by Age change - if (Util.isEmptyOrZero(newAge)) { - isBasalAreaDisabled.value = true - isTreesPerHectareDisabled.value = true - isPercentCrownClosureDisabled.value = true - } +const updateBasalAreaState = (isEnabled: boolean, isAgeZero: boolean) => { + isBasalAreaDisabled.value = !isEnabled || isAgeZero if (isBasalAreaDisabled.value) { basalAreaPlaceholder.value = 'N/A' basalArea.value = null } else { basalAreaPlaceholder.value = '' + basalArea.value = 10.0 } +} + +const updateTreesPerHectareState = (isEnabled: boolean, isAgeZero: boolean) => { + isTreesPerHectareDisabled.value = !isEnabled || isAgeZero if (isTreesPerHectareDisabled.value) { tphPlaceholder.value = 'N/A' treesPerHectare.value = null } else { tphPlaceholder.value = '' + treesPerHectare.value = 1000.0 } +} + +const updateCrownClosureState = ( + isVolume: boolean, + isComputed: boolean, + isAgeEmptyOrZero: boolean, +) => { + isPercentCrownClosureDisabled.value = + !(isVolume && isComputed) || isAgeEmptyOrZero if (isPercentCrownClosureDisabled.value) { crownClosurePlaceholder.value = 'N/A' @@ -250,11 +241,26 @@ const updateBasalAreaAndTreesState = ( } } +const updateStates = ( + newDerivedBy: string | null, + newSiteSpeciesValues: string | null, + newAge: number | null, +) => { + const isVolume = newDerivedBy === DERIVED_BY.VOLUME + const isBasalArea = newDerivedBy === DERIVED_BY.BASAL_AREA + const isComputed = newSiteSpeciesValues === SITE_SPECIES_VALUES.COMPUTED + const isAgeEmptyOrZero = Util.isEmptyOrZero(newAge) + + // Update states using individual functions + updateBasalAreaState(isBasalArea && isComputed, isAgeEmptyOrZero) + updateTreesPerHectareState(isBasalArea && isComputed, isAgeEmptyOrZero) + updateCrownClosureState(isVolume, isComputed, isAgeEmptyOrZero) +} + watch( [derivedBy, siteSpeciesValues, age], ([newDerivedBy, newSiteSpeciesValues, newAge]) => { - updatePercentCrownClosureState(newDerivedBy, newSiteSpeciesValues) - updateBasalAreaAndTreesState(newDerivedBy, newSiteSpeciesValues, newAge) + updateStates(newDerivedBy, newSiteSpeciesValues, newAge) }, { immediate: true }, ) diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts index 5c6cffb76..2fff5d298 100644 --- a/frontend/src/utils/util.ts +++ b/frontend/src/utils/util.ts @@ -13,26 +13,19 @@ export class Util { } static isZeroValue = (value: number | string | null): boolean => { const trimmedValue = this.trimValue(value) - return trimmedValue === 0 || trimmedValue === '0' || trimmedValue === '0.0' + const numericValue = Number(trimmedValue) + return numericValue === 0 } static isEmptyOrZero = (value: number | string | null): boolean => { const trimmedValue = this.trimValue(value) - return ( - this.isZeroValue(trimmedValue) || - trimmedValue === '' || - trimmedValue === null - ) - } - - static isValidVal(item: any): any { - return !Util.isBlank(item) ? item : '' + return this.isZeroValue(trimmedValue) || this.isBlank(trimmedValue) } // undefined, null, NaN, 0, "" (empty string), and false // isBlank(" ") = false static isBlank(item: any): boolean { - if (!item) { + if (item === undefined || item === null || Number.isNaN(item)) { return true } else if (Array.isArray(item)) { return item.length === 0 From 3421ca6c81f1670718c46a867a47345dc3c01b47 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Fri, 4 Oct 2024 13:42:28 -0700 Subject: [PATCH 022/172] Move hint for additional stand attributes / create new constants --- .../AdditionalStandAttributes.vue | 4 ++ frontend/src/constants/constants.ts | 21 +++++++-- frontend/src/constants/options.ts | 45 +++++++++++-------- 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue index 628bf4c28..1ea80784d 100644 --- a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue +++ b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue @@ -19,6 +19,10 @@ +
+ These additional Stand attributes require that a Stand Age and + Basal Area be supplied on the Site Index and the Density pages +
diff --git a/frontend/src/constants/constants.ts b/frontend/src/constants/constants.ts index 4be20c5ac..f415fe27a 100644 --- a/frontend/src/constants/constants.ts +++ b/frontend/src/constants/constants.ts @@ -36,8 +36,8 @@ export const FLOATING = Object.freeze({ }) export const COMPUTED_VALUES = Object.freeze({ - USE: 'Use Computed Values', - MODIFY: 'Modify Computed Values', + USE: 'Use', + MODIFY: 'Modify', }) export const MINIMUM_DBH_LIMITS = Object.freeze({ @@ -48,6 +48,19 @@ export const MINIMUM_DBH_LIMITS = Object.freeze({ CM22_5: '22.5 cm+', }) +export const VOLUME_REPORTED = Object.freeze({ + WHOLE_STEM: 'Whole Stem', + CLOSE_UTIL: 'Close Utilization', + NET_DECAY: 'Net Decay', + NET_DECAY_WASTE: 'Net Decay and Waste', + NET_DECAY_WASTE_BREAKAGE: 'Net Decay, Waste and Breakage', +}) + +export const PROJECTION_TYPE = Object.freeze({ + VOLUME: 'Volume', + CFS_BIOMASS: 'CFS Biomass', +}) + export const DEFAULT_VALUES = Object.freeze({ DERIVED_BY: DERIVED_BY.VOLUME, BEC_ZONE: '8', @@ -72,7 +85,7 @@ export const DEFAULT_VALUES = Object.freeze({ STARTING_AGE: 0, FINISHING_AGE: 250, AGE_INCREMENT: 25, - SELECTED_VOLUME_REPORTED: ['Whole Stem'], - PROJECTION_TYPE: 'Volume', + SELECTED_VOLUME_REPORTED: [VOLUME_REPORTED.WHOLE_STEM], + PROJECTION_TYPE: PROJECTION_TYPE.VOLUME, REPORT_TITLE: 'A Sample Report Title', }) diff --git a/frontend/src/constants/options.ts b/frontend/src/constants/options.ts index f6e12f801..54faccba2 100644 --- a/frontend/src/constants/options.ts +++ b/frontend/src/constants/options.ts @@ -1,4 +1,12 @@ -import { MINIMUM_DBH_LIMITS } from '@/constants/constants' +import { + MINIMUM_DBH_LIMITS, + COMPUTED_VALUES, + FLOATING, + SITE_SPECIES_VALUES, + AGE_TYPE, + VOLUME_REPORTED, + PROJECTION_TYPE, +} from '@/constants/constants' export const derivedByOptions = [ { label: 'Volume', value: 'Volume' }, @@ -58,13 +66,13 @@ export const becZoneOptions = [ ] export const siteSpeciesValuesOptions = [ - { label: 'Computed', value: 'Computed' }, - { label: 'Supplied', value: 'Supplied' }, + { label: 'Computed', value: SITE_SPECIES_VALUES.COMPUTED }, + { label: 'Supplied', value: SITE_SPECIES_VALUES.SUPPLIED }, ] export const ageTypeOptions = [ - { label: 'Total', value: 'Total' }, - { label: 'Breast', value: 'Breast' }, + { label: 'Total', value: AGE_TYPE.TOTAL }, + { label: 'Breast', value: AGE_TYPE.BREAST }, ] export const ecoZoneOptions = [ @@ -112,9 +120,9 @@ export const siteIndexCurveMap = { } export const floatingOptions = [ - { label: 'Float', value: 'Age' }, - { label: 'Float', value: 'Height' }, - { label: 'Float', value: 'SiteIndex' }, + { label: 'Float', value: FLOATING.AGE }, + { label: 'Float', value: FLOATING.HEIGHT }, + { label: 'Float', value: FLOATING.SITEINDEX }, ] export const minimumDBHLimitsOptions = [ @@ -127,21 +135,20 @@ export const minimumDBHLimitsOptions = [ export const additionalStandAttributesOptions = [ { - label: - 'Use Computed Values (These additional Stand attributes require that a Stand Age and Basal Area be supplied on the Site Index and the Density pages)', - value: 'Use Computed Values', + label: 'Use Computed Values', + value: COMPUTED_VALUES.USE, }, - { label: 'Modify Computed Values', value: 'Modify Computed Values' }, + { label: 'Modify Computed Values', value: COMPUTED_VALUES.MODIFY }, ] export const volumeReportedOptions = [ - { label: 'Whole Stem', value: 'Whole Stem' }, - { label: 'Close Utilization', value: 'Close Utilization' }, - { label: 'Net Decay', value: 'Net Decay' }, - { label: 'Net Decay and Waste', value: 'Net Decay and Waste' }, + { label: 'Whole Stem', value: VOLUME_REPORTED.WHOLE_STEM }, + { label: 'Close Utilization', value: VOLUME_REPORTED.CLOSE_UTIL }, + { label: 'Net Decay', value: VOLUME_REPORTED.NET_DECAY }, + { label: 'Net Decay and Waste', value: VOLUME_REPORTED.NET_DECAY_WASTE }, { label: 'Net Decay, Waste and Breakage', - value: 'Net Decay, Waste and Breakage', + value: VOLUME_REPORTED.NET_DECAY_WASTE_BREAKAGE, }, ] @@ -152,6 +159,6 @@ export const includeInReportOptions = [ ] export const projectionTypeOptions = [ - { label: 'Volume', value: 'Volume' }, - { label: 'CFS Biomass', value: 'CFS Biomass' }, + { label: 'Volume', value: PROJECTION_TYPE.VOLUME }, + { label: 'CFS Biomass', value: PROJECTION_TYPE.CFS_BIOMASS }, ] From a70ef78cfa9448f2796029a865cfd74cc6033707 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Fri, 4 Oct 2024 17:12:50 -0700 Subject: [PATCH 023/172] Handling confirm, edit, clear action states --- .../AdditionalStandAttributes.vue | 42 ++++++- .../ReportInfo.vue | 49 +++++++- .../model-param-selection-panes/SiteInfo.vue | 42 ++++++- .../SpeciesInfo.vue | 43 ++++++- .../StandDensity.vue | 44 ++++++- frontend/src/constants/constants.ts | 5 + frontend/src/stores/modelParameterStore.ts | 108 ++++++++++++++++-- .../ModelParameterInput.vue | 14 ++- 8 files changed, 314 insertions(+), 33 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue index 1ea80784d..51de0dafa 100644 --- a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue +++ b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue @@ -7,7 +7,7 @@ {{ - panelOpenStates.additionalStandAttributes === 0 + panelOpenStates.additionalStandAttributes === PANEL.OPEN ? 'mdi-chevron-up' : 'mdi-chevron-down' }} @@ -189,8 +189,22 @@
- Clear - Confirm + Clear + Confirm + Edit
@@ -207,6 +221,7 @@ import { useMessageDialogStore } from '@/stores/common/messageDialogStore' import { storeToRefs } from 'pinia' import { additionalStandAttributesOptions } from '@/constants/options' import { + PANEL, DERIVED_BY, SITE_SPECIES_VALUES, COMPUTED_VALUES, @@ -233,6 +248,14 @@ const { closeUtilNetDecayVolume, } = storeToRefs(modelParameterStore) +const panelName = 'additionalStandAttributes' +const isConfirmEnabled = computed( + () => modelParameterStore.panelState[panelName].editable, +) +const isConfirmed = computed( + () => modelParameterStore.panelState[panelName].confirmed, +) + const isComputedValuesDisabled = ref(false) const isLoreyHeightDisabled = ref(false) const isWholeStemVolume75cmDisabled = ref(false) @@ -507,12 +530,23 @@ const validateComputedValuesModification = (): boolean => { return true } -const confirm = () => { +const onConfirm = () => { const isAllFieldsValid = validateAllFields() const isModificationValid = validateComputedValuesModification() if (isAllFieldsValid && isModificationValid) { form.value?.validate() + // this panel is not in a confirmed state + if (!isConfirmed.value) { + modelParameterStore.confirmPanel(panelName) + } + } +} + +const onEdit = () => { + // this panel has already been confirmed. + if (isConfirmed.value) { + modelParameterStore.editPanel(panelName) } } diff --git a/frontend/src/components/model-param-selection-panes/ReportInfo.vue b/frontend/src/components/model-param-selection-panes/ReportInfo.vue index 44f4aada0..b3d3f4858 100644 --- a/frontend/src/components/model-param-selection-panes/ReportInfo.vue +++ b/frontend/src/components/model-param-selection-panes/ReportInfo.vue @@ -7,7 +7,7 @@ {{ - panelOpenStates.reportInfo === 0 + panelOpenStates.reportInfo === PANEL.OPEN ? 'mdi-chevron-up' : 'mdi-chevron-down' }} @@ -186,8 +186,22 @@ - Clear - Confirm + Clear + Confirm + Edit @@ -208,7 +222,11 @@ import { projectionTypeOptions, minimumDBHLimitsOptions, } from '@/constants/options' -import { DEFAULT_VALUES, MINIMUM_DBH_LIMITS } from '@/constants/constants' +import { + PANEL, + DEFAULT_VALUES, + MINIMUM_DBH_LIMITS, +} from '@/constants/constants' const form = ref() @@ -225,6 +243,14 @@ const { reportTitle, } = storeToRefs(modelParameterStore) +const panelName = 'reportInfo' +const isConfirmEnabled = computed( + () => modelParameterStore.panelState[panelName].editable, +) +const isConfirmed = computed( + () => modelParameterStore.panelState[panelName].confirmed, +) + const validateAge = (value: any) => { if (value === null || value === '') { return true @@ -278,6 +304,19 @@ const clear = () => { minimumDBHLimit: MINIMUM_DBH_LIMITS.CM4_0, })) } -const confirm = () => {} +const onConfirm = () => { + form.value?.validate() + // this panel is not in a confirmed state + if (!isConfirmed.value) { + modelParameterStore.confirmPanel(panelName) + } +} + +const onEdit = () => { + // this panel has already been confirmed. + if (isConfirmed.value) { + modelParameterStore.editPanel(panelName) + } +} diff --git a/frontend/src/components/model-param-selection-panes/SiteInfo.vue b/frontend/src/components/model-param-selection-panes/SiteInfo.vue index 21a0bc068..01792e699 100644 --- a/frontend/src/components/model-param-selection-panes/SiteInfo.vue +++ b/frontend/src/components/model-param-selection-panes/SiteInfo.vue @@ -7,7 +7,7 @@ {{ - panelOpenStates.siteInfo === 0 + panelOpenStates.siteInfo === PANEL.OPEN ? 'mdi-chevron-up' : 'mdi-chevron-down' }} @@ -250,8 +250,22 @@ - Clear - Confirm + Clear + Confirm + Edit @@ -275,6 +289,7 @@ import { floatingOptions, } from '@/constants/options' import { + PANEL, DERIVED_BY, SITE_SPECIES_VALUES, FLOATING, @@ -304,6 +319,14 @@ const { floating, } = storeToRefs(modelParameterStore) +const panelName = 'siteInfo' +const isConfirmEnabled = computed( + () => modelParameterStore.panelState[panelName].editable, +) +const isConfirmed = computed( + () => modelParameterStore.panelState[panelName].confirmed, +) + const computedSpeciesOptions = computed(() => (Object.keys(siteIndexCurveMap) as Array).map( (code) => ({ @@ -548,11 +571,22 @@ const validateRequiredFields = (): boolean => { return true } -const confirm = () => { +const onConfirm = () => { const isRequiredFieldsValid = validateRequiredFields() if (isRequiredFieldsValid) { form.value?.validate() + // this panel is not in a confirmed state + if (!isConfirmed.value) { + modelParameterStore.confirmPanel(panelName) + } + } +} + +const onEdit = () => { + // this panel has already been confirmed. + if (isConfirmed.value) { + modelParameterStore.editPanel(panelName) } } diff --git a/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue b/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue index 67df46c86..7752e717d 100644 --- a/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue +++ b/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue @@ -7,7 +7,7 @@ {{ - panelOpenStates.speciesInfo === 0 + panelOpenStates.speciesInfo === PANEL.OPEN ? 'mdi-chevron-up' : 'mdi-chevron-down' }} @@ -198,8 +198,22 @@ - Clear - Confirm + Clear + Confirm + Edit @@ -214,7 +228,7 @@ import { useMessageDialogStore } from '@/stores/common/messageDialogStore' import { storeToRefs } from 'pinia' import { derivedByOptions, speciesMap } from '@/constants/options' -import { DEFAULT_VALUES } from '@/constants/constants' +import { PANEL, DEFAULT_VALUES } from '@/constants/constants' const form = ref() @@ -232,6 +246,14 @@ const { highestPercentSpecies, } = storeToRefs(modelParameterStore) +const panelName = 'speciesInfo' +const isConfirmEnabled = computed( + () => modelParameterStore.panelState[panelName].editable, +) +const isConfirmed = computed( + () => modelParameterStore.panelState[panelName].confirmed, +) + const computedSpeciesOptions = computed(() => (Object.keys(speciesMap) as Array).map((code) => ({ label: `${code} - ${speciesMap[code]}`, @@ -369,13 +391,24 @@ const validateRequiredFields = (): boolean => { return true } -const confirm = () => { +const onConfirm = () => { const isDuplicateValid = validateDuplicateSpecies() const isTotalPercentValid = validateTotalSpeciesPercent() const isRequiredFieldsValid = validateRequiredFields() if (isDuplicateValid && isTotalPercentValid && isRequiredFieldsValid) { form.value?.validate() + // this panel is not in a confirmed state + if (!isConfirmed.value) { + modelParameterStore.confirmPanel(panelName) + } + } +} + +const onEdit = () => { + // this panel has already been confirmed. + if (isConfirmed.value) { + modelParameterStore.editPanel(panelName) } } diff --git a/frontend/src/components/model-param-selection-panes/StandDensity.vue b/frontend/src/components/model-param-selection-panes/StandDensity.vue index a2bcd4862..70da0f65d 100644 --- a/frontend/src/components/model-param-selection-panes/StandDensity.vue +++ b/frontend/src/components/model-param-selection-panes/StandDensity.vue @@ -7,7 +7,7 @@ {{ - panelOpenStates.standDensity === 0 + panelOpenStates.standDensity === PANEL.OPEN ? 'mdi-chevron-up' : 'mdi-chevron-down' }} @@ -155,8 +155,22 @@ - Clear - Confirm + Clear + Confirm + Edit @@ -172,6 +186,7 @@ import { useModelParameterStore } from '@/stores/modelParameterStore' import { storeToRefs } from 'pinia' import { minimumDBHLimitsOptions } from '@/constants/options' import { + PANEL, DERIVED_BY, SITE_SPECIES_VALUES, DEFAULT_VALUES, @@ -193,6 +208,14 @@ const { percentCrownClosure, } = storeToRefs(modelParameterStore) +const panelName = 'standDensity' +const isConfirmEnabled = computed( + () => modelParameterStore.panelState[panelName].editable, +) +const isConfirmed = computed( + () => modelParameterStore.panelState[panelName].confirmed, +) + const isPercentCrownClosureDisabled = ref(false) const isBasalAreaDisabled = ref(false) const isTreesPerHectareDisabled = ref(false) @@ -312,7 +335,20 @@ const clear = () => { minimumDBHLimit.value = DEFAULT_VALUES.MINIMUM_DBH_LIMIT percentCrownClosure.value = DEFAULT_VALUES.PERCENT_CROWN_CLOSURE } -const confirm = () => {} +const onConfirm = () => { + form.value?.validate() + // this panel is not in a confirmed state + if (!isConfirmed.value) { + modelParameterStore.confirmPanel(panelName) + } +} + +const onEdit = () => { + // this panel has already been confirmed. + if (isConfirmed.value) { + modelParameterStore.editPanel(panelName) + } +} diff --git a/frontend/src/constants/constants.ts b/frontend/src/constants/constants.ts index f415fe27a..ef4e9805d 100644 --- a/frontend/src/constants/constants.ts +++ b/frontend/src/constants/constants.ts @@ -14,6 +14,11 @@ export const SORT_ORDER = Object.freeze({ DESC: 'DESC', }) +export const PANEL = Object.freeze({ + OPEN: 0, + CLOSE: -1, +}) + export const DERIVED_BY = Object.freeze({ VOLUME: 'Volume', BASAL_AREA: 'Basal Area', diff --git a/frontend/src/stores/modelParameterStore.ts b/frontend/src/stores/modelParameterStore.ts index c776f28bb..0171086ab 100644 --- a/frontend/src/stores/modelParameterStore.ts +++ b/frontend/src/stores/modelParameterStore.ts @@ -1,17 +1,106 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' -import { FLOATING, DEFAULT_VALUES } from '@/constants/constants' +import { PANEL, FLOATING, DEFAULT_VALUES } from '@/constants/constants' + +// Define a type for the panel names +type PanelName = + | 'speciesInfo' + | 'siteInfo' + | 'standDensity' + | 'additionalStandAttributes' + | 'reportInfo' + +// Define a type for panel open states +type PanelState = typeof PANEL.OPEN | typeof PANEL.CLOSE export const useModelParameterStore = defineStore('modelParameter', () => { // panel open - const panelOpenStates = ref({ - speciesInfo: 0, // 0: open, -1: closed - siteInfo: 0, - standDensity: 0, - additionalStandAttributes: 0, - reportInfo: 0, + const panelOpenStates = ref>({ + speciesInfo: PANEL.OPEN, + siteInfo: PANEL.OPEN, + standDensity: PANEL.OPEN, + additionalStandAttributes: PANEL.OPEN, + reportInfo: PANEL.OPEN, }) + // Panel states for confirming and editing + const panelState = ref< + Record + >({ + speciesInfo: { confirmed: false, editable: true }, // Only speciesInfo is editable initially + siteInfo: { confirmed: false, editable: false }, + standDensity: { confirmed: false, editable: false }, + additionalStandAttributes: { confirmed: false, editable: false }, + reportInfo: { confirmed: false, editable: false }, + }) + + // Run Model button state + const runModelEnabled = ref(false) + + // + // Indicates that the panel has validated and confirmed the user's input. + // When the user completes all of the input within the panel and clicks the confirm button to pass validation, confirmed is set to true. + // This state means that the data in that panel is currently valid and does not need to be modified. + // When this happens, the confirm button on the current panel changes to "Edit" and the confirm button on the next panel becomes active. + + // + // Indicates that the panel can be modified by the user. + // editable becomes true when a panel is active and allows users to enter or modify data. + // On initial loading, only the first panel (SpeciesInfo) is set to editable to allow modification; + // the other panels start with editable false because the previous panel has not been confirmed. + // In this state, the user can modify the input fields within the panel, and the confirm button is enabled to confirm with validation. + + // Method to handle confirm action for each panel + const confirmPanel = (panelName: PanelName) => { + panelState.value[panelName].confirmed = true + panelState.value[panelName].editable = false + + // Enable the next panel's confirm and clear buttons + const panelOrder: PanelName[] = [ + 'speciesInfo', + 'siteInfo', + 'standDensity', + 'additionalStandAttributes', + 'reportInfo', + ] + const currentIndex = panelOrder.indexOf(panelName) + if (currentIndex !== -1 && currentIndex < panelOrder.length - 1) { + const nextPanel = panelOrder[currentIndex + 1] + panelState.value[nextPanel].editable = true + } + + // Check if all panels are confirmed to enable the 'Run Model' button + runModelEnabled.value = panelOrder.every( + (panel) => panelState.value[panel].confirmed, + ) + } + + // Method to handle edit action for each panel + const editPanel = (panelName: PanelName) => { + panelState.value[panelName].confirmed = false + panelState.value[panelName].editable = true + + // Disable all subsequent panels + const panelOrder: PanelName[] = [ + 'speciesInfo', + 'siteInfo', + 'standDensity', + 'additionalStandAttributes', + 'reportInfo', + ] + const currentIndex = panelOrder.indexOf(panelName) + if (currentIndex !== -1) { + for (let i = currentIndex + 1; i < panelOrder.length; i++) { + const nextPanel = panelOrder[i] + panelState.value[nextPanel].confirmed = false + panelState.value[nextPanel].editable = false + } + } + + // Disable 'Run Model' button + runModelEnabled.value = false + } + // species info const derivedBy = ref(null) @@ -174,6 +263,11 @@ export const useModelParameterStore = defineStore('modelParameter', () => { return { // panel open panelOpenStates, + // Panel state + panelState, + runModelEnabled, + confirmPanel, + editPanel, // species info derivedBy, speciesList, diff --git a/frontend/src/views/input-model-parameters/ModelParameterInput.vue b/frontend/src/views/input-model-parameters/ModelParameterInput.vue index 56fad4e06..4e8189426 100644 --- a/frontend/src/views/input-model-parameters/ModelParameterInput.vue +++ b/frontend/src/views/input-model-parameters/ModelParameterInput.vue @@ -75,10 +75,16 @@ text-align: end; " > - Cancel - Run Model + + + Cancel + Run Model + From 2ccf1c8b0152b16b17442f9cbad0854909d5941d Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Mon, 7 Oct 2024 11:16:47 -0700 Subject: [PATCH 024/172] Change default values of additional stand attributes --- frontend/src/constants/constants.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/constants/constants.ts b/frontend/src/constants/constants.ts index ef4e9805d..3a201d17b 100644 --- a/frontend/src/constants/constants.ts +++ b/frontend/src/constants/constants.ts @@ -80,13 +80,13 @@ export const DEFAULT_VALUES = Object.freeze({ PERCENT_CROWN_CLOSURE: 0, CURRENT_DIAMETER: 11.3, COMPUTED_VALUES: COMPUTED_VALUES.USE, - LOREY_HEIGHT: 13.45, - WHOLE_STEM_VOLUME: 106.6, - BASAL_AREA_125CM: 17.0482, - WHOLE_STEM_VOLUME_125CM: 97.0, - CLOSE_UTIL_VOLUME: 84.1, - CLOSE_UTIL_NET_DECAY_VOLUME: 78.2, - CLOSE_UTIL_NET_DECAY_WASTE_VOLUME: 75.1, + LOREY_HEIGHT: 14.47, + WHOLE_STEM_VOLUME: 61.1, + BASAL_AREA_125CM: 4.7545, + WHOLE_STEM_VOLUME_125CM: 34.8, + CLOSE_UTIL_VOLUME: 23.6, + CLOSE_UTIL_NET_DECAY_VOLUME: 22.6, + CLOSE_UTIL_NET_DECAY_WASTE_VOLUME: 22.2, STARTING_AGE: 0, FINISHING_AGE: 250, AGE_INCREMENT: 25, From 5008ff711eebe255a597ce489ef48f1708279f6e Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Mon, 7 Oct 2024 11:19:22 -0700 Subject: [PATCH 025/172] Add validateComparison and validateRange / remove rules and error-message attributes from components / reset min,max attributs from components --- .../AdditionalStandAttributes.vue | 253 +++++++++++++----- 1 file changed, 185 insertions(+), 68 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue index 51de0dafa..6b762c8c8 100644 --- a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue +++ b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue @@ -49,10 +49,9 @@ label="Lorey Height - 7.5cm+ (meters)" type="number" v-model="loreyHeight" - min="0" + min="0.01" + max="99.90" step="0.01" - :rules="[validateMinimum]" - :error-messages="loreyHeightError" persistent-placeholder :placeholder="loreyHeightPlaceholder" density="compact" @@ -65,10 +64,9 @@ { - if (value === null || value === '') { - return true - } - if (value < 0) { - return 'Please enter a value greater than 0' - } - return true -} - -const loreyHeightError = computed(() => { - const error = validateMinimum(loreyHeight.value) - return error === true ? [] : [error] -}) - -const wholeStemVolume75cmError = computed(() => { - const error = validateMinimum(wholeStemVolume75cm.value) - return error === true ? [] : [error] -}) - -const basalArea125cmError = computed(() => { - const error = validateMinimum(basalArea125cm.value) - return error === true ? [] : [error] -}) - -const wholeStemVolume125cmError = computed(() => { - const error = validateMinimum(wholeStemVolume125cm.value) - return error === true ? [] : [error] -}) - -const closeUtilVolumeError = computed(() => { - const error = validateMinimum(closeUtilVolume.value) - return error === true ? [] : [error] -}) - -const closeUtilNetDecayVolumeError = computed(() => { - const error = validateMinimum(closeUtilNetDecayVolume.value) - return error === true ? [] : [error] -}) - -const closeUtilNetDecayWasteVolumeError = computed(() => { - const error = validateMinimum(closeUtilNetDecayWasteVolume.value) - return error === true ? [] : [error] -}) - const clear = () => { if (form.value) { form.value.reset() @@ -530,11 +479,179 @@ const validateComputedValuesModification = (): boolean => { return true } +// Validation by comparing entered values +const validateComparison = (): boolean => { + if ( + basalArea125cm.value !== null && + basalArea.value !== null && + basalArea125cm.value > basalArea.value + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + `'Basal Area - 12.5cm+' is greater than the Basal Area defined on the Stand Density Pane.\n\n 'Basal Area - 7.5cm+' on Stand Density Pane: ${basalArea.value}`, + { width: 400 }, + ) + return false + } + + if ( + wholeStemVolume125cm.value !== null && + wholeStemVolume75cm.value !== null && + wholeStemVolume125cm.value > wholeStemVolume75cm.value + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Whole Stem Volume - 12.5cm+': is greater than 'Whole Stem Volume - 7.5cm+'", + { width: 400 }, + ) + return false + } + + if ( + closeUtilVolume.value !== null && + wholeStemVolume125cm.value !== null && + closeUtilVolume.value > wholeStemVolume125cm.value + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Close Utilization Volume - 12.5cm+': is greater than 'Whole Stem Volume - 12.5cm+'", + { width: 400 }, + ) + return false + } + + if ( + closeUtilNetDecayVolume.value !== null && + closeUtilVolume.value !== null && + closeUtilNetDecayVolume.value > closeUtilVolume.value + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Close Utilization Net Decay Volume - 12.5cm+': is greater than 'Close Utilization Volume - 12.5cm+'", + { width: 400 }, + ) + return false + } + + if ( + closeUtilNetDecayWasteVolume.value !== null && + closeUtilNetDecayVolume.value !== null && + closeUtilNetDecayWasteVolume.value > closeUtilNetDecayVolume.value + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Close Utilization Net Decay Waste Volume - 12.5cm+': is greater than 'Close Utilization Net Decay Volume - 12.5cm+'", + { width: 400 }, + ) + return false + } + + return true +} + +// Validation to check the range of input values +const validateRange = (): boolean => { + if ( + loreyHeight.value !== null && + (loreyHeight.value < 0.01 || loreyHeight.value > 99.9) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Lorey Height - 7.5cm+': must range from 0.01 and 99.90", + { width: 400 }, + ) + return false + } + + if ( + wholeStemVolume75cm.value !== null && + (wholeStemVolume75cm.value < 0.1 || wholeStemVolume75cm.value > 2500.0) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Whole Stem Volume - 7.5cm+': must range from 0.1 and 2500.0", + { width: 400 }, + ) + return false + } + + if ( + basalArea125cm.value !== null && + (basalArea125cm.value < 0.1 || basalArea125cm.value > 250.0) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Basal Area - 12.5cm+': must range from 0.1000 and 250.0000", + { width: 400 }, + ) + return false + } + + if ( + wholeStemVolume125cm.value !== null && + (wholeStemVolume125cm.value < 0.0 || wholeStemVolume125cm.value > 2500.0) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Whole Stem Volume - 12.5cm+': must range from 0.0 and 2500.0", + { width: 400 }, + ) + return false + } + + if ( + closeUtilVolume.value !== null && + (closeUtilVolume.value < 0.0 || closeUtilVolume.value > 2500.0) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Close Utilization Volume - 12.5cm+': must range from 0.0 and 2500.0", + { width: 400 }, + ) + return false + } + + if ( + closeUtilNetDecayVolume.value !== null && + (closeUtilNetDecayVolume.value < 0.0 || + closeUtilNetDecayVolume.value > 2500.0) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Close Utilization Net Decay Volume - 12.5cm+': must range from 0.0 and 2500.0", + { width: 400 }, + ) + return false + } + + if ( + closeUtilNetDecayWasteVolume.value !== null && + (closeUtilNetDecayWasteVolume.value < 0.0 || + closeUtilNetDecayWasteVolume.value > 2500.0) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Close Utilization Net Decay Waste Volume - 12.5cm+': must range from 0.0 and 2500.0", + { width: 400 }, + ) + return false + } + + return true +} + const onConfirm = () => { const isAllFieldsValid = validateAllFields() const isModificationValid = validateComputedValuesModification() - - if (isAllFieldsValid && isModificationValid) { + const isComparisonValid = validateComparison() + const isRangeValid = validateRange() + + if ( + isAllFieldsValid && + isModificationValid && + isComparisonValid && + isRangeValid + ) { form.value?.validate() // this panel is not in a confirmed state if (!isConfirmed.value) { From 92580ea8cd225a24a55920823a144437376ae8f2 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Mon, 7 Oct 2024 16:20:03 -0700 Subject: [PATCH 026/172] Define N/A constants / redefine min, max, step attributes in reportInfo and standDensity / remove rules and error-message attributes / add validateComparison and validateRange in reportInfo --- .../AdditionalStandAttributes.vue | 19 +-- .../ReportInfo.vue | 125 ++++++++++-------- .../model-param-selection-panes/SiteInfo.vue | 5 +- .../StandDensity.vue | 19 +-- frontend/src/constants/constants.ts | 4 + 5 files changed, 98 insertions(+), 74 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue index 6b762c8c8..bd5287ef5 100644 --- a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue +++ b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue @@ -219,6 +219,7 @@ import { SITE_SPECIES_VALUES, COMPUTED_VALUES, DEFAULT_VALUES, + NOT_AVAILABLE_INDI, } from '@/constants/constants' const form = ref() @@ -302,15 +303,15 @@ const updateFieldDisabledStates = (newComputedValues: string | null) => { } const updateFieldPlaceholderStates = (newAge: number | null) => { - // TODO - Make sure that all fields are changed to 'N/A' by Age. + // TODO - Make sure that all fields are changed to not available indicator by Age. if (Util.isEmptyOrZero(newAge)) { - loreyHeightPlaceholder.value = 'N/A' - wholeStemVolume75cmPlaceholder.value = 'N/A' - basalArea125cmPlaceholder.value = 'N/A' - wholeStemVolume125cmPlaceholder.value = 'N/A' - closeUtilVolumePlaceholder.value = 'N/A' - closeUtilNetDecayVolumePlaceholder.value = 'N/A' - closeUtilNetDecayWasteVolumePlaceholder.value = 'N/A' + loreyHeightPlaceholder.value = NOT_AVAILABLE_INDI.NA + wholeStemVolume75cmPlaceholder.value = NOT_AVAILABLE_INDI.NA + basalArea125cmPlaceholder.value = NOT_AVAILABLE_INDI.NA + wholeStemVolume125cmPlaceholder.value = NOT_AVAILABLE_INDI.NA + closeUtilVolumePlaceholder.value = NOT_AVAILABLE_INDI.NA + closeUtilNetDecayVolumePlaceholder.value = NOT_AVAILABLE_INDI.NA + closeUtilNetDecayWasteVolumePlaceholder.value = NOT_AVAILABLE_INDI.NA } else { loreyHeightPlaceholder.value = '' wholeStemVolume75cmPlaceholder.value = '' @@ -323,7 +324,7 @@ const updateFieldPlaceholderStates = (newAge: number | null) => { } const updateFieldValueStates = (newAge: number | null) => { - // TODO - Make sure that all fields are changed to 'N/A' by Age. + // TODO - Make sure that all fields are changed to not available by Age. if (Util.isEmptyOrZero(newAge)) { loreyHeight.value = null wholeStemVolume75cm.value = null diff --git a/frontend/src/components/model-param-selection-panes/ReportInfo.vue b/frontend/src/components/model-param-selection-panes/ReportInfo.vue index b3d3f4858..4178bc426 100644 --- a/frontend/src/components/model-param-selection-panes/ReportInfo.vue +++ b/frontend/src/components/model-param-selection-panes/ReportInfo.vue @@ -28,11 +28,9 @@ v-model="startingAge" min="0" max="500" - step="1" - :rules="[validateAge]" - :error-messages="startingAgeError" + step="10" persistent-placeholder - placeholder="Select..." + placeholder="" density="compact" dense > @@ -43,13 +41,11 @@ label="Finishing Age" type="number" v-model="finishingAge" - min="0" - max="500" + min="1" + max="450" step="10" - :rules="[validateAge]" - :error-messages="finishingAgeError" persistent-placeholder - placeholder="Select..." + placeholder="" density="compact" dense > @@ -63,10 +59,8 @@ min="1" max="350" step="5" - :rules="[validateAgeIncrement]" - :error-messages="ageIncrementError" persistent-placeholder - placeholder="Select..." + placeholder="" density="compact" dense > @@ -213,6 +207,7 @@ diff --git a/frontend/src/constants/constants.ts b/frontend/src/constants/constants.ts index 5d3ba7e9e..3b1ca7938 100644 --- a/frontend/src/constants/constants.ts +++ b/frontend/src/constants/constants.ts @@ -79,6 +79,10 @@ export const MODEL_PARAMETER_PANEL = Object.freeze({ }) export const NUM_INPUT_LIMITS = Object.freeze({ + SPECIES_PERCENT_MAX: 100, + SPECIES_PERCENT_MIN: 0, + SPECIES_PERCENT_STEP: 5, + SPECIES_PERCENT_DECIMAL_NUM: 1, AGE_MAX: 500, AGE_MIN: 0, AGE_STEP: 10, diff --git a/frontend/src/constants/mappings.ts b/frontend/src/constants/mappings.ts index 6f507c3ec..e9d37e63a 100644 --- a/frontend/src/constants/mappings.ts +++ b/frontend/src/constants/mappings.ts @@ -1,5 +1,5 @@ // Mapping species code and species names -export const SPECIES_Map = { +export const SPECIES_MAP = { AC: 'Poplar', AT: 'Aspen', B: 'True Fir', diff --git a/frontend/src/stores/modelParameterStore.ts b/frontend/src/stores/modelParameterStore.ts index b56dc6ffd..b4867860b 100644 --- a/frontend/src/stores/modelParameterStore.ts +++ b/frontend/src/stores/modelParameterStore.ts @@ -1,6 +1,11 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' -import { PANEL, FLOATING, MODEL_PARAMETER_PANEL } from '@/constants/constants' +import { + PANEL, + FLOATING, + MODEL_PARAMETER_PANEL, + NUM_INPUT_LIMITS, +} from '@/constants/constants' import { DEFAULT_VALUES } from '@/constants/defaults' import type { PanelName, PanelState } from '@/types/types' @@ -95,7 +100,7 @@ export const useModelParameterStore = defineStore('modelParameter', () => { // species info const derivedBy = ref(null) - const speciesList = ref<{ species: string | null; percent: number | null }[]>( + const speciesList = ref<{ species: string | null; percent: string | null }[]>( [ { species: null, percent: null }, { species: null, percent: null }, @@ -125,8 +130,13 @@ export const useModelParameterStore = defineStore('modelParameter', () => { const totalPercent = speciesList.value.reduce((acc, item) => { return acc + (parseFloat(item.percent as any) || 0) }, 0) - // preserve to the first decimal place and truncate after that - return Math.floor(totalPercent * 10) / 10 + + // Preserve to the first decimal place and convert to string in '##0.0' format + const formattedPercent = (Math.floor(totalPercent * 10) / 10).toFixed( + NUM_INPUT_LIMITS.SPECIES_PERCENT_DECIMAL_NUM, + ) + + return formattedPercent }) const totalSpeciesGroupPercent = computed(() => { @@ -136,7 +146,8 @@ export const useModelParameterStore = defineStore('modelParameter', () => { }) const isOverTotalPercent = computed(() => { - return totalSpeciesPercent.value > 100 + const numericTotalPercent = parseFloat(totalSpeciesPercent.value) || 0 + return numericTotalPercent > 100 }) const updateSpeciesGroup = () => { @@ -208,12 +219,12 @@ export const useModelParameterStore = defineStore('modelParameter', () => { const setDefaultValues = () => { derivedBy.value = DEFAULT_VALUES.DERIVED_BY speciesList.value = [ - { species: 'PL', percent: 30 }, - { species: 'AC', percent: 30 }, - { species: 'H', percent: 30 }, - { species: 'S', percent: 10 }, - { species: null, percent: 0 }, - { species: null, percent: 0 }, + { species: 'PL', percent: '30.0' }, + { species: 'AC', percent: '30.0' }, + { species: 'H', percent: '30.0' }, + { species: 'S', percent: '10.0' }, + { species: null, percent: '0.0' }, + { species: null, percent: '0.0' }, ] updateSpeciesGroup() From ca707c62c76d1e5be12b0ad3fa98a540f762ac21 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Tue, 15 Oct 2024 15:38:00 -0700 Subject: [PATCH 045/172] Change to string types on additionalStandAttributes, reportInfo, siteInfo and standDensity / add ValidateValues --- .../AdditionalStandAttributes.vue | 149 +++++++++++++++--- .../ReportInfo.vue | 45 +++++- .../model-param-selection-panes/SiteInfo.vue | 38 ++++- .../SpeciesInfo.vue | 6 +- .../StandDensity.vue | 77 +++++++-- frontend/src/constants/defaults.ts | 14 +- frontend/src/stores/modelParameterStore.ts | 18 +-- 7 files changed, 281 insertions(+), 66 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue index 853b3373c..d844b9817 100644 --- a/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue +++ b/frontend/src/components/model-param-selection-panes/AdditionalStandAttributes.vue @@ -258,13 +258,13 @@ const closeUtilVolumePlaceholder = ref('') const closeUtilNetDecayVolumePlaceholder = ref('') const closeUtilNetDecayWasteVolumePlaceholder = ref('') -const loreyHeightOriginal = ref(null) -const wholeStemVolume75cmOriginal = ref(null) -const basalArea125cmOriginal = ref(null) -const wholeStemVolume125cmOriginal = ref(null) -const closeUtilVolumeOriginal = ref(null) -const closeUtilNetDecayVolumeOriginal = ref(null) -const closeUtilNetDecayWasteVolumeOriginal = ref(null) +const loreyHeightOriginal = ref(null) +const wholeStemVolume75cmOriginal = ref(null) +const basalArea125cmOriginal = ref(null) +const wholeStemVolume125cmOriginal = ref(null) +const closeUtilVolumeOriginal = ref(null) +const closeUtilNetDecayVolumeOriginal = ref(null) +const closeUtilNetDecayWasteVolumeOriginal = ref(null) const updateComputedValuesState = ( newDerivedBy: string | null, @@ -376,7 +376,7 @@ const clear = () => { } const validateFieldPresenceAndValue = ( - fieldValue: number | null, + fieldValue: string | null, fieldName: string, ): boolean => { if (Util.isBlank(fieldValue)) { @@ -452,7 +452,7 @@ const validateComputedValuesModification = (): boolean => { if (!hasModification) { messageDialogStore.openDialog( 'No Modifications!', - "At least one of the starting values must have been modified from the original computed values.\n\n Please modify at least one starting value or switch to 'Computed Values' mode.", + "At least one of the starting values must have been modified from the original computed values.\n\n Please modify at least one starting value or switch to 'Computed Values' mode", { width: 400 }, ) return false @@ -461,12 +461,11 @@ const validateComputedValuesModification = (): boolean => { return true } -// Validation by comparing entered values const validateComparison = (): boolean => { if ( basalArea125cm.value !== null && basalArea.value !== null && - basalArea125cm.value > basalArea.value + parseFloat(basalArea125cm.value) > parseFloat(basalArea.value) ) { messageDialogStore.openDialog( 'Invalid Input!', @@ -535,8 +534,8 @@ const validateComparison = (): boolean => { const validateRange = (): boolean => { if ( loreyHeight.value !== null && - (loreyHeight.value < NUM_INPUT_LIMITS.LOREY_HEIGHT_MIN || - loreyHeight.value > NUM_INPUT_LIMITS.LOREY_HEIGHT_MAX) + (parseFloat(loreyHeight.value) < NUM_INPUT_LIMITS.LOREY_HEIGHT_MIN || + parseFloat(loreyHeight.value) > NUM_INPUT_LIMITS.LOREY_HEIGHT_MAX) ) { messageDialogStore.openDialog( 'Invalid Input!', @@ -548,8 +547,10 @@ const validateRange = (): boolean => { if ( wholeStemVolume75cm.value !== null && - (wholeStemVolume75cm.value < NUM_INPUT_LIMITS.WHOLE_STEM_VOL_75CM_MIN || - wholeStemVolume75cm.value > NUM_INPUT_LIMITS.WHOLE_STEM_VOL_75CM_MAX) + (parseFloat(wholeStemVolume75cm.value) < + NUM_INPUT_LIMITS.WHOLE_STEM_VOL_75CM_MIN || + parseFloat(wholeStemVolume75cm.value) > + NUM_INPUT_LIMITS.WHOLE_STEM_VOL_75CM_MAX) ) { messageDialogStore.openDialog( 'Invalid Input!', @@ -561,8 +562,8 @@ const validateRange = (): boolean => { if ( basalArea125cm.value !== null && - (basalArea125cm.value < NUM_INPUT_LIMITS.BASAL_AREA_125CM_MIN || - basalArea125cm.value > NUM_INPUT_LIMITS.BASAL_AREA_125CM_MAX) + (parseFloat(basalArea125cm.value) < NUM_INPUT_LIMITS.BASAL_AREA_125CM_MIN || + parseFloat(basalArea125cm.value) > NUM_INPUT_LIMITS.BASAL_AREA_125CM_MAX) ) { messageDialogStore.openDialog( 'Invalid Input!', @@ -574,8 +575,10 @@ const validateRange = (): boolean => { if ( wholeStemVolume125cm.value !== null && - (wholeStemVolume125cm.value < NUM_INPUT_LIMITS.WHOLE_STEM_VOL_125CM_MIN || - wholeStemVolume125cm.value > NUM_INPUT_LIMITS.WHOLE_STEM_VOL_125CM_MAX) + (parseFloat(wholeStemVolume125cm.value) < + NUM_INPUT_LIMITS.WHOLE_STEM_VOL_125CM_MIN || + parseFloat(wholeStemVolume125cm.value) > + NUM_INPUT_LIMITS.WHOLE_STEM_VOL_125CM_MAX) ) { messageDialogStore.openDialog( 'Invalid Input!', @@ -587,8 +590,8 @@ const validateRange = (): boolean => { if ( closeUtilVolume.value !== null && - (closeUtilVolume.value < NUM_INPUT_LIMITS.CU_VOLUME_MIN || - closeUtilVolume.value > NUM_INPUT_LIMITS.CU_VOLUME_MAX) + (parseFloat(closeUtilVolume.value) < NUM_INPUT_LIMITS.CU_VOLUME_MIN || + parseFloat(closeUtilVolume.value) > NUM_INPUT_LIMITS.CU_VOLUME_MAX) ) { messageDialogStore.openDialog( 'Invalid Input!', @@ -600,8 +603,10 @@ const validateRange = (): boolean => { if ( closeUtilNetDecayVolume.value !== null && - (closeUtilNetDecayVolume.value < NUM_INPUT_LIMITS.CU_NET_DECAY_VOL_MIN || - closeUtilNetDecayVolume.value > NUM_INPUT_LIMITS.CU_NET_DECAY_VOL_MAX) + (parseFloat(closeUtilNetDecayVolume.value) < + NUM_INPUT_LIMITS.CU_NET_DECAY_VOL_MIN || + parseFloat(closeUtilNetDecayVolume.value) > + NUM_INPUT_LIMITS.CU_NET_DECAY_VOL_MAX) ) { messageDialogStore.openDialog( 'Invalid Input!', @@ -613,9 +618,9 @@ const validateRange = (): boolean => { if ( closeUtilNetDecayWasteVolume.value !== null && - (closeUtilNetDecayWasteVolume.value < + (parseFloat(closeUtilNetDecayWasteVolume.value) < NUM_INPUT_LIMITS.CU_NET_DECAY_WASTE_VOL_MIN || - closeUtilNetDecayWasteVolume.value > + parseFloat(closeUtilNetDecayWasteVolume.value) > NUM_INPUT_LIMITS.CU_NET_DECAY_WASTE_VOL_MAX) ) { messageDialogStore.openDialog( @@ -629,12 +634,104 @@ const validateRange = (): boolean => { return true } +const validateValues = (): boolean => { + if (loreyHeight.value && !/^\d+(\.\d{2})?$/.test(loreyHeight.value)) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Lorey Height - 7.5cm+' must be in the format ##0.00", + { width: 400 }, + ) + return false + } + + if ( + wholeStemVolume75cm.value && + !/^\d+(\.\d)?$/.test(wholeStemVolume75cm.value) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Whole Stem Volume - 7.5cm+' must be in the format ####0.0", + { width: 400 }, + ) + return false + } + + if (basalArea125cm.value && !/^\d+(\.\d{4})?$/.test(basalArea125cm.value)) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Basal Area - 12.5cm+' must be in the format ##0.0000", + { width: 400 }, + ) + return false + } + + if ( + wholeStemVolume125cm.value && + !/^\d+(\.\d)?$/.test(wholeStemVolume125cm.value) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Whole Stem Volume - 12.5cm+' must be in the format ###0.0", + { width: 400 }, + ) + return false + } + + if (closeUtilVolume.value && !/^\d+(\.\d)?$/.test(closeUtilVolume.value)) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Close Utilization Volume - 12.5cm+' must be in the format ###0.0", + { width: 400 }, + ) + return false + } + + if ( + closeUtilNetDecayVolume.value && + !/^\d+(\.\d)?$/.test(closeUtilNetDecayVolume.value) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Close Utilization Net Decay Volume - 12.5cm+' must be in the format ###0.0", + { width: 400 }, + ) + return false + } + + if ( + closeUtilNetDecayVolume.value && + !/^\d+(\.\d)?$/.test(closeUtilNetDecayVolume.value) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Close Utilization Net Decay Volume - 12.5cm+' must be in the format ###0.0", + { width: 400 }, + ) + return false + } + + if ( + closeUtilNetDecayWasteVolume.value && + !/^\d+(\.\d)?$/.test(closeUtilNetDecayWasteVolume.value) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Close Utilization Net Decay Waste Volume - 12.5cm+' must be in the format ###0.0", + { width: 400 }, + ) + return false + } + + return true +} + const onConfirm = () => { if ( validateAllFields() && validateComputedValuesModification() && validateComparison() && - validateRange() + validateRange() && + validateValues() ) { form.value?.validate() // this panel is not in a confirmed state diff --git a/frontend/src/components/model-param-selection-panes/ReportInfo.vue b/frontend/src/components/model-param-selection-panes/ReportInfo.vue index 11882336a..8abbfab28 100644 --- a/frontend/src/components/model-param-selection-panes/ReportInfo.vue +++ b/frontend/src/components/model-param-selection-panes/ReportInfo.vue @@ -278,7 +278,7 @@ const validateComparison = (): boolean => { if (finishingAge.value < startingAge.value) { messageDialogStore.openDialog( 'Invalid Input!', - "'Finish Age' must be at least as great as the 'Start Age'.", + "'Finish Age' must be at least as great as the 'Start Age'", { width: 400 }, ) return false @@ -333,8 +333,49 @@ const validateRange = (): boolean => { return true } + +const validateValues = (): boolean => { + if ( + startingAge.value && + (!Number.isInteger(startingAge.value) || startingAge.value < 0) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Starting Age' must be a non-negative integer", + { width: 400 }, + ) + return false + } + + if ( + finishingAge.value && + (!Number.isInteger(finishingAge.value) || finishingAge.value < 0) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Finishing Age' must be a non-negative integer", + { width: 400 }, + ) + return false + } + + if ( + ageIncrement.value && + (!Number.isInteger(ageIncrement.value) || ageIncrement.value < 0) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Age Increment' must be a non-negative integer", + { width: 400 }, + ) + return false + } + + return true +} + const onConfirm = () => { - if (validateComparison() && validateRange()) { + if (validateComparison() && validateRange() && validateValues()) { form.value?.validate() // this panel is not in a confirmed state if (!isConfirmed.value) { diff --git a/frontend/src/components/model-param-selection-panes/SiteInfo.vue b/frontend/src/components/model-param-selection-panes/SiteInfo.vue index c1dbb1ce1..9fcc343b6 100644 --- a/frontend/src/components/model-param-selection-panes/SiteInfo.vue +++ b/frontend/src/components/model-param-selection-panes/SiteInfo.vue @@ -657,7 +657,37 @@ const clear = () => { ) } -// Validation to check the range of input values +const validateValues = (): boolean => { + if (age.value && (!Number.isInteger(age.value) || age.value < 0)) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Age' must be a non-negative integer", + { width: 400 }, + ) + return false + } + + if (height.value && !/^\d+(\.\d{2})?$/.test(height.value)) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Height' must be in the format ##0.00", + { width: 400 }, + ) + return false + } + + if (bha50SiteIndex.value && !/^\d+(\.\d{2})?$/.test(bha50SiteIndex.value)) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'BHA 50 Site Index' must be in the format ##0.00", + { width: 400 }, + ) + return false + } + + return true +} + const validateRange = (): boolean => { if (age.value !== null) { if (age.value < 0 || age.value > 500) { @@ -706,7 +736,7 @@ const validateRequiredFields = (): boolean => { ) { messageDialogStore.openDialog( 'Missing Information', - `The species '${selectedSiteSpecies.value}' must have Age/Height/BHA 50 Site Index values supplied.`, + `The species '${selectedSiteSpecies.value}' must have Age/Height/BHA 50 Site Index values supplied`, { width: 400 }, ) return false @@ -715,7 +745,7 @@ const validateRequiredFields = (): boolean => { if (Util.isEmptyOrZero(bha50SiteIndex.value)) { messageDialogStore.openDialog( 'Missing Information', - `The species '${selectedSiteSpecies.value}' must have an BHA 50 Site Index value supplied.`, + `The species '${selectedSiteSpecies.value}' must have an BHA 50 Site Index value supplied`, { width: 400 }, ) return false @@ -726,7 +756,7 @@ const validateRequiredFields = (): boolean => { } const onConfirm = () => { - if (validateRequiredFields() && validateRange()) { + if (validateRequiredFields() && validateRange() && validateValues()) { form.value?.validate() // this panel is not in a confirmed state if (!isConfirmed.value) { diff --git a/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue b/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue index 0cafb5fa0..9f037a98b 100644 --- a/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue +++ b/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue @@ -455,8 +455,8 @@ const validateDuplicateSpecies = (): boolean => { : '' const message = speciesLabel - ? `Species '${duplicateSpecies} - ${speciesLabel}' already specified.` - : `Species '${duplicateSpecies}' already specified.` + ? `Species '${duplicateSpecies} - ${speciesLabel}' already specified` + : `Species '${duplicateSpecies}' already specified` messageDialogStore.openDialog('Data Duplicated!', message) return false @@ -472,7 +472,7 @@ const validateTotalSpeciesPercent = (): boolean => { ) { messageDialogStore.openDialog( 'Data Incomplete!', - 'Species percentage must add up to a total of 100.0% in order to run a valid model.', + 'Species percentage must add up to a total of 100.0% in order to run a valid model', { width: 400 }, ) return false diff --git a/frontend/src/components/model-param-selection-panes/StandDensity.vue b/frontend/src/components/model-param-selection-panes/StandDensity.vue index 3009699ec..d4cca10c2 100644 --- a/frontend/src/components/model-param-selection-panes/StandDensity.vue +++ b/frontend/src/components/model-param-selection-panes/StandDensity.vue @@ -37,7 +37,7 @@ :disabled="!isConfirmEnabled" > A default will be computed when the model is run. @@ -184,7 +184,7 @@ > { const updateCrownClosureState = ( isVolume: boolean, isComputed: boolean, - isAgeEmptyOrZero: boolean, + isAgeZero: boolean, ) => { - isPercentCrownClosureDisabled.value = - !(isVolume && isComputed) || isAgeEmptyOrZero + isPercentCrownClosureDisabled.value = !(isVolume && isComputed) || isAgeZero if (isPercentCrownClosureDisabled.value) { crownClosurePlaceholder.value = NOT_AVAILABLE_INDI.NA percentCrownClosure.value = null } else { crownClosurePlaceholder.value = '' + percentCrownClosure.value = 0 } } @@ -356,12 +356,12 @@ const updateStates = ( const isVolume = newDerivedBy === DERIVED_BY.VOLUME const isBasalArea = newDerivedBy === DERIVED_BY.BASAL_AREA const isComputed = newSiteSpeciesValues === SITE_SPECIES_VALUES.COMPUTED - const isAgeEmptyOrZero = Util.isEmptyOrZero(newAge) + const isAgeZero = Util.isZeroValue(newAge) // Update states using individual functions - updateBasalAreaState(isBasalArea && isComputed, isAgeEmptyOrZero) - updateTreesPerHectareState(isBasalArea && isComputed, isAgeEmptyOrZero) - updateCrownClosureState(isVolume, isComputed, isAgeEmptyOrZero) + updateBasalAreaState(isBasalArea && isComputed, isAgeZero) + updateTreesPerHectareState(isBasalArea && isComputed, isAgeZero) + updateCrownClosureState(isVolume, isComputed, isAgeZero) } watch( @@ -486,13 +486,60 @@ const clear = () => { percentCrownClosure.value = DEFAULT_VALUES.PERCENT_CROWN_CLOSURE } -// Validation to check the range of input values +const validateValues = (): boolean => { + if ( + percentStockableArea.value && + (!Number.isInteger(percentStockableArea.value) || + percentStockableArea.value < 0) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'% Stockable Area' must be a non-negative integer", + { width: 400 }, + ) + return false + } + + if ( + percentCrownClosure.value && + (!Number.isInteger(percentCrownClosure.value) || + percentCrownClosure.value < 0) + ) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Crown Closure' must be a non-negative integer", + { width: 400 }, + ) + return false + } + + if (basalArea.value && !/^\d+(\.\d{4})?$/.test(basalArea.value)) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Basal Area' must be in the format ##0.0000", + { width: 400 }, + ) + return false + } + + if (treesPerHectare.value && !/^\d+(\.\d{2})?$/.test(treesPerHectare.value)) { + messageDialogStore.openDialog( + 'Invalid Input!', + "'Trees per Hectare' must be in the format ####0.00", + { width: 400 }, + ) + return false + } + + return true +} + const validateRange = (): boolean => { const psa = Util.toNumber(percentStockableArea.value) if (psa && (psa < 0 || psa > 100)) { messageDialogStore.openDialog( 'Invalid Input!', - "'Percent Stockable Area' must range from 0 and 100.", + "'Percent Stockable Area' must range from 0 and 100", { width: 400 }, ) return false @@ -502,7 +549,7 @@ const validateRange = (): boolean => { if (ba && (ba < 0.1 || ba > 250.0)) { messageDialogStore.openDialog( 'Invalid Input!', - "'Basal Area' must range from 0.1000 and 250.0000.", + "'Basal Area' must range from 0.1000 and 250.0000", { width: 400 }, ) return false @@ -512,7 +559,7 @@ const validateRange = (): boolean => { if (tph && (tph < 0.1 || tph > 9999.9)) { messageDialogStore.openDialog( 'Invalid Input!', - "'Trees per Hectare' must range from 0.10 and 9999.90.", + "'Trees per Hectare' must range from 0.10 and 9999.90", { width: 400 }, ) return false @@ -522,7 +569,7 @@ const validateRange = (): boolean => { if (pcc && (pcc < 0 || pcc > 100)) { messageDialogStore.openDialog( 'Invalid Input!', - "'Crown Closure' must range from 0 and 100.", + "'Crown Closure' must range from 0 and 100", { width: 400 }, ) return false @@ -583,7 +630,7 @@ function validateQuadDiameter(): string | null { } async function validateFormInputs(): Promise { - if (!validateRange()) { + if (!validateRange() || !validateValues()) { return false } diff --git a/frontend/src/constants/defaults.ts b/frontend/src/constants/defaults.ts index b46ba1db6..913827ddb 100644 --- a/frontend/src/constants/defaults.ts +++ b/frontend/src/constants/defaults.ts @@ -16,13 +16,13 @@ export const DEFAULT_VALUES = Object.freeze({ CURRENT_DIAMETER: '11.3', PERCENT_CROWN_CLOSURE: 0, COMPUTED_VALUES: CONSTANTS.COMPUTED_VALUES.USE, - LOREY_HEIGHT: 14.47, - WHOLE_STEM_VOLUME: 61.1, - BASAL_AREA_125CM: 4.7545, - WHOLE_STEM_VOLUME_125CM: 34.8, - CLOSE_UTIL_VOLUME: 23.6, - CLOSE_UTIL_NET_DECAY_VOLUME: 22.6, - CLOSE_UTIL_NET_DECAY_WASTE_VOLUME: 22.2, + LOREY_HEIGHT: '14.47', + WHOLE_STEM_VOLUME: '61.1', + BASAL_AREA_125CM: '4.7545', + WHOLE_STEM_VOLUME_125CM: '34.8', + CLOSE_UTIL_VOLUME: '23.6', + CLOSE_UTIL_NET_DECAY_VOLUME: '22.6', + CLOSE_UTIL_NET_DECAY_WASTE_VOLUME: '22.2', STARTING_AGE: 0, FINISHING_AGE: 250, AGE_INCREMENT: 25, diff --git a/frontend/src/stores/modelParameterStore.ts b/frontend/src/stores/modelParameterStore.ts index b4867860b..da3e9f9f8 100644 --- a/frontend/src/stores/modelParameterStore.ts +++ b/frontend/src/stores/modelParameterStore.ts @@ -189,22 +189,22 @@ export const useModelParameterStore = defineStore('modelParameter', () => { const floating = ref(null) // stand density - const percentStockableArea = ref(null) + const percentStockableArea = ref(null) const basalArea = ref(null) const treesPerHectare = ref(null) const minimumDBHLimit = ref(null) const currentDiameter = ref(null) - const percentCrownClosure = ref(null) + const percentCrownClosure = ref(null) // additional stand attributes const computedValues = ref(null) - const loreyHeight = ref(null) - const basalArea125cm = ref(null) - const closeUtilVolume = ref(null) - const closeUtilNetDecayWasteVolume = ref(null) - const wholeStemVolume75cm = ref(null) - const wholeStemVolume125cm = ref(null) - const closeUtilNetDecayVolume = ref(null) + const loreyHeight = ref(null) + const basalArea125cm = ref(null) + const closeUtilVolume = ref(null) + const closeUtilNetDecayWasteVolume = ref(null) + const wholeStemVolume75cm = ref(null) + const wholeStemVolume125cm = ref(null) + const closeUtilNetDecayVolume = ref(null) // report info const startingAge = ref(null) From 711ac3f63c3cfcbbb4f26c024f83a97eca054749 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Wed, 16 Oct 2024 09:55:47 -0700 Subject: [PATCH 046/172] Change to use constants --- .../model-param-selection-panes/SiteInfo.vue | 17 +++++++++++--- .../SpeciesInfo.vue | 9 +++++--- .../StandDensity.vue | 23 +++++++++++++++---- frontend/src/constants/constants.ts | 3 ++- frontend/src/stores/modelParameterStore.ts | 2 +- 5 files changed, 42 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/model-param-selection-panes/SiteInfo.vue b/frontend/src/components/model-param-selection-panes/SiteInfo.vue index 9fcc343b6..bcedcec53 100644 --- a/frontend/src/components/model-param-selection-panes/SiteInfo.vue +++ b/frontend/src/components/model-param-selection-panes/SiteInfo.vue @@ -690,7 +690,10 @@ const validateValues = (): boolean => { const validateRange = (): boolean => { if (age.value !== null) { - if (age.value < 0 || age.value > 500) { + if ( + age.value < NUM_INPUT_LIMITS.AGE_MIN || + age.value > NUM_INPUT_LIMITS.AGE_MAX + ) { messageDialogStore.openDialog( 'Invalid Input!', "'Stand Age' must range from 0 and 500", @@ -702,7 +705,11 @@ const validateRange = (): boolean => { if (height.value !== null) { const numericHeight = parseFloat(height.value) - if (isNaN(numericHeight) || numericHeight < 0 || numericHeight > 99.9) { + if ( + isNaN(numericHeight) || + numericHeight < NUM_INPUT_LIMITS.HEIGHT_MIN || + numericHeight > NUM_INPUT_LIMITS.HEIGHT_MAX + ) { messageDialogStore.openDialog( 'Invalid Input!', "'Stand Height' must range from 0.00 and 99.90", @@ -714,7 +721,11 @@ const validateRange = (): boolean => { if (height.value !== null) { const numericHeight = parseFloat(height.value) - if (isNaN(numericHeight) || numericHeight < 0 || numericHeight > 60) { + if ( + isNaN(numericHeight) || + numericHeight < NUM_INPUT_LIMITS.BHA50_SITE_INDEX_MIN || + numericHeight > NUM_INPUT_LIMITS.BHA50_SITE_INDEX_MAX + ) { messageDialogStore.openDialog( 'Invalid Input!', "'Site Index' must range from 0.00 and 60.00", diff --git a/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue b/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue index 9f037a98b..f60892e5e 100644 --- a/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue +++ b/frontend/src/components/model-param-selection-panes/SpeciesInfo.vue @@ -373,8 +373,11 @@ const validatePercent = (value: any) => { return true } const numValue = Math.floor(parseFloat(value) * 10) / 10 // validate to the first decimal place only - if (numValue < 0 || numValue > 100) { - return 'Please enter a value between 0 and 100' + if ( + numValue < NUM_INPUT_LIMITS.SPECIES_PERCENT_MIN || + numValue > NUM_INPUT_LIMITS.SPECIES_PERCENT_MAX + ) { + return `Please enter a value between ${NUM_INPUT_LIMITS.SPECIES_PERCENT_MIN} and ${NUM_INPUT_LIMITS.SPECIES_PERCENT_MAX}` } return true } @@ -467,7 +470,7 @@ const validateDuplicateSpecies = (): boolean => { const validateTotalSpeciesPercent = (): boolean => { if ( - totalSpeciesGroupPercent.value !== 100 && + totalSpeciesGroupPercent.value !== NUM_INPUT_LIMITS.TOTAL_SPECIES_PERCENT && highestPercentSpecies !== null ) { messageDialogStore.openDialog( diff --git a/frontend/src/components/model-param-selection-panes/StandDensity.vue b/frontend/src/components/model-param-selection-panes/StandDensity.vue index d4cca10c2..2fde28caf 100644 --- a/frontend/src/components/model-param-selection-panes/StandDensity.vue +++ b/frontend/src/components/model-param-selection-panes/StandDensity.vue @@ -536,7 +536,11 @@ const validateValues = (): boolean => { const validateRange = (): boolean => { const psa = Util.toNumber(percentStockableArea.value) - if (psa && (psa < 0 || psa > 100)) { + if ( + psa && + (psa < NUM_INPUT_LIMITS.PERCENT_STOCKABLE_AREA_MIN || + psa > NUM_INPUT_LIMITS.PERCENT_STOCKABLE_AREA_MAX) + ) { messageDialogStore.openDialog( 'Invalid Input!', "'Percent Stockable Area' must range from 0 and 100", @@ -546,7 +550,11 @@ const validateRange = (): boolean => { } const ba = Util.toNumber(basalArea.value) - if (ba && (ba < 0.1 || ba > 250.0)) { + if ( + ba && + (ba < NUM_INPUT_LIMITS.BASAL_AREA_MIN || + ba > NUM_INPUT_LIMITS.BASAL_AREA_MAX) + ) { messageDialogStore.openDialog( 'Invalid Input!', "'Basal Area' must range from 0.1000 and 250.0000", @@ -556,7 +564,10 @@ const validateRange = (): boolean => { } const tph = Util.toNumber(treesPerHectare.value) - if (tph && (tph < 0.1 || tph > 9999.9)) { + if ( + tph && + (tph < NUM_INPUT_LIMITS.TPH_MIN || tph > NUM_INPUT_LIMITS.TPH_MAX) + ) { messageDialogStore.openDialog( 'Invalid Input!', "'Trees per Hectare' must range from 0.10 and 9999.90", @@ -566,7 +577,11 @@ const validateRange = (): boolean => { } const pcc = Util.toNumber(percentCrownClosure.value) - if (pcc && (pcc < 0 || pcc > 100)) { + if ( + pcc && + (pcc < NUM_INPUT_LIMITS.CROWN_CLOSURE_MIN || + pcc > NUM_INPUT_LIMITS.CROWN_CLOSURE_MAX) + ) { messageDialogStore.openDialog( 'Invalid Input!', "'Crown Closure' must range from 0 and 100", diff --git a/frontend/src/constants/constants.ts b/frontend/src/constants/constants.ts index 3b1ca7938..41bf416d8 100644 --- a/frontend/src/constants/constants.ts +++ b/frontend/src/constants/constants.ts @@ -83,10 +83,11 @@ export const NUM_INPUT_LIMITS = Object.freeze({ SPECIES_PERCENT_MIN: 0, SPECIES_PERCENT_STEP: 5, SPECIES_PERCENT_DECIMAL_NUM: 1, + TOTAL_SPECIES_PERCENT: 100, AGE_MAX: 500, AGE_MIN: 0, AGE_STEP: 10, - HEIGHT_MAX: 99, + HEIGHT_MAX: 99.9, HEIGHT_MIN: 0, HEIGHT_STEP: 1, HEIGHT_DECIMAL_NUM: 2, diff --git a/frontend/src/stores/modelParameterStore.ts b/frontend/src/stores/modelParameterStore.ts index da3e9f9f8..2dba49c29 100644 --- a/frontend/src/stores/modelParameterStore.ts +++ b/frontend/src/stores/modelParameterStore.ts @@ -147,7 +147,7 @@ export const useModelParameterStore = defineStore('modelParameter', () => { const isOverTotalPercent = computed(() => { const numericTotalPercent = parseFloat(totalSpeciesPercent.value) || 0 - return numericTotalPercent > 100 + return numericTotalPercent > NUM_INPUT_LIMITS.TOTAL_SPECIES_PERCENT }) const updateSpeciesGroup = () => { From 0c3074d719b198ce71d276f7a9c9bf550938c410 Mon Sep 17 00:00:00 2001 From: Roy Jeong Date: Tue, 22 Oct 2024 14:37:00 -0700 Subject: [PATCH 047/172] Each time a message on snackbar is called, it sets a new timer, hides the existing message, and clear up the timer. --- frontend/src/components/common/AppMessage.vue | 24 +++++++---- frontend/src/constants/constants.ts | 4 ++ frontend/src/stores/common/messageStore.ts | 41 +++++++++++-------- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/frontend/src/components/common/AppMessage.vue b/frontend/src/components/common/AppMessage.vue index 7c9dcf148..520ceaebd 100644 --- a/frontend/src/components/common/AppMessage.vue +++ b/frontend/src/components/common/AppMessage.vue @@ -1,19 +1,21 @@