diff --git a/moped-database/docker-compose.yml b/moped-database/docker-compose.yml index e276799246..018e1058cf 100644 --- a/moped-database/docker-compose.yml +++ b/moped-database/docker-compose.yml @@ -1,7 +1,7 @@ # Architecture Independent Docker Stack Components for Moped Development Environment services: hasura: - image: hasura/graphql-engine:v2.45.0 + image: hasura/graphql-engine:v2.45.1 depends_on: - moped-pgsql expose: diff --git a/moped-database/metadata/tables.yaml b/moped-database/metadata/tables.yaml index 4b6ffb88ab..e4341e898c 100644 --- a/moped-database/metadata/tables.yaml +++ b/moped-database/metadata/tables.yaml @@ -1767,18 +1767,21 @@ columns: - funding_status_id - funding_status_name + - funding_status_description filter: {} - role: moped-editor permission: columns: - funding_status_id - funding_status_name + - funding_status_description filter: {} - role: moped-viewer permission: columns: - funding_status_id - funding_status_name + - funding_status_description filter: {} - table: name: moped_funds @@ -5466,25 +5469,28 @@ - role: moped-admin permission: columns: + - date_added + - is_deleted - workgroup_id - workgroup_name - - date_added filter: {} allow_aggregations: true - role: moped-editor permission: columns: + - date_added + - is_deleted - workgroup_id - workgroup_name - - date_added filter: {} allow_aggregations: true - role: moped-viewer permission: columns: - - workgroup_name - - workgroup_id - date_added + - is_deleted + - workgroup_id + - workgroup_name filter: {} allow_aggregations: true - table: diff --git a/moped-database/migrations/1734717637542_update_workgroups/down.sql b/moped-database/migrations/1734717637542_update_workgroups/down.sql new file mode 100644 index 0000000000..ac995fb234 --- /dev/null +++ b/moped-database/migrations/1734717637542_update_workgroups/down.sql @@ -0,0 +1,3 @@ +-- Updating moped_workgroup table will be up only. If we need to revert, we will need do it manually or +-- update with a future migration. +SELECT 0; diff --git a/moped-database/migrations/1734717637542_update_workgroups/up.sql b/moped-database/migrations/1734717637542_update_workgroups/up.sql new file mode 100644 index 0000000000..b26a6a4622 --- /dev/null +++ b/moped-database/migrations/1734717637542_update_workgroups/up.sql @@ -0,0 +1,37 @@ +-- Add soft deletes to workgroup table +ALTER TABLE moped_workgroup ADD is_deleted boolean DEFAULT FALSE; +COMMENT ON COLUMN moped_workgroup.is_deleted IS 'Indicates soft deletion'; + +INSERT INTO moped_workgroup ("workgroup_name", "workgroup_abbreviation", "department_id") VALUES +('Sidewalks and Urban Trails', 'SUTD', 11); + +-- Soft delete existing workgroup records that are merging into the new one +UPDATE moped_workgroup SET is_deleted = TRUE WHERE workgroup_name IN ('Sidewalks', 'Urban Trails'); + +-- Find existing users records that are associated with the workgroups that are merging +-- and update them to the new workgroup row for SUTD +WITH user_todos AS ( + SELECT workgroup_id AS ids + FROM + moped_workgroup + WHERE + workgroup_name IN ( + 'Sidewalks', + 'Urban Trails' + ) +), + +new_workgroup_row AS ( + SELECT workgroup_id AS id + FROM + moped_workgroup + WHERE + workgroup_name = 'Sidewalks and Urban Trails' +) + +UPDATE +moped_users +SET + workgroup_id = (SELECT id FROM new_workgroup_row) +WHERE + workgroup_id IN (SELECT ids FROM user_todos); diff --git a/moped-database/migrations/1735340549358_add-funding-status-description/down.sql b/moped-database/migrations/1735340549358_add-funding-status-description/down.sql new file mode 100644 index 0000000000..5d16624389 --- /dev/null +++ b/moped-database/migrations/1735340549358_add-funding-status-description/down.sql @@ -0,0 +1,3 @@ +-- Remove the funding_status_description column +ALTER TABLE "public"."moped_fund_status" +DROP COLUMN "funding_status_description"; diff --git a/moped-database/migrations/1735340549358_add-funding-status-description/up.sql b/moped-database/migrations/1735340549358_add-funding-status-description/up.sql new file mode 100644 index 0000000000..19bdbc1c90 --- /dev/null +++ b/moped-database/migrations/1735340549358_add-funding-status-description/up.sql @@ -0,0 +1,14 @@ +ALTER TABLE "public"."moped_fund_status" +ADD COLUMN "funding_status_description" text NULL; + +COMMENT ON COLUMN "public"."moped_fund_status"."funding_status_description" IS 'Description of the funding status'; + +UPDATE "public"."moped_fund_status" +SET "funding_status_description" = CASE "funding_status_name" + WHEN 'Tentative' THEN 'In conversation about possible funding commitment' + WHEN 'Confirmed' THEN 'Commitment to funding' + WHEN 'Available' THEN 'Funding is available, e.g. private developer' + WHEN 'Funding setup requested' THEN 'Requested that funding be set up in eCAPRIS' + WHEN 'Set up' THEN 'Funding has been set up in eCAPRIS; has FDU' + ELSE "funding_status_description" +END; \ No newline at end of file diff --git a/moped-editor/package.json b/moped-editor/package.json index 4678fa81a5..9e13b90b23 100644 --- a/moped-editor/package.json +++ b/moped-editor/package.json @@ -2,7 +2,7 @@ "name": "atd-moped-editor", "author": "ATD Data & Technology Services", "license": "CC0-1.0", - "version": "2.26.0", + "version": "2.27.0", "homepage": "/moped", "private": false, "repository": { diff --git a/moped-editor/src/components/DataGridPro/DataGridActions.js b/moped-editor/src/components/DataGridPro/DataGridActions.js index fa682daed1..f6e8c27f3a 100644 --- a/moped-editor/src/components/DataGridPro/DataGridActions.js +++ b/moped-editor/src/components/DataGridPro/DataGridActions.js @@ -45,8 +45,12 @@ const DataGridActions = ({ */ const hasRequiredFields = useGridSelector(apiRef, () => { const editState = apiRef.current.state.editRows; + for (const field of requiredFields) { - if (!editState[id]?.[field]?.value) { + const hasError = Boolean(editState[id]?.[field]?.error); + const hasValue = Boolean(editState[id]?.[field]?.value); + + if (hasError || !hasValue) { return false; } } @@ -58,6 +62,7 @@ const DataGridActions = ({ } label="Save" + key="save" sx={{ color: "primary.main", }} @@ -67,6 +72,7 @@ const DataGridActions = ({ } label="Cancel" + key="cancel" className="textPrimary" onClick={handleCancelClick(id)} color="inherit" @@ -79,6 +85,7 @@ const DataGridActions = ({ } label="Edit" + key="edit" className="textPrimary" onClick={handleEditClick(id)} color="inherit" @@ -87,6 +94,7 @@ const DataGridActions = ({ } label="Delete" + key="delete" onClick={handleDeleteOpen(id)} color="inherit" />, diff --git a/moped-editor/src/components/DataGridPro/DataGridTextField.js b/moped-editor/src/components/DataGridPro/DataGridTextField.js index 1fcd5a2379..425e5a28cd 100644 --- a/moped-editor/src/components/DataGridPro/DataGridTextField.js +++ b/moped-editor/src/components/DataGridPro/DataGridTextField.js @@ -42,7 +42,7 @@ const DataGridTextField = ({ return ( { value={undefined} onChange={handleFileNameChange} fullWidth + helperText={"Required"} /> @@ -244,7 +245,7 @@ const FileUploadDialogSingle = (props) => { value={undefined} onChange={handleExternalLinkChange} fullWidth - helperText={"Enter URL or network location"} + helperText={"Required. Enter URL or network location"} /> ) : ( { - const apiRef = useGridApiContext(); - const ref = React.useRef(null); - - React.useEffect(() => { - if (hasFocus) { - ref.current.focus(); - } - }, [hasFocus]); - - const handleChange = (event, newValue) => { - const { value: inputValue } = event.target; - - apiRef.current.setEditCellValue({ - id, - field, - value: inputValue, - }); - }; - - return ( - - ); -}; - -export default DataGridTextField; diff --git a/moped-editor/src/views/projects/projectView/ProjectFiles.js b/moped-editor/src/views/projects/projectView/ProjectFiles.js index c880610978..9d1044b52c 100644 --- a/moped-editor/src/views/projects/projectView/ProjectFiles.js +++ b/moped-editor/src/views/projects/projectView/ProjectFiles.js @@ -29,7 +29,7 @@ import downloadFileAttachment from "../../../utils/downloadFileAttachment"; import { FormattedDateString } from "src/utils/dateAndTime"; import { isValidUrl } from "src/utils/urls"; import ProjectFilesToolbar from "./ProjectFilesToolbar"; -import DataGridTextField from "./DataGridTextField"; +import DataGridTextField from "src/components/DataGridPro/DataGridTextField"; import ProjectFilesTypeSelect from "./ProjectFilesTypeSelect"; import DeleteConfirmationModal from "./DeleteConfirmationModal"; import DataGridActions from "src/components/DataGridPro/DataGridActions"; @@ -64,7 +64,7 @@ const fileTypes = ["", "Funding", "Plans", "Estimates", "Other"]; // 'private/project/65/80_04072022191747_40d4c982e064d0f9_1800halfscofieldridgepwkydesignprint.pdf' const cleanUpFileKey = (str) => str.replace(/^(?:[^_]*_){3}/g, ""); -const requiredFields = ["file_name", "file_url", "file_type"]; +const requiredFields = ["file_name", "file_type"]; const useColumns = ({ classes, @@ -161,7 +161,7 @@ const useColumns = ({ field: "file_description", editable: true, width: 200, - renderEditCell: (props) => , + renderEditCell: (props) => , }, { headerName: "Uploaded by", diff --git a/moped-editor/src/views/projects/projectView/ProjectFilesTypeSelect.js b/moped-editor/src/views/projects/projectView/ProjectFilesTypeSelect.js index 51860beb60..6544350982 100644 --- a/moped-editor/src/views/projects/projectView/ProjectFilesTypeSelect.js +++ b/moped-editor/src/views/projects/projectView/ProjectFilesTypeSelect.js @@ -30,7 +30,7 @@ const ProjectFilesTypeSelect = ({ id, value, field, hasFocus }) => { }; return ( - + } @@ -88,7 +89,7 @@ const ProjectTeamRoleMultiselect = ({ id, field, roles, value }) => { } )} - Required + Required ); }; diff --git a/moped-editor/src/views/projects/projectView/ProjectTeam/ProjectTeamTable.js b/moped-editor/src/views/projects/projectView/ProjectTeam/ProjectTeamTable.js index b0b1973c5a..e13565048a 100644 --- a/moped-editor/src/views/projects/projectView/ProjectTeam/ProjectTeamTable.js +++ b/moped-editor/src/views/projects/projectView/ProjectTeam/ProjectTeamTable.js @@ -4,23 +4,11 @@ import { v4 as uuidv4 } from "uuid"; import { Box, Icon, Link, CircularProgress, Typography } from "@mui/material"; import makeStyles from "@mui/styles/makeStyles"; -import { - EditOutlined as EditOutlinedIcon, - DeleteOutline as DeleteOutlineIcon, - Check as CheckIcon, - Close as CloseIcon, -} from "@mui/icons-material"; -import { - DataGridPro, - GridRowModes, - GridActionsCellItem, - useGridApiRef, -} from "@mui/x-data-grid-pro"; +import { DataGridPro, GridRowModes, useGridApiRef } from "@mui/x-data-grid-pro"; import { useQuery, useMutation } from "@apollo/client"; import ApolloErrorHandler from "src/components/ApolloErrorHandler"; -import { defaultEditColumnIconStyle } from "src/utils/dataGridHelpers"; import { TEAM_QUERY, UPDATE_PROJECT_PERSONNEL, @@ -31,6 +19,7 @@ import dataGridProStyleOverrides from "src/styles/dataGridProStylesOverrides"; import ProjectTeamToolbar from "./ProjectTeamToolbar"; import ProjectTeamRoleMultiselect from "./ProjectTeamRoleMultiselect"; import TeamAutocompleteComponent from "./TeamAutocompleteComponent"; +import DataGridActions from "src/components/DataGridPro/DataGridActions"; import DataGridTextField from "src/components/DataGridPro/DataGridTextField"; import DeleteConfirmationModal from "../DeleteConfirmationModal"; @@ -70,6 +59,8 @@ const useRoleNameLookup = (data) => }, {}); }, [data]); +const requiredFields = ["moped_user", "moped_proj_personnel_roles"]; + const useColumns = ({ data, rowModesModel, @@ -98,9 +89,16 @@ const useColumns = ({ name={"user"} value={props.row.moped_user} nameLookup={teamNameLookup} + error={props.error} /> ); }, + preProcessEditCellProps: (params) => { + // Enforce required field + const hasError = + !params.props.value || params.props.value.length === 0; + return { ...params.props, error: hasError }; + }, }, { headerName: "Workgroup", @@ -146,6 +144,7 @@ const useColumns = ({ {...props} value={props.row.moped_proj_personnel_roles || []} roles={data.moped_project_roles} + error={props.error} /> ); }, @@ -171,43 +170,17 @@ const useColumns = ({ sortable: false, editable: false, type: "actions", - getActions: ({ id }) => { - const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit; - if (isInEditMode) { - return [ - } - label="Save" - sx={{ - color: "primary.main", - }} - onClick={handleSaveClick(id)} - />, - } - label="Cancel" - className="textPrimary" - onClick={handleCancelClick(id, "project_personnel_id")} - color="inherit" - />, - ]; - } - return [ - } - label="Edit" - className="textPrimary" - onClick={handleEditClick(id)} - color="inherit" - />, - } - label="Delete" - onClick={() => handleDeleteOpen(id)} - color="inherit" - />, - ]; - }, + renderCell: ({ id }) => ( + + ), }, ]; }, [ @@ -365,12 +338,12 @@ const ProjectTeamTable = ({ projectId }) => { ); const handleCancelClick = useCallback( - (id, tableId) => () => { + (id) => () => { setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View, ignoreModifications: true }, }); - const editedRow = rows.find((row) => row[tableId] === id); + const editedRow = rows.find((row) => row.project_personnel_id === id); if (editedRow.isNew) { setRows(rows.filter((row) => row.id !== id)); } @@ -378,10 +351,13 @@ const ProjectTeamTable = ({ projectId }) => { [rowModesModel, rows] ); - const handleDeleteOpen = useCallback((id) => { - setIsDeleteConfirmationOpen(true); - setDeleteConfirmationId(id); - }, []); + const handleDeleteOpen = useCallback( + (id) => () => { + setIsDeleteConfirmationOpen(true); + setDeleteConfirmationId(id); + }, + [] + ); const processRowUpdate = useCallback( (updatedRow, originalRow, params, data) => { diff --git a/moped-editor/src/views/projects/projectView/ProjectTeam/TeamAutocompleteComponent.js b/moped-editor/src/views/projects/projectView/ProjectTeam/TeamAutocompleteComponent.js index 223fa4764f..87388b8b88 100644 --- a/moped-editor/src/views/projects/projectView/ProjectTeam/TeamAutocompleteComponent.js +++ b/moped-editor/src/views/projects/projectView/ProjectTeam/TeamAutocompleteComponent.js @@ -1,13 +1,18 @@ import React from "react"; -import { - Autocomplete, - TextField, - FormHelperText, - FormControl, -} from "@mui/material"; +import { Autocomplete, TextField, FormControl } from "@mui/material"; import { useGridApiContext } from "@mui/x-data-grid-pro"; import { useTheme } from "@mui/material/styles"; +/** + * @param {Integer} id - Data Grid row id (same as record id) + * @param {String} value - field value + * @param {String} field - name of field + * @param {Boolean} hasFocus - does this field have focus + * @param {Object} nameLookup - maps user id to user name + * @param {Boolean} error - toggles error style in textfield + * @param {Object} name - name of the field + * @return {JSX.Element} + */ const TeamAutocompleteComponent = ({ id, value, @@ -65,10 +70,7 @@ const TeamAutocompleteComponent = ({ }; return ( - + { handleChange(event, newValue); }} @@ -88,10 +90,10 @@ const TeamAutocompleteComponent = ({ {...params} inputRef={ref} error={error} + helperText="Required" /> )} /> - Required ); };