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 (
-
+
- 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
);
};