From c1a23abde7874c3f6ebaa646ac2729ee02af764a Mon Sep 17 00:00:00 2001 From: Daniel Stefan Date: Tue, 5 Jul 2022 17:30:50 +0300 Subject: [PATCH] feat: edit project staging --- src/components/blocks/StagingDataGroups.js | 140 ++++---- src/components/blocks/StagingDataTable.js | 311 ------------------ src/components/blocks/UploadXLSX.js | 2 +- src/components/blocks/index.js | 1 - .../forms/ProjectEditStagingModal.js | 251 ++++++++++++++ src/components/forms/index.js | 1 + src/components/icons/EditIcon.js | 22 ++ src/components/icons/RemoveIcon.js | 22 ++ src/components/icons/index.js | 2 + src/pages/Projects/index.js | 14 +- src/pages/Units/index.js | 14 +- src/store/actions/climateWarehouseActions.js | 52 ++- src/translations/tokens/en-US.json | 3 +- src/translations/tokens/es.json | 3 +- src/translations/tokens/ja.json | 3 +- 15 files changed, 456 insertions(+), 385 deletions(-) delete mode 100644 src/components/blocks/StagingDataTable.js create mode 100644 src/components/forms/ProjectEditStagingModal.js create mode 100644 src/components/icons/EditIcon.js create mode 100644 src/components/icons/RemoveIcon.js diff --git a/src/components/blocks/StagingDataGroups.js b/src/components/blocks/StagingDataGroups.js index 3ab7e504..41efa765 100644 --- a/src/components/blocks/StagingDataGroups.js +++ b/src/components/blocks/StagingDataGroups.js @@ -1,12 +1,23 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import styled, { withTheme } from 'styled-components'; import { FormattedMessage, useIntl } from 'react-intl'; + import { convertPascalCaseToSentenceCase } from '../../utils/stringUtils'; import { getDiff } from '../../utils/objectUtils'; -import { Modal, MinusIcon, Body, ErrorIcon, SuccessIcon, ReloadIcon } from '..'; -import { modalTypeEnum, APIStagingPagination } from '.'; +import { + Modal, + RemoveIcon, + Body, + ErrorIcon, + SuccessIcon, + ReloadIcon, + EditIcon, + ProjectEditStagingModal, + DetailedViewStagingModal, + modalTypeEnum, + APIStagingPagination, +} from '..'; import { useWindowSize } from '../hooks/useWindowSize'; -import { DetailedViewStagingModal } from './DetailedViewStagingModal'; const StyledPaginationContainer = styled('div')` box-sizing: border-box; @@ -85,6 +96,9 @@ const StyledDeleteGroupIcon = styled('div')` top: 16px; right: 20px; cursor: pointer; + display: flex; + flex-direction: row; + gap: 15px; `; const StyledRetryGroupIcon = styled('div')` @@ -134,9 +148,9 @@ const ChangeCard = ({ heading !== 'unitBlockEnd' && heading !== 'unitBlockStart', ) .map((heading, index) => ( - <> + {!(typeof data[heading] === 'object') && ( - + {convertPascalCaseToSentenceCase(heading)} @@ -156,7 +170,7 @@ const ChangeCard = ({ )} {typeof data[heading] === 'object' && ( - + {convertPascalCaseToSentenceCase(heading)} @@ -177,7 +191,7 @@ const ChangeCard = ({ )} - + ))} @@ -210,14 +224,14 @@ const StagingDataGroups = withTheme( retryStagingData, }) => { const [detailedViewData, setDetailedViewData] = useState(null); - const [deleteFromStaging, setDeleteFromStaging] = useState(false); - const [deleteUUID, setDeleteUUID] = useState(); + const [changeGroupToBeEdited, setChangeGroupToBeEdited] = useState(null); + const [uuidToBeDeleted, setUuidToBeDeleted] = useState(null); const ref = useRef(null); const [height, setHeight] = useState(0); const windowSize = useWindowSize(); const intl = useIntl(); - const changeGroupIsValid = changeGroup => { + const getIsChangeGroupValid = useCallback(changeGroup => { if (!changeGroup.diff) { return false; } @@ -238,7 +252,7 @@ const StagingDataGroups = withTheme( } return true; - }; + }, []); useEffect(() => { setHeight( @@ -246,15 +260,18 @@ const StagingDataGroups = withTheme( ); }, [ref.current, windowSize.height, data]); - const onDeleteStaging = uuid => { - if (!deleteStagingData) return null; - return () => { - deleteStagingData(uuid); - setDeleteFromStaging(false); - }; - }; + const onDeleteStaging = useCallback( + uuid => { + if (!deleteStagingData) return null; + return () => { + deleteStagingData(uuid); + setUuidToBeDeleted(null); + }; + }, + [deleteStagingData, setUuidToBeDeleted], + ); - const getTranslatedCardTitle = changeGroup => { + const getTranslatedCardTitle = useCallback(changeGroup => { const table = changeGroup.table.toLowerCase(); const action = changeGroup.action.toLowerCase(); let translationId = 'record'; @@ -286,7 +303,9 @@ const StagingDataGroups = withTheme( return intl.formatMessage({ id: translationId, }); - }; + }, []); + + console.log('changeGroupToBeEdited', changeGroupToBeEdited); return ( @@ -295,18 +314,20 @@ const StagingDataGroups = withTheme( headings && data.map((changeGroup, index) => ( - {changeGroupIsValid(changeGroup) && ( + {getIsChangeGroupValid(changeGroup) && ( {deleteStagingData && (
{ - setDeleteUUID(changeGroup.uuid); - setDeleteFromStaging(true); - }} + onClick={() => setUuidToBeDeleted(changeGroup.uuid)} > - +
+ setChangeGroupToBeEdited(changeGroup)} + />
)} {retryStagingData && ( @@ -367,34 +388,33 @@ const StagingDataGroups = withTheme( )} {changeGroup.action === 'UPDATE' && changeGroup.diff.change.map((change, index) => ( - - setDetailedViewData({ - record: changeGroup.diff.original, - changes: changeGroup.diff.change, - title: getTranslatedCardTitle(changeGroup), - action: changeGroup.action, - }) - } - addedIsVisible - /> + + + setDetailedViewData({ + record: changeGroup.diff.original, + changes: changeGroup.diff.change, + title: getTranslatedCardTitle(changeGroup), + action: changeGroup.action, + }) + } + addedIsVisible + /> + ))}
)} - {!changeGroupIsValid(changeGroup) && ( + {!getIsChangeGroupValid(changeGroup) && ( -
{ - setDeleteUUID(changeGroup.uuid); - setDeleteFromStaging(true); - }} - > - +
setUuidToBeDeleted(changeGroup.uuid)}> +
{ - setDeleteUUID(changeGroup.uuid); - setDeleteFromStaging(true); - }} + onDeleteChangeGroup={() => + setUuidToBeDeleted(changeGroup.uuid) + } /> )} @@ -432,7 +451,7 @@ const StagingDataGroups = withTheme( } /> )} - {deleteFromStaging && ( + {uuidToBeDeleted && ( setDeleteFromStaging(false)} - onOk={onDeleteStaging(deleteUUID)} + onClose={() => setUuidToBeDeleted(null)} + onOk={onDeleteStaging(uuidToBeDeleted)} + /> + )} + {changeGroupToBeEdited && ( + setChangeGroupToBeEdited(null)} + modalSizeAndPosition={modalSizeAndPosition} /> )}
diff --git a/src/components/blocks/StagingDataTable.js b/src/components/blocks/StagingDataTable.js deleted file mode 100644 index 674dc603..00000000 --- a/src/components/blocks/StagingDataTable.js +++ /dev/null @@ -1,311 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { useSelector } from 'react-redux'; -import { useIntl } from 'react-intl'; -import styled, { withTheme } from 'styled-components'; -import { TableCellHeaderText, TableCellText } from '../typography'; -import { convertPascalCaseToSentenceCase } from '../../utils/stringUtils'; -import { Modal, MinusIcon, modalTypeEnum } from '..'; -import { TableDrawer } from './'; -import { useWindowSize } from '../hooks/useWindowSize'; - -const Table = styled('table')` - background-color: white; - display: table; - border-spacing: 0; - border-collapse: collapse; - margin: 0px 0px 50px 0px; - width: 100%; -`; - -const THead = styled('thead')` - font-weight: 500; - background-color: ${props => - props.theme.colors[props.selectedTheme].secondary}; -`; - -const Th = styled('th')` - padding: 1rem; - color: ${props => props.theme.colors[props.selectedTheme].onSurface}; - display: table-cell; - text-align: center; - letter-spacing: 0.01071em; - vertical-align: inherit; - max-width: 80px; - min-width: 80px; -`; - -const Tr = styled('tr')` - ${props => { - if (props.color === 'green') { - return ` - border: 1px solid ${props.theme.colors.default.status.ok.primary}; - background: ${props.theme.colors.default.status.ok.secondary}; - p, span { - color: ${props.theme.colors.default.status.ok.primary} !important; - }; - `; - } else if (props.color === 'red') { - return ` - background: ${props.theme.colors.default.status.error.secondary}; - border: 1px solid ${props.theme.colors.default.status.error.primary} - p, span { - color: ${props.theme.colors.default.status.error.primary} !important; - }; - `; - } else if (props.color === 'gray') { - return `border: 1px solid ${props.theme.colors.default.secondary};`; - } - }}; - th:last-child { - text-align: center; - max-width: 50px; - min-width: 50px; - } -`; - -const Td = styled('td')` - display: table-cell; - padding: 1rem; - text-align: center; - letter-spacing: 0.01071em; - vertical-align: inherit; - max-width: 80px; - min-width: 80px; -`; - -const StagingDataTableContainer = styled('div')` - height: 100%; -`; - -const ChangeGroupHeader = ({ headings, appStore }) => { - return ( - - - {headings && - headings.map((heading, index) => ( - - - {convertPascalCaseToSentenceCase(heading)} - - - ))} - - - - - - - ); -}; - -const ChangeGroupItem = ({ - headings, - data, - appStore, - color, - onClick, - onDeleteStaging, -}) => { - return ( - <> - - {headings.map((key, index) => ( - - - {data[key] ? data[key].toString() : '--'} - - - ))} - - {onDeleteStaging && ( -
- - - -
- )} - - - - - - - ); -}; - -const InvalidChangeGroup = ({ onDeleteStaging, appStore, headings }) => { - return ( - <> - - - - - This change request has been corrupted, click here to remove. - - - - - {onDeleteStaging && ( -
- - - -
- )} - - - - ); -}; - -const StagingDataTable = withTheme(({ headings, data, deleteStagingData }) => { - const appStore = useSelector(state => state.app); - const [getRecord, setRecord] = useState(null); - const [deleteFromStaging, setDeleteFromStaging] = useState(false); - const [deleteUUID, setDeleteUUID] = useState(); - const ref = useRef(null); - const [height, setHeight] = useState(0); - const windowSize = useWindowSize(); - const intl = useIntl(); - - const changeGroupIsValid = changeGroup => { - if (!changeGroup.diff) { - return false; - } - - if (!changeGroup.diff.original || !changeGroup.diff.change) { - return false; - } - - if (changeGroup.diff.change.length === 1 && !changeGroup.diff.change[0]) { - return false; - } - - if ( - changeGroup.diff.change.length === 2 && - (!changeGroup.diff.change[0] || !changeGroup.diff.change[1]) - ) { - return false; - } - - return true; - }; - - useEffect(() => { - setHeight(windowSize.height - ref.current.getBoundingClientRect().top - 20); - }, [ref.current, windowSize.height, data]); - - const onDeleteStaging = uuid => { - if (!deleteStagingData) return null; - return () => { - deleteStagingData(uuid); - setDeleteFromStaging(false); - }; - }; - - return ( - -
- {data && - headings && - data.map((changeGroup, index) => ( - - - - {!changeGroupIsValid(changeGroup) && ( - { - setDeleteUUID(changeGroup.uuid); - setDeleteFromStaging(true); - }} - /> - )} - {changeGroup.action === 'DELETE' && - changeGroupIsValid(changeGroup) && ( - setRecord(changeGroup.diff.original)} - onDeleteStaging={() => { - setDeleteUUID(changeGroup.uuid); - setDeleteFromStaging(true); - }} - /> - )} - {changeGroup.action === 'INSERT' && - changeGroupIsValid(changeGroup) && ( - setRecord(changeGroup.diff.change[0])} - onDeleteStaging={() => { - setDeleteUUID(changeGroup.uuid); - setDeleteFromStaging(true); - }} - /> - )} - {changeGroup.action === 'UPDATE' && - changeGroupIsValid(changeGroup) && ( - <> - setRecord(changeGroup.diff.original)} - onDeleteStaging={() => { - setDeleteUUID(changeGroup.uuid); - setDeleteFromStaging(true); - }} - /> - {changeGroup.diff.change.map((change, index) => ( - setRecord(change)} - /> - ))} - - )} - -
- ))} - setRecord(null)} /> - {deleteFromStaging && ( - setDeleteFromStaging(false)} - onOk={onDeleteStaging(deleteUUID)} - /> - )} -
-
- ); -}); - -export { StagingDataTable }; diff --git a/src/components/blocks/UploadXLSX.js b/src/components/blocks/UploadXLSX.js index 6635c3dd..e47d9787 100644 --- a/src/components/blocks/UploadXLSX.js +++ b/src/components/blocks/UploadXLSX.js @@ -27,7 +27,7 @@ const UploadXLSX = ({ type }) => { return ( <> diff --git a/src/components/blocks/index.js b/src/components/blocks/index.js index d5e3e0ed..888af5a1 100644 --- a/src/components/blocks/index.js +++ b/src/components/blocks/index.js @@ -15,7 +15,6 @@ export * from './APIStagingPagination'; export * from './TableDrawer'; export * from './ComponentRepeater'; export * from './Modal'; -export * from './StagingDataTable'; export * from './APIDataTable'; export * from './NotificationCard'; export * from './BasicMenu'; diff --git a/src/components/forms/ProjectEditStagingModal.js b/src/components/forms/ProjectEditStagingModal.js new file mode 100644 index 00000000..38ed5b82 --- /dev/null +++ b/src/components/forms/ProjectEditStagingModal.js @@ -0,0 +1,251 @@ +import _ from 'lodash'; +import React, { useState, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Stepper, Step, StepLabel } from '@mui/material'; +import { useIntl } from 'react-intl'; +import { Formik, setNestedObjectValues } from 'formik'; + +import { + Modal, + TabPanel, + modalTypeEnum, + StyledFormContainer, + FormikRepeater, + ProjectDetailsForm, + ProjectIssuanceForm, + ProjectLocationForm, + ProjectEstimationForm, + ProjectLabelForm, + ProjectRatingForm, + ProjectCoBenefitForm, + ProjectRelatedProjectForm, + emptyIssuance, + emptyCobenefit, + emptyEstimation, + emptyLocation, + emptyLabel, + emptyRelatedProject, + emptyRating, +} from '..'; +import { editStagingData } from '../../store/actions/climateWarehouseActions'; +import { cleanObjectFromEmptyFieldsOrArrays } from '../../utils/formatData'; +import { projectSchema } from '../../store/validations'; + +const ProjectEditStagingModal = ({ + onClose, + changeGroup, + modalSizeAndPosition = { modalSizeAndPosition }, +}) => { + const [project, setProject] = useState(changeGroup?.diff?.change[0] ?? null); + const [tabValue, setTabValue] = useState(0); + const dispatch = useDispatch(); + const intl = useIntl(); + const { notification, showProgressOverlay: apiResponseIsPending } = + useSelector(state => state.app); + + useEffect(() => { + setProject(changeGroup?.diff?.change[0]); + }, [changeGroup]); + + const stepperStepsTranslationIds = [ + 'project', + 'issuances', + 'project-locations', + 'estimations', + 'labels', + 'ratings', + 'co-benefits', + 'related-projects', + ]; + + const onChangeStepTo = async ({ formik, desiredStep = null }) => { + const errors = await formik.validateForm(); + + // manually setting touched for error fields so errors are displayed + formik.setTouched(setNestedObjectValues(errors, true)); + + const isProjectValid = _.isEmpty(errors); + + if (isProjectValid) { + if ( + desiredStep >= stepperStepsTranslationIds.length && + !apiResponseIsPending + ) { + formik.submitForm(); + } else { + setTabValue(desiredStep); + } + } + }; + + // if project was successfully edited, close modal + const projectWasSuccessfullyEdited = + notification?.id === 'project-successfully-edited'; + useEffect(() => { + if (projectWasSuccessfullyEdited) { + onClose(); + } + }, [notification]); + + if (!project) { + return null; + } + + return ( + { + const dataToSend = _.cloneDeep(values); + cleanObjectFromEmptyFieldsOrArrays(dataToSend); + dispatch(editStagingData(changeGroup.uuid, dataToSend)); + }} + > + {formik => ( + onChangeStepTo({ formik, desiredStep: tabValue + 1 })} + onClose={onClose} + modalType={modalTypeEnum.basic} + title={intl.formatMessage({ + id: 'edit-staged-project', + })} + label={intl.formatMessage({ + id: tabValue < 7 ? 'next' : 'update-project', + })} + extraButtonLabel={ + tabValue > 0 + ? intl.formatMessage({ + id: 'back', + }) + : undefined + } + extraButtonOnClick={() => + onChangeStepTo({ + formik, + desiredStep: tabValue > 0 ? tabValue - 1 : tabValue, + }) + } + body={ + + + {stepperStepsTranslationIds && + stepperStepsTranslationIds.map((stepTranslationId, index) => ( + + onChangeStepTo({ formik, desiredStep: index }) + } + sx={{ cursor: 'pointer' }} + > + + {intl.formatMessage({ + id: stepTranslationId, + })} + + + ))} + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ } + /> + )} +
+ ); +}; + +export { ProjectEditStagingModal }; diff --git a/src/components/forms/index.js b/src/components/forms/index.js index dad762ec..ca2fcfc7 100644 --- a/src/components/forms/index.js +++ b/src/components/forms/index.js @@ -9,6 +9,7 @@ export * from './ProjectLabelForm'; export * from './ProjectCoBenefitForm'; export * from './ProjectRelatedProjectForm'; export * from './ProjectEditModal'; +export * from './ProjectEditStagingModal'; export * from './UnitEditModal'; export * from './UnitCreateModal'; export * from './UnitDetailsForm'; diff --git a/src/components/icons/EditIcon.js b/src/components/icons/EditIcon.js new file mode 100644 index 00000000..9b8f1f58 --- /dev/null +++ b/src/components/icons/EditIcon.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { withTheme } from 'styled-components'; + +const EditIcon = withTheme(({ width, height, fill, ...props }) => { + return ( + + + + ); +}); + +export { EditIcon }; diff --git a/src/components/icons/RemoveIcon.js b/src/components/icons/RemoveIcon.js new file mode 100644 index 00000000..846d3893 --- /dev/null +++ b/src/components/icons/RemoveIcon.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { withTheme } from 'styled-components'; + +const RemoveIcon = withTheme(({ width, height, fill, ...props }) => { + return ( + + + + ); +}); + +export { RemoveIcon }; diff --git a/src/components/icons/index.js b/src/components/icons/index.js index 68d9bad8..e82dd753 100644 --- a/src/components/icons/index.js +++ b/src/components/icons/index.js @@ -35,3 +35,5 @@ export * from './LinkIcon'; export * from './ReloadIcon'; export * from './AscendingClockIcon'; export * from './DescendingClockIcon'; +export * from './EditIcon'; +export * from './RemoveIcon'; diff --git a/src/pages/Projects/index.js b/src/pages/Projects/index.js index 0e5fb9eb..5ec3bd03 100644 --- a/src/pages/Projects/index.js +++ b/src/pages/Projects/index.js @@ -29,7 +29,7 @@ import { CommitModal, Modal, modalTypeEnum, - MinusIcon, + RemoveIcon, ProjectDetailedViewModal, } from '../../components'; @@ -354,7 +354,13 @@ const Projects = () => { } + icon={ + + } onClick={() => { if ( _.isEmpty(stagingData.units.pending) && @@ -419,11 +425,11 @@ const Projects = () => { tabValue === 1 && stagingData?.projects?.staging?.length > 0 && ( setIsDeleteAllStagingVisible(true)}> - + )} downloadTxtFile('projects', searchParams)}> - + {pageIsMyRegistryPage && ( diff --git a/src/pages/Units/index.js b/src/pages/Units/index.js index e7e5c703..7e60e6ff 100644 --- a/src/pages/Units/index.js +++ b/src/pages/Units/index.js @@ -44,7 +44,7 @@ import { CommitModal, Modal, modalTypeEnum, - MinusIcon, + RemoveIcon, UnitsDetailViewModal, } from '../../components'; import theme from '../../theme'; @@ -353,7 +353,13 @@ const Units = () => { } + icon={ + + } onClick={() => { if ( _.isEmpty(stagingData.units.pending) && @@ -419,11 +425,11 @@ const Units = () => { tabValue === 1 && stagingData?.units?.staging?.length > 0 && ( setIsDeleteAllStagingVisible(true)}> - + )} downloadTxtFile('units', searchParams)}> - + {pageIsMyRegistryPage && ( diff --git a/src/store/actions/climateWarehouseActions.js b/src/store/actions/climateWarehouseActions.js index 6e066325..f0f22fb4 100644 --- a/src/store/actions/climateWarehouseActions.js +++ b/src/store/actions/climateWarehouseActions.js @@ -702,6 +702,53 @@ export const commitStagingData = (data, comment) => { }; }; +export const editStagingData = (uuid, data) => { + return async dispatch => { + try { + dispatch(activateProgressIndicator); + + const url = `${constants.API_HOST}/staging`; + const payload = { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ uuid, data }), + }; + const response = await fetchWrapper(url, payload); + + if (response.ok) { + dispatch(setConnectionCheck(true)); + dispatch( + setNotificationMessage( + NotificationMessageTypeEnum.success, + 'transactions-committed', + ), + ); + dispatch(getStagingData({ useMockedResponse: false })); + } else { + const errorResponse = await response.json(); + dispatch( + setNotificationMessage( + NotificationMessageTypeEnum.error, + formatApiErrorResponse(errorResponse, 'transactions-not-committed'), + ), + ); + } + } catch { + dispatch( + setNotificationMessage( + NotificationMessageTypeEnum.error, + 'transactions-not-committed', + ), + ); + dispatch(setConnectionCheck(false)); + } finally { + dispatch(deactivateProgressIndicator); + } + }; +}; + export const updateGovernancePickLists = data => { return async dispatch => { try { @@ -1439,10 +1486,7 @@ export const uploadXLSXFile = (file, type) => { dispatch( setNotificationMessage( NotificationMessageTypeEnum.error, - formatApiErrorResponse( - errorResponse, - errorResponse.error, - ), + formatApiErrorResponse(errorResponse, errorResponse.error), ), ); } diff --git a/src/translations/tokens/en-US.json b/src/translations/tokens/en-US.json index 8710f826..3f5d7411 100644 --- a/src/translations/tokens/en-US.json +++ b/src/translations/tokens/en-US.json @@ -411,5 +411,6 @@ "governance-picklists-updated-successfully": "The governance pick lists were updated successfully.", "governance-picklists-update-failed": "The governance pick lists could not be updated.", "governance-orglist-updated-successfully": "The governance organization list was updated successfully.", - "governance-orglist-update-failed": "The governance organization list could not be updated." + "governance-orglist-update-failed": "The governance organization list could not be updated.", + "edit-staged-project": "Edit staged project" } diff --git a/src/translations/tokens/es.json b/src/translations/tokens/es.json index faa44c82..943daba9 100644 --- a/src/translations/tokens/es.json +++ b/src/translations/tokens/es.json @@ -393,5 +393,6 @@ "governance-picklists-updated-successfully": "Las listas de selección de gobernanza se actualizaron correctamente.", "governance-picklists-update-failed": "Las listas de selección de gobernanza no se pudieron actualizar.", "governance-orglist-updated-successfully": "La lista de organizaciones de gobierno se actualizó correctamente.", - "governance-orglist-update-failed": "No se pudo actualizar la lista de organizaciones de gobierno." + "governance-orglist-update-failed": "No se pudo actualizar la lista de organizaciones de gobierno.", + "edit-staged-project": "Editar proyecto en etapas" } diff --git a/src/translations/tokens/ja.json b/src/translations/tokens/ja.json index 9bcb2038..31433a42 100644 --- a/src/translations/tokens/ja.json +++ b/src/translations/tokens/ja.json @@ -393,5 +393,6 @@ "governance-picklists-updated-successfully": "ガバナンス選択リストは正常に更新されました。", "governance-picklists-update-failed": "ガバナンス選択リストを更新できませんでした。", "governance-orglist-updated-successfully": "ガバナンス組織リストが正常に更新されました。", - "governance-orglist-update-failed": "ガバナンス組織リストを更新できませんでした。" + "governance-orglist-update-failed": "ガバナンス組織リストを更新できませんでした。", + "edit-staged-project": "段階的なプロジェクトを編集する" } \ No newline at end of file