Displaying differences for changeset
 
display as  

services.py

@@ -1324,6 +1324,30 @@
             logger.info(e)
             raise e
 
+    def is_eqip_num_already_used(self):
+        try:
+            request_json = er2_api.load_json(self._request.body)
+            self.check_request(['eqip_num'])
+            eqip_num = request_json["eqip_num"]
+
+            with self.get_connection() as conn:
+                cursor = conn.cursor(as_dict=True)
+                query = f"""SELECT 
+                            COUNT(*) as count
+                        FROM [project]
+                        WHERE project.eqip_contract_number='{eqip_num}'"""
+
+                cursor.execute(query)
+                rows = cursor.fetchall()
+                logger.info(rows)
+                if rows[0]["count"] == 0:
+                    return er2_api.respond({'used': False})
+                else:
+                    return er2_api.respond({'used': True})
+        except Exception as e:
+            logger.error(e)
+            raise e
+
     def get_status(self):
         try:
             with self.get_connection() as conn:

static/er2_eofDB/js/actions/manageProjects.tsx

@@ -2,6 +2,7 @@
 import { windowStates } from "@owsi/catena/er2_map_userlayers/js/components/window_state"
 import * as er2MapTypes from "@owsi/catena/er2_map_userlayers/js/constants/action_types"
 import { onUpdateUserLayers } from "@owsi/catena/er2_map_userlayers/js/actions/map"
+import {setMapError} from "@owsi/catena/er2_map_userlayers/js/actions"
 import { appendToLogger } from "@owsi/catena/er2_map_userlayers/js/actions/logger"
 import {
     getArea,
@@ -176,6 +177,39 @@
         .catch((ret) => console.log(ret.message, ret.stack))
 }
 
+export const isEQIPNumAlreadyUsed = (eqipNum) => (dispatch, getState) => {
+    let fetchError
+        
+        fetch(
+            `/${getState().meta.name}/api/v1/is_eqip_num_already_used/?token=${
+                getState().token.value
+            }`,
+            {
+                method: "POST",
+                body: JSON.stringify({
+                    eqip_num: eqipNum,
+                }),
+            }
+        )
+            .then((response) => {
+                fetchError = !response.ok
+                return response.json()
+            })
+            .then((result) => {
+                if (fetchError) {
+                    console.log(
+                        "Something went wrong when attempting determine is the user entered a unique eqip contract number"
+                    )
+                    dispatch(
+                        setMapError("We're unable to determine if that EQIP ID has already been used.  Please contact the site administrator or try again later.")
+                    )
+                } else {
+                    dispatch({ type: types.SET_EQIP_NUM_ALREADY_USED, payload: result.used })
+                }
+            })
+            .catch((ret) => console.log(ret.message, ret.stack))
+}
+
 export const setProjectUndo = (projectEQIPNum) => ({
     type: types.SET_UNDO,
     payload: { projectEQIPNum },

static/er2_eofDB/js/components/ManageProjects.tsx

@@ -1,7 +1,7 @@
 /* eslint-disable flowtype/no-types-missing-file-annotation */
-
 import * as React from 'react'
-import { connect, useSelector } from 'react-redux'
+import {useEffect, useState, Fragment, ChangeEvent } from 'react'
+import { connect, useSelector, useDispatch } from 'react-redux'
 import { useKeycloak } from '@react-keycloak/web'
 import area from '@turf/area'
 import shp from 'shpjs'
@@ -65,6 +65,7 @@
 import type { SettingsType } from '../reducers/settings'
 import { getProjectByEQIPNum } from '../reducers/manageProjects'
 import * as routes from '../constants/routes'
+import * as types from '../constants/action_types'
 
 import {
   onDeleteProject,
@@ -90,6 +91,7 @@
 import Project from './Project'
 import {
   getEditingStation,
+  isEQIPNumAlreadyUsed,
   onEditProjectGetProjectMembers,
 } from '../actions/manageProjects'
 import UserSelect from './UserSelect'
@@ -219,43 +221,71 @@
 }))(Dialog)
 
 function ManageProjects(props: Props) {
-  const [undoProject, setUndoProject] = React.useState<ProjectType | undefined>()
-  const [undoMember, setUndoMember] = React.useState<MemberType | undefined>()
-  const [isCreatingNewProject, setIsCreatingNewProject] = React.useState(false)
-  const [isEditingProject, setIsEditingProject] = React.useState(false)
-  const [activeStep, setActiveStep] = React.useState(0)
-  const [createProjectMemberName, setCreateProjectMemberName] = React.useState('')
-  const [createProjectMemberNameInputValue, setCreateProjectMemberNameInputValue] = React.useState('')
-  const [createProjectMemberRoleInputValue, setCreateProjectMemberRoleInputValue] = React.useState('')
-  const [createProjectMembers, setCreateProjectMembers] = React.useState([])
-  const [createProjectSelectedPractice, setCreateProjectSelectedPractice] = React.useState('')
-  const [createProjectPractices, setCreateProjectPractices] = React.useState([])
-  const [createProjectDetailsTitle, setCreateProjectDetailsTitle] = React.useState('')
-  const [createProjectDetailsId, setCreateProjectDetailsId] = React.useState('')
-  const [createProjectDetailsEQIPNum, setCreateProjectDetailsEQIPNum] = React.useState(-1)
-  const [createProjectDetailsTreatmentType, setCreateProjectDetailsTreatmentType] = React.useState('')
-  const [createProjectDetailsStatus, setCreateProjectDetailsStatus] = React.useState('New')
-  const [createProjectDetailsTreatmentDescription, setCreateProjectDetailsTreatmentDescription] = React.useState('')
-  const [createStationDetailsID, setCreateStationDetailsID] = React.useState('')
-  const [createStationDetailsProjectID, setCreateStationDetailsProjectID] = React.useState('')
-  const [createStationDetailsTreatmentIndicator, setCreateStationDetailsTreatmentIndicator] = React.useState('')
-  const [createStationLatitude, setCreateStationLatitude] = React.useState('')
-  const [createStationLongitude, setCreateStationLongitude] = React.useState('')
-  const [createStationDrainageAcres, setCreateStationDrainageAcres] = React.useState('')
-  const [createStationDraingeGeometry, setCreateStationDrainageGeometry] = React.useState()
-  const [documentUploadCategories, setDocumentUploadCategories] = React.useState([])
-  const [errorInputs, setErrorInputs] = React.useState([])
-  const [projectFilterText, setProjectFilterText] = React.useState('')
-  const [userRoleConservationist, setUserRoleConservationist] = React.useState(false)
+  const [undoProject, setUndoProject] = useState<ProjectType | undefined>()
+  const [undoMember, setUndoMember] = useState<MemberType | undefined>()
+  const [isCreatingNewProject, setIsCreatingNewProject] = useState(false)
+  const [isEditingProject, setIsEditingProject] = useState(false)
+  const [activeStep, setActiveStep] = useState(0)
+  const [createProjectMemberName, setCreateProjectMemberName] = useState('')
+  const [createProjectMemberNameInputValue, setCreateProjectMemberNameInputValue] = useState('')
+  const [createProjectMemberRoleInputValue, setCreateProjectMemberRoleInputValue] = useState('')
+  const [createProjectMembers, setCreateProjectMembers] = useState([])
+  const [createProjectSelectedPractice, setCreateProjectSelectedPractice] = useState('')
+  const [createProjectPractices, setCreateProjectPractices] = useState([])
+  const [createProjectDetailsTitle, setCreateProjectDetailsTitle] = useState('')
+  const [createProjectDetailsId, setCreateProjectDetailsId] = useState('')
+  const [createProjectDetailsEQIPNum, setCreateProjectDetailsEQIPNum] = useState(-1)
+  const [createProjectDetailsTreatmentType, setCreateProjectDetailsTreatmentType] = useState('')
+  const [createProjectDetailsStatus, setCreateProjectDetailsStatus] = useState('New')
+  const [createProjectDetailsTreatmentDescription, setCreateProjectDetailsTreatmentDescription] = useState('')
+  const [createStationDetailsID, setCreateStationDetailsID] = useState('')
+  const [createStationDetailsProjectID, setCreateStationDetailsProjectID] = useState('')
+  const [createStationDetailsTreatmentIndicator, setCreateStationDetailsTreatmentIndicator] = useState('')
+  const [createStationLatitude, setCreateStationLatitude] = useState('')
+  const [createStationLongitude, setCreateStationLongitude] = useState('')
+  const [createStationDrainageAcres, setCreateStationDrainageAcres] = useState('')
+  const [createStationDraingeGeometry, setCreateStationDrainageGeometry] = useState()
+  const [documentUploadCategories, setDocumentUploadCategories] = useState([])
+  const [errorInputs, setErrorInputs] = useState([])
+  const [projectFilterText, setProjectFilterText] = useState('')
+  const [userRoleConservationist, setUserRoleConservationist] = useState(false)
 
   const users = useSelector((state) => state.users)
   const { keycloak, initialized } = useKeycloak()
+  const dispatch = useDispatch()
+  const eqipIdAlreadyUsed = useSelector((state) => state.manageProjects.isEQIPIDAlreadyUsed)
+  console.log({eqipIdAlreadyUsed, createProjectDetailsEQIPNum})
 
   const selectedProjects = props.manageProjects.projects.filter(
     (p) => p.selected === true
   )
 
-  React.useEffect(() => {
+  useEffect(() => {
+    dispatch({ type: types.SET_EQIP_NUM_ALREADY_USED, payload: null })
+    const timeoutHandle = setTimeout(() => {
+      console.log('user stopped typing 2 seconds ago')
+      if(createProjectDetailsEQIPNum !== -1){
+        dispatch(isEQIPNumAlreadyUsed(createProjectDetailsEQIPNum))
+      }
+    }, 2000)
+
+    return () => {
+      clearTimeout(timeoutHandle)
+    }
+  },[createProjectDetailsEQIPNum])
+
+  useEffect(() => {
+    if(eqipIdAlreadyUsed){
+      const oldErrorInputs = errorInputs
+      oldErrorInputs.push('detailsEQIPNum')
+      setErrorInputs(oldErrorInputs)
+    } else{
+      const newErrorInputs = errorInputs.filter(e => e!== 'detailsEQIPNum')
+      setErrorInputs(newErrorInputs)
+    }
+  }, [eqipIdAlreadyUsed])
+
+  useEffect(() => {
     //TODO: on fetch from the backend, set isEditingStation to false.
     if (props.manageProjects.isEditingStation && !createStationDetailsID) {
       loadMonitoringStation(getEditingStation(props.manageProjects))
@@ -266,20 +296,20 @@
     }
   }, [props.manageProjects.isEditingStation])
 
-  React.useEffect(() => {
+  useEffect(() => {
     if (users.user.id) {
       setUserRoleConservationist('Conservationist' === users.user['role'])
     }
   }, [users])
 
-  React.useEffect(() => {
+  useEffect(() => {
     // hide the south panel, the user won't be using it in this context.
     if (props.tabbedPanel.windowState === windowStates.minimized) {
       props.setTabbedPanelWindowState(windowStates.opened)
     }
   })
 
-  React.useEffect(() => {
+  useEffect(() => {
     if (
       props.manageProjects.isEditingDocument &&
             selectedProjects.length >= 0
@@ -296,7 +326,7 @@
   ])
 
   // when users.users is updated, update members for each project --> to address the case of if a user's role is updated, then this will be reflected in project stepper
-  React.useEffect(() => {
+  useEffect(() => {
     props.manageProjects.projects.map((project) =>
       props.onEditProjectGetProjectMembers(project.id)
     )
@@ -662,7 +692,7 @@
                   />
                 )}
                 renderOption={(option, { selected }) => (
-                  <React.Fragment>
+                  <Fragment>
                     <Checkbox
                       icon={icon}
                       checkedIcon={checkedIcon}
@@ -673,7 +703,7 @@
                     <span style={{ fontSize: 12 }}>
                       {option.text}
                     </span>
-                  </React.Fragment>
+                  </Fragment>
                 )}
               />
             </FormControl>
@@ -847,7 +877,7 @@
               <Select
                 value={createStationDetailsTreatmentIndicator}
                 onChange={(
-                  event: React.ChangeEvent<HTMLInputElement>
+                  event: ChangeEvent<HTMLInputElement>
                 ) =>
                   setCreateStationDetailsTreatmentIndicator(
                     event.target.value
@@ -1227,7 +1257,7 @@
     const errors = []
     if (createProjectDetailsTitle === '') errors.push('detailsTitle')
     if (createProjectDetailsId === '') errors.push('detailsId')
-    if (createProjectDetailsEQIPNum === -1) errors.push('detailsEQIPNum')
+    if (createProjectDetailsEQIPNum === -1 || eqipIdAlreadyUsed) errors.push('detailsEQIPNum')
     if (createProjectDetailsTreatmentType === '')
       errors.push('detailsTreatmentType')
     if (createProjectDetailsStatus === '') errors.push('detailsStatus')
@@ -1394,12 +1424,12 @@
         <HtmlTooltip
           key={member.id}
           title={
-            <React.Fragment>
+            <Fragment>
               <Typography color="inherit">
                                 Name: {member.name}
               </Typography>
                                 Role: {member.role} <br />
-            </React.Fragment>
+            </Fragment>
           }
         >
           <Chip
@@ -1446,14 +1476,14 @@
                 options={projectUsers}
                 getOptionLabel={(option) => option.name}
                 renderOption={(option) => (
-                  <React.Fragment>
+                  <Fragment>
                     <ListItemText style={{fontWeight: 'bold'}}>
                       {option.name}
                     </ListItemText>
                     <Typography variant="body2" style={{color: '#969696'}}>
                       {option.role}
                     </Typography>
-                  </React.Fragment>
+                  </Fragment>
                 )}
                 renderInput={(params) => (
                   <TextField
@@ -1525,7 +1555,7 @@
               <Select
                 value={createProjectSelectedPractice}
                 onChange={(
-                  event: React.ChangeEvent<HTMLInputElement>
+                  event: ChangeEvent<HTMLInputElement>
                 ) => {
                   setCreateProjectSelectedPractice(
                     event.target.value
@@ -1612,6 +1642,7 @@
           <Grid item xs={6}>
             <TextField
               error={errorInputs.indexOf('detailsEQIPNum') >= 0}
+              helperText={errorInputs.indexOf('detailsEQIPNum') >= 0 ? 'That EQIP Contract Number is already associated with a project.' : null}
               label="EQIP Contract Number"
               fullWidth
               onChange={(event) => {
@@ -1653,7 +1684,7 @@
               <Select
                 value={createProjectDetailsTreatmentType}
                 onChange={(
-                  event: React.ChangeEvent<HTMLInputElement>
+                  event: ChangeEvent<HTMLInputElement>
                 ) =>
                   setCreateProjectDetailsTreatmentType(
                     event.target.value
@@ -1684,7 +1715,7 @@
               <Select
                 value={createProjectDetailsStatus}
                 onChange={(
-                  event: React.ChangeEvent<HTMLInputElement>
+                  event: ChangeEvent<HTMLInputElement>
                 ) =>
                   setCreateProjectDetailsStatus(
                     event.target.value
@@ -1943,7 +1974,7 @@
           onClose={() => props.closeDismissSnackbar()}
           message={dismissSnackbarMessage}
           action={
-            <React.Fragment>
+            <Fragment>
               <Button
                 color="secondary"
                 size="small"
@@ -1961,7 +1992,7 @@
               >
                 <CloseIcon fontSize="small" />
               </IconButton>
-            </React.Fragment>
+            </Fragment>
           }
         />
       </Portal>
@@ -1983,7 +2014,7 @@
           onExit={(e) => handleSnackBarExit()}
           message={snackbarMessage}
           action={
-            <React.Fragment>
+            <Fragment>
               <Button
                 color="secondary"
                 size="small"
@@ -2001,7 +2032,7 @@
               >
                 <CloseIcon fontSize="small" />
               </IconButton>
-            </React.Fragment>
+            </Fragment>
           }
         />
       </Portal>

static/er2_eofDB/js/components/Project.tsx

@@ -19,8 +19,6 @@
 
 interface PropsType {
   project: ProjectType,
-  
-  onSelectProject: Function,
 }
 
 export const Project = (props: PropsType) => {

static/er2_eofDB/js/constants/action_types.js

@@ -14,6 +14,7 @@
 export const SET_CONTRACT_ERROR = 'SET_CONTRACT_ERROR'
 export const SET_FETCHING = 'SET_FETCHING'
 export const SET_CONSERVATION_PRACTICES = 'SET_CONSERVATION_PRACTICES'
+export const SET_EQIP_NUM_ALREADY_USED = 'SET_EQIP_NUM_ALREADY_USED'
 
 export const UPDATE_META = 'UPDATE_META'
 

static/er2_eofDB/js/reducers/manageProjects.tsx

@@ -74,6 +74,7 @@
     isEditingDocument: boolean
     isFilteringDocuments: boolean
     isFilteringMonitoringStations: boolean
+    isEQIPIDAlreadyUsed: boolean
     isDocumentSelected: boolean
     lastChangedProjectEQIPNum: number
     newProject: ProjectType
@@ -97,6 +98,7 @@
   conservationPractices: [],
   savedManagements: null,
   isEditingStation: false,
+  isEQIPIDAlreadyUsed: null,
   isCreatingStation: false,
   isUploadDocumentDialogOpen: false,
   isEditingDocument: false,
@@ -213,6 +215,7 @@
         isEditingStation: false,
         isCreatingStation: false,
         isEditingDocument: false,
+        isEQIPIDAlreadyUsed: null,
       }
     }
   } else if (action.type === actionTypes.CREATE_NEW_PROJECT) {
@@ -533,6 +536,12 @@
       ...state,
       isFilteringDocuments: !state.isFilteringDocuments,
     }
+  } else if (action.type === actionTypes.SET_EQIP_NUM_ALREADY_USED) {
+    console.log(action.payload)
+    newState = {
+      ...state,
+      isEQIPIDAlreadyUsed: action.payload
+    }
   } else if (action.type === actionTypes.SET_IS_UPLOADING_DOCUMENTS) {
     const { isUploadingDocuments } = action.payload
     newState = { ...state, isUploadingDocuments }

urls.py

@@ -30,4 +30,5 @@
     django.conf.urls.url(r'api/v1/get_status', views.get_status),
     django.conf.urls.url(r'api/v1/update_users', views.update_users),
     django.conf.urls.url(r'api/v1/get_project_members', views.get_project_members),
+    django.conf.urls.url(r'api/v1/is_eqip_num_already_used', views.is_eqip_num_already_used),
 ]

views.py

@@ -105,3 +105,9 @@
 def get_project_members(request):
     """ When project is edited, get updated project members & their roles from EOFDMDb.project_participant """
     return svcs.State(request=request).edit_project_get_project_members()
+
+@csrf_exempt
+def is_eqip_num_already_used(request):
+    """ Given a particular EQIP Contract ID, this service returns True if the ID is already assigned to a project
+    in the EOF DB.  Otherwise returns False. """
+    return svcs.EofDB(request=request).is_eqip_num_already_used()
\ No newline at end of file